doc2mcp 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +34 -8
  2. package/dist/index.js +174 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="./assets/doc2mcp-cli-banner.png" alt="doc2mcp — turn any docs site into a hosted MCP server from your terminal" width="100%" />
3
+ <img src="https://doc2mcp.site/doc2mcp-cli-banner.png" alt="doc2mcp — turn any docs site into a hosted MCP server from your terminal" width="100%" />
4
4
 
5
5
  # doc2mcp
6
6
 
@@ -13,7 +13,7 @@ Point it at a docs URL, and doc2mcp crawls, analyzes, and serves it as a token-s
13
13
  [![node](https://img.shields.io/node/v/doc2mcp?color=8b5cf6&logo=node.js)](https://nodejs.org)
14
14
  [![license](https://img.shields.io/npm/l/doc2mcp?color=8b5cf6)](https://github.com/gautammanak1/doc2mcp/blob/main/LICENSE)
15
15
 
16
- [Website](https://doc2mcp.site) · [Playground](https://doc2mcp.site/playground) · [Docs](https://doc2mcp.site/docs) · [CLI guide](https://doc2mcp.site/docs/cli)
16
+ [Website](https://doc2mcp.site) · [CLI](https://doc2mcp.site/cli) · [Docs](https://doc2mcp.site/docs) · [CLI guide](https://doc2mcp.site/docs/cli)
17
17
 
18
18
  </div>
19
19
 
@@ -51,11 +51,13 @@ doc2mcp https://docs.stripe.com
51
51
 
52
52
  # 3. When it's ready, pick your editor — the MCP is installed for you
53
53
  # ✔ Cursor ✔ VS Code ✔ Claude Desktop ✔ Windsurf
54
+
55
+ # 4. Chat with your docs without leaving the terminal
56
+ doc2mcp chat
54
57
  ```
55
58
 
56
- That's it. The same hosted pipeline powers the [website](https://doc2mcp.site) and the
57
- [Playground](https://doc2mcp.site/playground), so a project you create in the CLI shows up in your
58
- dashboard and marketplace too.
59
+ That's it. The same hosted pipeline powers the [website](https://doc2mcp.site), so a project you
60
+ create in the CLI shows up in your dashboard and marketplace too.
59
61
 
60
62
  ## Commands
61
63
 
@@ -67,6 +69,7 @@ dashboard and marketplace too.
67
69
  | [`doc2mcp whoami`](#doc2mcp-whoami) | Show the account you're signed in as |
68
70
  | [`doc2mcp list`](#doc2mcp-list) | List the MCP projects on your account |
69
71
  | [`doc2mcp install <projectId>`](#doc2mcp-install-projectid) | Install an existing MCP into your editors |
72
+ | [`doc2mcp chat [projectId]`](#doc2mcp-chat-projectid) | Chat with your docs in the terminal (AI answers from your MCP) |
70
73
  | `doc2mcp --version` | Print the installed CLI version |
71
74
  | `doc2mcp --help` | Show usage and all commands |
72
75
 
@@ -92,7 +95,7 @@ Tips:
92
95
  - Point at the **docs** URL (`https://docs.stripe.com`), not the marketing homepage.
93
96
  - The URL must start with `http://` or `https://`.
94
97
  - Conversions count against your plan's monthly limit (free includes 5/month), shared with the
95
- website, Playground, and chat.
98
+ website and chat.
96
99
 
97
100
  ---
98
101
 
@@ -161,6 +164,29 @@ You'll be prompted to choose which detected clients to write to:
161
164
 
162
165
  Existing config is merged, not overwritten.
163
166
 
167
+ ---
168
+
169
+ ### `doc2mcp chat [projectId]`
170
+
171
+ Chat with your docs **right from the terminal**. doc2mcp answers natural-language questions from
172
+ the crawled documentation — with cited sources — using the project's hosted MCP (the same
173
+ `ask_documentation` tool your editor calls). This is the Playground experience, in your shell.
174
+
175
+ ```bash
176
+ # Interactive: choose a project, then ask anything
177
+ doc2mcp chat
178
+
179
+ # Skip the picker by passing a project ID
180
+ doc2mcp chat prj_123abc
181
+
182
+ # One-shot answer (handy in scripts / CI)
183
+ doc2mcp chat prj_123abc -m "How do I authenticate requests?"
184
+ ```
185
+
186
+ - With no arguments, you pick from your `ready` projects.
187
+ - Type `/exit` (or press Esc) to leave an interactive session.
188
+ - Each answer lists the source pages it used so you can verify it.
189
+
164
190
  ## Configuration
165
191
 
166
192
  | Setting | Default | Notes |
@@ -175,7 +201,7 @@ Existing config is merged, not overwritten.
175
201
  | `command not found: doc2mcp` | You installed locally or your shell cached PATH. Reinstall with `npm i -g doc2mcp`, then run `hash -r` or open a new terminal. You can also use `npx doc2mcp <url>`. |
176
202
  | Browser doesn't open on `login` | Copy the printed URL into your browser manually, then approve. |
177
203
  | `login` can't reach the server | Confirm you're online; for self-hosting set `DOC2MCP_API_URL` to your instance. |
178
- | "Limit reached" | You've hit your plan's monthly conversion limit (shared across CLI, web, and Playground). |
204
+ | "Limit reached" | You've hit your plan's monthly conversion limit (shared across CLI and web). |
179
205
  | Editor doesn't pick up the MCP | Fully restart the editor after install so it reloads MCP config. |
180
206
 
181
207
  ## How it works
@@ -209,7 +235,7 @@ already exists).
209
235
 
210
236
  - 📦 npm: https://www.npmjs.com/package/doc2mcp
211
237
  - 🌐 Website: https://doc2mcp.site
212
- - 🕹️ Playground: https://doc2mcp.site/playground
238
+ - 🖥️ CLI page: https://doc2mcp.site/cli
213
239
  - 📚 Docs: https://doc2mcp.site/docs
214
240
  - 🧭 CLI guide: https://doc2mcp.site/docs/cli
215
241
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc6 from "picocolors";
5
+ import pc7 from "picocolors";
6
6
 
7
7
  // src/commands/account.ts
8
8
  import pc from "picocolors";
@@ -73,8 +73,10 @@ async function runWhoami() {
73
73
  `);
74
74
  }
75
75
 
76
- // src/commands/convert.ts
77
- import pc5 from "picocolors";
76
+ // src/commands/chat.ts
77
+ import { cancel, intro, isCancel, outro, select, text } from "@clack/prompts";
78
+ import ora2 from "ora";
79
+ import pc4 from "picocolors";
78
80
 
79
81
  // src/api.ts
80
82
  import pc2 from "picocolors";
@@ -88,14 +90,14 @@ var ApiError = class extends Error {
88
90
  }
89
91
  };
90
92
  async function parseJson(response) {
91
- const text = await response.text();
92
- if (!text) {
93
+ const text2 = await response.text();
94
+ if (!text2) {
93
95
  return null;
94
96
  }
95
97
  try {
96
- return JSON.parse(text);
98
+ return JSON.parse(text2);
97
99
  } catch {
98
- return { raw: text };
100
+ return { raw: text2 };
99
101
  }
100
102
  }
101
103
  async function apiFetch(path, options = {}) {
@@ -230,9 +232,151 @@ async function ensureLoggedIn() {
230
232
  }
231
233
  }
232
234
 
235
+ // src/commands/chat.ts
236
+ async function pickProject(explicitId) {
237
+ if (explicitId) {
238
+ return await apiFetch(`/api/cli/projects/${explicitId}`);
239
+ }
240
+ const data = await apiFetch(
241
+ "/api/cli/projects"
242
+ );
243
+ const ready = data.projects.filter((p) => p.status === "ready");
244
+ if (ready.length === 0) {
245
+ process.stdout.write(
246
+ `${pc4.yellow("No ready MCP projects yet.")} Create one: ${pc4.bold("doc2mcp <docs-url>")}
247
+ `
248
+ );
249
+ return null;
250
+ }
251
+ const choice = await select({
252
+ message: "Which docs do you want to chat with?",
253
+ options: ready.map((p) => ({
254
+ value: p.id,
255
+ label: p.name,
256
+ hint: p.sourceUrl ?? void 0
257
+ }))
258
+ });
259
+ if (isCancel(choice)) {
260
+ cancel("Cancelled.");
261
+ return null;
262
+ }
263
+ return await apiFetch(`/api/cli/projects/${choice}`);
264
+ }
265
+ async function askDocs(mcp, question) {
266
+ const response = await fetch(mcp.url, {
267
+ method: "POST",
268
+ headers: {
269
+ "Content-Type": "application/json",
270
+ Authorization: `Bearer ${mcp.token}`
271
+ },
272
+ body: JSON.stringify({
273
+ jsonrpc: "2.0",
274
+ id: 1,
275
+ method: "tools/call",
276
+ params: { name: "ask_documentation", arguments: { question } }
277
+ })
278
+ });
279
+ const payload = await response.json();
280
+ if (!response.ok || payload.error) {
281
+ throw new ApiError(
282
+ payload.error?.message ?? `MCP request failed (${response.status})`,
283
+ response.status,
284
+ payload
285
+ );
286
+ }
287
+ const raw = payload.result?.content?.[0]?.text ?? "";
288
+ try {
289
+ return JSON.parse(raw);
290
+ } catch {
291
+ return { question, answer: raw };
292
+ }
293
+ }
294
+ function renderAnswer(answer) {
295
+ process.stdout.write(`
296
+ ${pc4.cyan("\u25C6")} ${answer.answer.trim()}
297
+ `);
298
+ if (answer.sources && answer.sources.length > 0) {
299
+ process.stdout.write(`
300
+ ${pc4.dim("Sources:")}
301
+ `);
302
+ for (const source of answer.sources.slice(0, 6)) {
303
+ process.stdout.write(` ${pc4.dim("\u2022")} ${source.title} ${pc4.dim(source.url)}
304
+ `);
305
+ }
306
+ }
307
+ process.stdout.write("\n");
308
+ }
309
+ async function answerOnce(mcp, question) {
310
+ const spinner = ora2("Thinking\u2026").start();
311
+ try {
312
+ const answer = await askDocs(mcp, question);
313
+ spinner.stop();
314
+ renderAnswer(answer);
315
+ } catch (error) {
316
+ spinner.fail("Failed to get an answer");
317
+ printError(error);
318
+ }
319
+ }
320
+ async function runChat(projectId, options = {}) {
321
+ try {
322
+ await ensureLoggedIn();
323
+ const detail = await pickProject(projectId);
324
+ if (!detail) {
325
+ return;
326
+ }
327
+ if (!detail.mcp) {
328
+ process.stderr.write(
329
+ `${pc4.red("That project is not ready yet.")} Check: ${pc4.bold("doc2mcp list")}
330
+ `
331
+ );
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+ const { mcp } = detail;
336
+ if (options.message) {
337
+ await answerOnce(mcp, options.message);
338
+ return;
339
+ }
340
+ intro(
341
+ `${pc4.bold(`Chatting with ${detail.project.name}`)} ${pc4.dim("\u2014 ask anything about these docs")}`
342
+ );
343
+ process.stdout.write(
344
+ `${pc4.dim("Type your question. Use /exit to leave.")}
345
+ `
346
+ );
347
+ let active = true;
348
+ while (active) {
349
+ const question = await text({
350
+ message: "You",
351
+ placeholder: "How do I authenticate requests?"
352
+ });
353
+ if (isCancel(question)) {
354
+ active = false;
355
+ break;
356
+ }
357
+ const trimmed = String(question).trim();
358
+ if (!trimmed) {
359
+ continue;
360
+ }
361
+ if (trimmed === "/exit" || trimmed === "/quit") {
362
+ active = false;
363
+ break;
364
+ }
365
+ await answerOnce(mcp, trimmed);
366
+ }
367
+ outro(pc4.dim("Bye \u2014 your docs MCP stays live for your editor."));
368
+ } catch (error) {
369
+ printError(error);
370
+ process.exitCode = 1;
371
+ }
372
+ }
373
+
374
+ // src/commands/convert.ts
375
+ import pc6 from "picocolors";
376
+
233
377
  // src/commands/install.ts
234
378
  import { confirm, multiselect } from "@clack/prompts";
235
- import pc4 from "picocolors";
379
+ import pc5 from "picocolors";
236
380
 
237
381
  // src/installers/detect.ts
238
382
  import { access } from "fs/promises";
@@ -419,8 +563,8 @@ async function promptInstall(install) {
419
563
  }
420
564
  await installToClient(client.id, client.configPath, install);
421
565
  process.stdout.write(
422
- `${pc4.green("Installed")} ${client.label}
423
- ${pc4.dim(client.configPath)}
566
+ `${pc5.green("Installed")} ${client.label}
567
+ ${pc5.dim(client.configPath)}
424
568
  `
425
569
  );
426
570
  }
@@ -430,7 +574,7 @@ async function runInstallCommand(projectId) {
430
574
  await ensureLoggedIn();
431
575
  const detail = await apiFetch(`/api/cli/projects/${projectId}`);
432
576
  if (!detail.install) {
433
- process.stderr.write(`${pc4.red("Project is not ready or missing install bundle.")}
577
+ process.stderr.write(`${pc5.red("Project is not ready or missing install bundle.")}
434
578
  `);
435
579
  process.exitCode = 1;
436
580
  return;
@@ -451,19 +595,19 @@ function sleep2(ms) {
451
595
  function printStatus(detail) {
452
596
  const { project } = detail;
453
597
  process.stdout.write(
454
- `\r${pc5.cyan("Status:")} ${project.status.padEnd(12)} ${pc5.dim(project.name)}`
598
+ `\r${pc6.cyan("Status:")} ${project.status.padEnd(12)} ${pc6.dim(project.name)}`
455
599
  );
456
600
  }
457
601
  async function runConvert(sourceUrl) {
458
602
  try {
459
603
  await ensureLoggedIn();
460
- process.stdout.write(`${pc5.bold("Converting")} ${sourceUrl}
604
+ process.stdout.write(`${pc6.bold("Converting")} ${sourceUrl}
461
605
  `);
462
606
  const created = await apiFetch("/api/cli/convert", {
463
607
  method: "POST",
464
608
  body: JSON.stringify({ sourceUrl })
465
609
  });
466
- process.stdout.write(`${pc5.dim("Project:")} ${created.id}
610
+ process.stdout.write(`${pc6.dim("Project:")} ${created.id}
467
611
  `);
468
612
  let delayMs = 2e3;
469
613
  const terminal = /* @__PURE__ */ new Set(["ready", "error"]);
@@ -485,29 +629,29 @@ async function runConvert(sourceUrl) {
485
629
  if (finalDetail.project.status === "error") {
486
630
  const lastLog = finalDetail.project.logs.at(-1);
487
631
  process.stderr.write(
488
- `${pc5.red("Conversion failed.")}${lastLog ? ` ${lastLog.message}` : ""}
632
+ `${pc6.red("Conversion failed.")}${lastLog ? ` ${lastLog.message}` : ""}
489
633
  `
490
634
  );
491
635
  process.exitCode = 1;
492
636
  return;
493
637
  }
494
638
  if (!finalDetail.mcp || !finalDetail.install) {
495
- process.stderr.write(`${pc5.red("MCP ready but missing install bundle.")}
639
+ process.stderr.write(`${pc6.red("MCP ready but missing install bundle.")}
496
640
  `);
497
641
  process.exitCode = 1;
498
642
  return;
499
643
  }
500
644
  process.stdout.write(`
501
- ${pc5.green("MCP ready")}
645
+ ${pc6.green("MCP ready")}
502
646
  `);
503
- process.stdout.write(`${pc5.bold("Server:")} ${finalDetail.mcp.serverName}
647
+ process.stdout.write(`${pc6.bold("Server:")} ${finalDetail.mcp.serverName}
504
648
  `);
505
- process.stdout.write(`${pc5.bold("URL:")} ${finalDetail.mcp.url}
649
+ process.stdout.write(`${pc6.bold("URL:")} ${finalDetail.mcp.url}
506
650
  `);
507
- process.stdout.write(`${pc5.bold("Token:")} ${finalDetail.mcp.token}
651
+ process.stdout.write(`${pc6.bold("Token:")} ${finalDetail.mcp.token}
508
652
  `);
509
653
  process.stdout.write(
510
- `${pc5.dim("Also listed in the doc2mcp marketplace when ready.")}
654
+ `${pc6.dim("Also listed in the doc2mcp marketplace when ready.")}
511
655
  `
512
656
  );
513
657
  await promptInstall(finalDetail.install);
@@ -521,16 +665,16 @@ async function runList() {
521
665
  await ensureLoggedIn();
522
666
  const data = await apiFetch("/api/cli/projects");
523
667
  if (data.projects.length === 0) {
524
- process.stdout.write(`${pc5.dim("No projects yet.")}
668
+ process.stdout.write(`${pc6.dim("No projects yet.")}
525
669
  `);
526
670
  return;
527
671
  }
528
672
  for (const project of data.projects) {
529
673
  process.stdout.write(
530
- `${pc5.bold(project.name)} ${pc5.dim(`[${project.status}]`)} ${project.source}
674
+ `${pc6.bold(project.name)} ${pc6.dim(`[${project.status}]`)} ${project.source}
531
675
  `
532
676
  );
533
- process.stdout.write(` ${pc5.dim(project.id)} ${project.sourceUrl ?? ""}
677
+ process.stdout.write(` ${pc6.dim(project.id)} ${project.sourceUrl ?? ""}
534
678
  `);
535
679
  }
536
680
  } catch (error) {
@@ -541,7 +685,7 @@ async function runList() {
541
685
 
542
686
  // src/index.ts
543
687
  var program = new Command();
544
- program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.16", "-v, --version", "Print the installed CLI version");
688
+ program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.17", "-v, --version", "Print the installed CLI version");
545
689
  program.command("login").description("Authorize the CLI via browser").action(async () => {
546
690
  await runLogin();
547
691
  });
@@ -557,6 +701,9 @@ program.command("list").description("List your MCP projects").action(async () =>
557
701
  program.command("install <projectId>").description("Install an existing MCP into Cursor, VS Code, Claude, or Windsurf").action(async (projectId) => {
558
702
  await runInstallCommand(projectId);
559
703
  });
704
+ program.command("chat [projectId]").description("Chat with your docs in the terminal (AI answers from your MCP)").option("-m, --message <text>", "Ask a single question and exit").action(async (projectId, options) => {
705
+ await runChat(projectId, options);
706
+ });
560
707
  program.argument("[url]", "Documentation URL to convert").action(async (url) => {
561
708
  if (!url) {
562
709
  program.help();
@@ -569,7 +716,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
569
716
  }
570
717
  } catch {
571
718
  process.stderr.write(
572
- `${pc6.red("Error:")} Invalid URL. Example: doc2mcp https://docs.example.com
719
+ `${pc7.red("Error:")} Invalid URL. Example: doc2mcp https://docs.example.com
573
720
  `
574
721
  );
575
722
  process.exitCode = 1;
@@ -579,7 +726,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
579
726
  });
580
727
  program.parseAsync(process.argv).catch((error) => {
581
728
  const message = error instanceof Error ? error.message : "Unknown error";
582
- process.stderr.write(`${pc6.red("Error:")} ${message}
729
+ process.stderr.write(`${pc7.red("Error:")} ${message}
583
730
  `);
584
731
  process.exit(1);
585
732
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc2mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Turn any documentation site into a hosted MCP server from your terminal — for Cursor, Claude, VS Code, Windsurf, and OpenAI agents.",
5
5
  "type": "module",
6
6
  "bin": {