doc2mcp 0.1.16 → 0.1.18
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.
- package/README.md +56 -9
- package/dist/index.js +272 -50
- package/package.json +3 -1
- package/scripts/postinstall.js +48 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
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
|
[](https://nodejs.org)
|
|
14
14
|
[](https://github.com/gautammanak1/doc2mcp/blob/main/LICENSE)
|
|
15
15
|
|
|
16
|
-
[Website](https://doc2mcp.site) · [
|
|
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
|
|
|
@@ -29,6 +29,20 @@ npm install -g doc2mcp
|
|
|
29
29
|
> Install with **`-g`** (global). The `doc2mcp` command only lands on your `PATH` when installed globally.
|
|
30
30
|
> If you ran `npm i doc2mcp` (without `-g`) and see `command not found: doc2mcp`, either reinstall with `-g`
|
|
31
31
|
> or run it through your package runner: `npx doc2mcp <docs-url>`.
|
|
32
|
+
>
|
|
33
|
+
> If `npm install -g doc2mcp` succeeds but `doc2mcp` is still `command not found`, your npm global bin folder is
|
|
34
|
+
> not on PATH. Run:
|
|
35
|
+
>
|
|
36
|
+
> ```bash
|
|
37
|
+
> echo 'export PATH="'$(npm prefix -g)'/bin:$PATH"' >> ~/.zshrc
|
|
38
|
+
> source ~/.zshrc
|
|
39
|
+
> ```
|
|
40
|
+
>
|
|
41
|
+
> Quick no-setup option:
|
|
42
|
+
>
|
|
43
|
+
> ```bash
|
|
44
|
+
> npx doc2mcp login
|
|
45
|
+
> ```
|
|
32
46
|
|
|
33
47
|
Other package managers:
|
|
34
48
|
|
|
@@ -51,11 +65,16 @@ doc2mcp https://docs.stripe.com
|
|
|
51
65
|
|
|
52
66
|
# 3. When it's ready, pick your editor — the MCP is installed for you
|
|
53
67
|
# ✔ Cursor ✔ VS Code ✔ Claude Desktop ✔ Windsurf
|
|
68
|
+
|
|
69
|
+
# 4. Chat with your docs without leaving the terminal
|
|
70
|
+
doc2mcp chat
|
|
71
|
+
|
|
72
|
+
# Or paste a docs URL directly into chat mode
|
|
73
|
+
doc2mcp chat https://uagents.fetch.ai/docs
|
|
54
74
|
```
|
|
55
75
|
|
|
56
|
-
That's it. The same hosted pipeline powers the [website](https://doc2mcp.site)
|
|
57
|
-
|
|
58
|
-
dashboard and marketplace too.
|
|
76
|
+
That's it. The same hosted pipeline powers the [website](https://doc2mcp.site), so a project you
|
|
77
|
+
create in the CLI shows up in your dashboard and marketplace too.
|
|
59
78
|
|
|
60
79
|
## Commands
|
|
61
80
|
|
|
@@ -67,6 +86,7 @@ dashboard and marketplace too.
|
|
|
67
86
|
| [`doc2mcp whoami`](#doc2mcp-whoami) | Show the account you're signed in as |
|
|
68
87
|
| [`doc2mcp list`](#doc2mcp-list) | List the MCP projects on your account |
|
|
69
88
|
| [`doc2mcp install <projectId>`](#doc2mcp-install-projectid) | Install an existing MCP into your editors |
|
|
89
|
+
| [`doc2mcp chat [target]`](#doc2mcp-chat-target) | Chat with your docs in the terminal; target can be a project ID or docs URL |
|
|
70
90
|
| `doc2mcp --version` | Print the installed CLI version |
|
|
71
91
|
| `doc2mcp --help` | Show usage and all commands |
|
|
72
92
|
|
|
@@ -92,7 +112,7 @@ Tips:
|
|
|
92
112
|
- Point at the **docs** URL (`https://docs.stripe.com`), not the marketing homepage.
|
|
93
113
|
- The URL must start with `http://` or `https://`.
|
|
94
114
|
- Conversions count against your plan's monthly limit (free includes 5/month), shared with the
|
|
95
|
-
website
|
|
115
|
+
website and chat.
|
|
96
116
|
|
|
97
117
|
---
|
|
98
118
|
|
|
@@ -161,6 +181,32 @@ You'll be prompted to choose which detected clients to write to:
|
|
|
161
181
|
|
|
162
182
|
Existing config is merged, not overwritten.
|
|
163
183
|
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### `doc2mcp chat [target]`
|
|
187
|
+
|
|
188
|
+
Chat with your docs **right from the terminal**. doc2mcp answers natural-language questions from
|
|
189
|
+
the crawled documentation — with cited sources — using the project's hosted MCP (the same
|
|
190
|
+
`ask_documentation` tool your editor calls). This is the Playground experience, in a Claude Code-style shell loop.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Interactive: paste a docs URL, project ID, or choose an existing MCP
|
|
194
|
+
doc2mcp chat
|
|
195
|
+
|
|
196
|
+
# Paste a docs URL directly: doc2mcp converts it, then starts chat
|
|
197
|
+
doc2mcp chat https://uagents.fetch.ai/docs
|
|
198
|
+
|
|
199
|
+
# Skip the picker by passing a project ID
|
|
200
|
+
doc2mcp chat prj_123abc
|
|
201
|
+
|
|
202
|
+
# One-shot answer (handy in scripts / CI)
|
|
203
|
+
doc2mcp chat prj_123abc -m "How do I authenticate requests?"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- With no arguments, you pick from your `ready` projects.
|
|
207
|
+
- Type `/exit` to leave an interactive session.
|
|
208
|
+
- Each answer lists the source pages it used so you can verify it.
|
|
209
|
+
|
|
164
210
|
## Configuration
|
|
165
211
|
|
|
166
212
|
| Setting | Default | Notes |
|
|
@@ -172,10 +218,11 @@ Existing config is merged, not overwritten.
|
|
|
172
218
|
|
|
173
219
|
| Symptom | Fix |
|
|
174
220
|
| --- | --- |
|
|
175
|
-
| `command not found: doc2mcp` | You installed locally or
|
|
221
|
+
| `command not found: doc2mcp` | You installed locally or npm's global bin is not on PATH. Use `npx doc2mcp <url>`, or add `$(npm prefix -g)/bin` to PATH in `~/.zshrc`. |
|
|
222
|
+
| `pnpm add -g doc2mcp` says `ERR_PNPM_NO_GLOBAL_BIN_DIR` | Run `pnpm setup`, then `source ~/.zshrc`, then retry `pnpm add -g doc2mcp`. |
|
|
176
223
|
| Browser doesn't open on `login` | Copy the printed URL into your browser manually, then approve. |
|
|
177
224
|
| `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
|
|
225
|
+
| "Limit reached" | You've hit your plan's monthly conversion limit (shared across CLI and web). |
|
|
179
226
|
| Editor doesn't pick up the MCP | Fully restart the editor after install so it reloads MCP config. |
|
|
180
227
|
|
|
181
228
|
## How it works
|
|
@@ -209,7 +256,7 @@ already exists).
|
|
|
209
256
|
|
|
210
257
|
- 📦 npm: https://www.npmjs.com/package/doc2mcp
|
|
211
258
|
- 🌐 Website: https://doc2mcp.site
|
|
212
|
-
-
|
|
259
|
+
- 🖥️ CLI page: https://doc2mcp.site/cli
|
|
213
260
|
- 📚 Docs: https://doc2mcp.site/docs
|
|
214
261
|
- 🧭 CLI guide: https://doc2mcp.site/docs/cli
|
|
215
262
|
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc7 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/commands/account.ts
|
|
8
8
|
import pc from "picocolors";
|
|
@@ -73,8 +73,11 @@ async function runWhoami() {
|
|
|
73
73
|
`);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// src/commands/
|
|
77
|
-
import
|
|
76
|
+
// src/commands/chat.ts
|
|
77
|
+
import { createInterface } from "readline/promises";
|
|
78
|
+
import { stdin as input, stdout as output } from "process";
|
|
79
|
+
import ora2 from "ora";
|
|
80
|
+
import pc6 from "picocolors";
|
|
78
81
|
|
|
79
82
|
// src/api.ts
|
|
80
83
|
import pc2 from "picocolors";
|
|
@@ -136,6 +139,9 @@ function printError(error) {
|
|
|
136
139
|
`);
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
// src/commands/convert.ts
|
|
143
|
+
import pc5 from "picocolors";
|
|
144
|
+
|
|
139
145
|
// src/commands/login.ts
|
|
140
146
|
import open from "open";
|
|
141
147
|
import ora from "ora";
|
|
@@ -454,63 +460,69 @@ function printStatus(detail) {
|
|
|
454
460
|
`\r${pc5.cyan("Status:")} ${project.status.padEnd(12)} ${pc5.dim(project.name)}`
|
|
455
461
|
);
|
|
456
462
|
}
|
|
457
|
-
async function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
process.stdout.write(`${pc5.bold("Converting")} ${sourceUrl}
|
|
463
|
+
async function convertUrlToProject(sourceUrl, options = { offerInstall: true }) {
|
|
464
|
+
await ensureLoggedIn();
|
|
465
|
+
process.stdout.write(`${pc5.bold("Converting")} ${sourceUrl}
|
|
461
466
|
`);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
+
const created = await apiFetch("/api/cli/convert", {
|
|
468
|
+
method: "POST",
|
|
469
|
+
body: JSON.stringify({ sourceUrl })
|
|
470
|
+
});
|
|
471
|
+
process.stdout.write(`${pc5.dim("Project:")} ${created.id}
|
|
467
472
|
`);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
`/api/cli/projects/${created.id}`
|
|
473
|
-
);
|
|
474
|
-
printStatus(detail);
|
|
475
|
-
if (terminal.has(detail.project.status)) {
|
|
476
|
-
process.stdout.write("\n");
|
|
477
|
-
break;
|
|
478
|
-
}
|
|
479
|
-
await sleep2(delayMs);
|
|
480
|
-
delayMs = Math.min(delayMs + 1e3, 1e4);
|
|
481
|
-
}
|
|
482
|
-
const finalDetail = await apiFetch(
|
|
473
|
+
let delayMs = 2e3;
|
|
474
|
+
const terminal = /* @__PURE__ */ new Set(["ready", "error"]);
|
|
475
|
+
while (true) {
|
|
476
|
+
const detail = await apiFetch(
|
|
483
477
|
`/api/cli/projects/${created.id}`
|
|
484
478
|
);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
process.
|
|
488
|
-
|
|
489
|
-
`
|
|
490
|
-
);
|
|
491
|
-
process.exitCode = 1;
|
|
492
|
-
return;
|
|
479
|
+
printStatus(detail);
|
|
480
|
+
if (terminal.has(detail.project.status)) {
|
|
481
|
+
process.stdout.write("\n");
|
|
482
|
+
break;
|
|
493
483
|
}
|
|
494
|
-
|
|
495
|
-
|
|
484
|
+
await sleep2(delayMs);
|
|
485
|
+
delayMs = Math.min(delayMs + 1e3, 1e4);
|
|
486
|
+
}
|
|
487
|
+
const finalDetail = await apiFetch(
|
|
488
|
+
`/api/cli/projects/${created.id}`
|
|
489
|
+
);
|
|
490
|
+
if (finalDetail.project.status === "error") {
|
|
491
|
+
const lastLog = finalDetail.project.logs.at(-1);
|
|
492
|
+
process.stderr.write(
|
|
493
|
+
`${pc5.red("Conversion failed.")}${lastLog ? ` ${lastLog.message}` : ""}
|
|
494
|
+
`
|
|
495
|
+
);
|
|
496
|
+
process.exitCode = 1;
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
if (!finalDetail.mcp || !finalDetail.install) {
|
|
500
|
+
process.stderr.write(`${pc5.red("MCP ready but missing install bundle.")}
|
|
496
501
|
`);
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
process.stdout.write(`
|
|
501
506
|
${pc5.green("MCP ready")}
|
|
502
507
|
`);
|
|
503
|
-
|
|
508
|
+
process.stdout.write(`${pc5.bold("Server:")} ${finalDetail.mcp.serverName}
|
|
504
509
|
`);
|
|
505
|
-
|
|
510
|
+
process.stdout.write(`${pc5.bold("URL:")} ${finalDetail.mcp.url}
|
|
506
511
|
`);
|
|
507
|
-
|
|
512
|
+
process.stdout.write(`${pc5.bold("Token:")} ${finalDetail.mcp.token}
|
|
508
513
|
`);
|
|
509
|
-
|
|
510
|
-
|
|
514
|
+
process.stdout.write(
|
|
515
|
+
`${pc5.dim("Also listed in the doc2mcp marketplace when ready.")}
|
|
511
516
|
`
|
|
512
|
-
|
|
517
|
+
);
|
|
518
|
+
if (options.offerInstall) {
|
|
513
519
|
await promptInstall(finalDetail.install);
|
|
520
|
+
}
|
|
521
|
+
return finalDetail;
|
|
522
|
+
}
|
|
523
|
+
async function runConvert(sourceUrl) {
|
|
524
|
+
try {
|
|
525
|
+
await convertUrlToProject(sourceUrl, { offerInstall: true });
|
|
514
526
|
} catch (error) {
|
|
515
527
|
printError(error);
|
|
516
528
|
process.exitCode = 1;
|
|
@@ -539,9 +551,216 @@ async function runList() {
|
|
|
539
551
|
}
|
|
540
552
|
}
|
|
541
553
|
|
|
554
|
+
// src/commands/chat.ts
|
|
555
|
+
function isDocsUrl(value) {
|
|
556
|
+
try {
|
|
557
|
+
const url = new URL(value);
|
|
558
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
559
|
+
} catch {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function createPrompt() {
|
|
564
|
+
return createInterface({ input, output });
|
|
565
|
+
}
|
|
566
|
+
async function readLine(prompt) {
|
|
567
|
+
const rl = createPrompt();
|
|
568
|
+
try {
|
|
569
|
+
const answer = await rl.question(prompt);
|
|
570
|
+
return answer.trim();
|
|
571
|
+
} catch {
|
|
572
|
+
return null;
|
|
573
|
+
} finally {
|
|
574
|
+
rl.close();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function listReadyProjects() {
|
|
578
|
+
const data = await apiFetch(
|
|
579
|
+
"/api/cli/projects"
|
|
580
|
+
);
|
|
581
|
+
return data.projects.filter((p) => p.status === "ready");
|
|
582
|
+
}
|
|
583
|
+
async function pickExistingProject() {
|
|
584
|
+
const ready = await listReadyProjects();
|
|
585
|
+
if (ready.length === 0) {
|
|
586
|
+
process.stdout.write(
|
|
587
|
+
`${pc6.yellow("No ready MCP projects yet.")} Paste a docs URL to create one.
|
|
588
|
+
`
|
|
589
|
+
);
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
process.stdout.write(`${pc6.dim("Ready MCPs")}
|
|
593
|
+
`);
|
|
594
|
+
ready.forEach((p, index) => {
|
|
595
|
+
process.stdout.write(
|
|
596
|
+
` ${pc6.cyan(String(index + 1).padStart(2, " "))}. ${pc6.bold(p.name)} ${pc6.dim(p.sourceUrl ?? p.id)}
|
|
597
|
+
`
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
const choice = await readLine(
|
|
601
|
+
`${pc6.bold(">")} choose number, paste URL, or paste project id: `
|
|
602
|
+
);
|
|
603
|
+
if (!choice) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
if (isDocsUrl(choice)) {
|
|
607
|
+
return await convertUrlToProject(choice, { offerInstall: false });
|
|
608
|
+
}
|
|
609
|
+
const chosenIndex = Number(choice);
|
|
610
|
+
if (Number.isInteger(chosenIndex) && chosenIndex > 0) {
|
|
611
|
+
const project = ready.at(chosenIndex - 1);
|
|
612
|
+
if (project) {
|
|
613
|
+
return await apiFetch(`/api/cli/projects/${project.id}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return await apiFetch(`/api/cli/projects/${choice}`);
|
|
617
|
+
}
|
|
618
|
+
async function resolveProject(target) {
|
|
619
|
+
if (target) {
|
|
620
|
+
if (isDocsUrl(target)) {
|
|
621
|
+
return await convertUrlToProject(target, { offerInstall: false });
|
|
622
|
+
}
|
|
623
|
+
return await apiFetch(`/api/cli/projects/${target}`);
|
|
624
|
+
}
|
|
625
|
+
process.stdout.write(`${pc6.bold("doc2mcp chat")}
|
|
626
|
+
`);
|
|
627
|
+
process.stdout.write(
|
|
628
|
+
`${pc6.dim("Paste a docs URL to create a new MCP, paste a project id, or press Enter to choose existing.")}
|
|
629
|
+
|
|
630
|
+
`
|
|
631
|
+
);
|
|
632
|
+
const first = await readLine(`${pc6.bold(">")} docs url or project id: `);
|
|
633
|
+
if (!first) {
|
|
634
|
+
return await pickExistingProject();
|
|
635
|
+
}
|
|
636
|
+
if (isDocsUrl(first)) {
|
|
637
|
+
return await convertUrlToProject(first, { offerInstall: false });
|
|
638
|
+
}
|
|
639
|
+
return await apiFetch(`/api/cli/projects/${first}`);
|
|
640
|
+
}
|
|
641
|
+
async function askDocs(mcp, question) {
|
|
642
|
+
const response = await fetch(mcp.url, {
|
|
643
|
+
method: "POST",
|
|
644
|
+
headers: {
|
|
645
|
+
"Content-Type": "application/json",
|
|
646
|
+
Authorization: `Bearer ${mcp.token}`
|
|
647
|
+
},
|
|
648
|
+
body: JSON.stringify({
|
|
649
|
+
jsonrpc: "2.0",
|
|
650
|
+
id: 1,
|
|
651
|
+
method: "tools/call",
|
|
652
|
+
params: { name: "ask_documentation", arguments: { question } }
|
|
653
|
+
})
|
|
654
|
+
});
|
|
655
|
+
const payload = await response.json();
|
|
656
|
+
if (!response.ok || payload.error) {
|
|
657
|
+
throw new ApiError(
|
|
658
|
+
payload.error?.message ?? `MCP request failed (${response.status})`,
|
|
659
|
+
response.status,
|
|
660
|
+
payload
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
const raw = payload.result?.content?.[0]?.text ?? "";
|
|
664
|
+
try {
|
|
665
|
+
return JSON.parse(raw);
|
|
666
|
+
} catch {
|
|
667
|
+
return { question, answer: raw };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function renderAnswer(answer) {
|
|
671
|
+
process.stdout.write(`
|
|
672
|
+
${pc6.cyan("\u256D\u2500 doc2mcp")}
|
|
673
|
+
`);
|
|
674
|
+
for (const line of answer.answer.trim().split("\n")) {
|
|
675
|
+
process.stdout.write(`${pc6.cyan("\u2502")} ${line}
|
|
676
|
+
`);
|
|
677
|
+
}
|
|
678
|
+
if (answer.sources && answer.sources.length > 0) {
|
|
679
|
+
process.stdout.write(`${pc6.cyan("\u2502")}
|
|
680
|
+
${pc6.cyan("\u2502")} ${pc6.dim("Sources:")}
|
|
681
|
+
`);
|
|
682
|
+
for (const source of answer.sources.slice(0, 6)) {
|
|
683
|
+
process.stdout.write(
|
|
684
|
+
`${pc6.cyan("\u2502")} ${pc6.dim("\u2022")} ${source.title} ${pc6.dim(source.url)}
|
|
685
|
+
`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
process.stdout.write(`${pc6.cyan("\u2570")}
|
|
690
|
+
|
|
691
|
+
`);
|
|
692
|
+
}
|
|
693
|
+
async function answerOnce(mcp, question) {
|
|
694
|
+
const spinner = ora2("Thinking\u2026").start();
|
|
695
|
+
try {
|
|
696
|
+
const answer = await askDocs(mcp, question);
|
|
697
|
+
spinner.stop();
|
|
698
|
+
renderAnswer(answer);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
spinner.fail("Failed to get an answer");
|
|
701
|
+
printError(error);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async function runChat(target, options = {}) {
|
|
705
|
+
try {
|
|
706
|
+
await ensureLoggedIn();
|
|
707
|
+
const detail = await resolveProject(target);
|
|
708
|
+
if (!detail) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (!detail.mcp) {
|
|
712
|
+
process.stderr.write(
|
|
713
|
+
`${pc6.red("That project is not ready yet.")} Check: ${pc6.bold("doc2mcp list")}
|
|
714
|
+
`
|
|
715
|
+
);
|
|
716
|
+
process.exitCode = 1;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const { mcp } = detail;
|
|
720
|
+
if (options.message) {
|
|
721
|
+
await answerOnce(mcp, options.message);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
process.stdout.write(
|
|
725
|
+
`
|
|
726
|
+
${pc6.cyan("\u256D\u2500")} ${pc6.bold(`Chatting with ${detail.project.name}`)}
|
|
727
|
+
`
|
|
728
|
+
);
|
|
729
|
+
process.stdout.write(
|
|
730
|
+
`${pc6.cyan("\u2502")} ${pc6.dim("Ask anything about these docs. Type /exit to leave.")}
|
|
731
|
+
`
|
|
732
|
+
);
|
|
733
|
+
process.stdout.write(`${pc6.cyan("\u2570")}
|
|
734
|
+
|
|
735
|
+
`);
|
|
736
|
+
let active = true;
|
|
737
|
+
while (active) {
|
|
738
|
+
const question = await readLine(`${pc6.bold(">")} `);
|
|
739
|
+
if (question === null) {
|
|
740
|
+
active = false;
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
const trimmed = question.trim();
|
|
744
|
+
if (!trimmed) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
748
|
+
active = false;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
await answerOnce(mcp, trimmed);
|
|
752
|
+
}
|
|
753
|
+
process.stdout.write(`${pc6.dim("Bye \u2014 your docs MCP stays live for your editor.")}
|
|
754
|
+
`);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
printError(error);
|
|
757
|
+
process.exitCode = 1;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
542
761
|
// src/index.ts
|
|
543
762
|
var program = new Command();
|
|
544
|
-
program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.
|
|
763
|
+
program.name("doc2mcp").description("Generate documentation MCP servers from your terminal").version("0.1.18", "-v, --version", "Print the installed CLI version");
|
|
545
764
|
program.command("login").description("Authorize the CLI via browser").action(async () => {
|
|
546
765
|
await runLogin();
|
|
547
766
|
});
|
|
@@ -557,6 +776,9 @@ program.command("list").description("List your MCP projects").action(async () =>
|
|
|
557
776
|
program.command("install <projectId>").description("Install an existing MCP into Cursor, VS Code, Claude, or Windsurf").action(async (projectId) => {
|
|
558
777
|
await runInstallCommand(projectId);
|
|
559
778
|
});
|
|
779
|
+
program.command("chat [target]").description("Chat with docs in the terminal; target can be a project id or docs URL").option("-m, --message <text>", "Ask a single question and exit").action(async (target, options) => {
|
|
780
|
+
await runChat(target, options);
|
|
781
|
+
});
|
|
560
782
|
program.argument("[url]", "Documentation URL to convert").action(async (url) => {
|
|
561
783
|
if (!url) {
|
|
562
784
|
program.help();
|
|
@@ -569,7 +791,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
|
|
|
569
791
|
}
|
|
570
792
|
} catch {
|
|
571
793
|
process.stderr.write(
|
|
572
|
-
`${
|
|
794
|
+
`${pc7.red("Error:")} Invalid URL. Example: doc2mcp https://docs.example.com
|
|
573
795
|
`
|
|
574
796
|
);
|
|
575
797
|
process.exitCode = 1;
|
|
@@ -579,7 +801,7 @@ program.argument("[url]", "Documentation URL to convert").action(async (url) =>
|
|
|
579
801
|
});
|
|
580
802
|
program.parseAsync(process.argv).catch((error) => {
|
|
581
803
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
582
|
-
process.stderr.write(`${
|
|
804
|
+
process.stderr.write(`${pc7.red("Error:")} ${message}
|
|
583
805
|
`);
|
|
584
806
|
process.exit(1);
|
|
585
807
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc2mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
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": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"assets",
|
|
20
20
|
"dist",
|
|
21
|
+
"scripts",
|
|
21
22
|
"README.md"
|
|
22
23
|
],
|
|
23
24
|
"engines": {
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"scripts": {
|
|
27
28
|
"build": "tsup",
|
|
28
29
|
"dev": "tsup --watch",
|
|
30
|
+
"postinstall": "node ./scripts/postinstall.js",
|
|
29
31
|
"prepublishOnly": "pnpm build"
|
|
30
32
|
},
|
|
31
33
|
"keywords": [
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
function isGlobalInstall() {
|
|
7
|
+
return (
|
|
8
|
+
process.env.npm_config_global === "true" ||
|
|
9
|
+
process.env.npm_config_location === "global"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function npmGlobalBin() {
|
|
14
|
+
try {
|
|
15
|
+
const prefix = execFileSync("npm", ["prefix", "-g"], {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
}).trim();
|
|
19
|
+
return path.join(prefix, "bin");
|
|
20
|
+
} catch {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isGlobalInstall()) {
|
|
26
|
+
const binDir = npmGlobalBin();
|
|
27
|
+
const pathEntries = (process.env.PATH || "").split(path.delimiter);
|
|
28
|
+
if (binDir && !pathEntries.includes(binDir)) {
|
|
29
|
+
process.stdout.write(`
|
|
30
|
+
doc2mcp installed, but npm's global bin is not on your PATH.
|
|
31
|
+
|
|
32
|
+
Run this once for zsh:
|
|
33
|
+
echo 'export PATH="${binDir}:$PATH"' >> ~/.zshrc
|
|
34
|
+
source ~/.zshrc
|
|
35
|
+
|
|
36
|
+
Then try:
|
|
37
|
+
doc2mcp login
|
|
38
|
+
|
|
39
|
+
Quick alternative (no PATH setup):
|
|
40
|
+
npx doc2mcp login
|
|
41
|
+
|
|
42
|
+
For pnpm global installs, run:
|
|
43
|
+
pnpm setup
|
|
44
|
+
source ~/.zshrc
|
|
45
|
+
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
}
|