okfy-ai 0.1.2 → 0.1.3
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 +75 -22
- package/assets/logo-dark.png +0 -0
- package/assets/logo-light.png +0 -0
- package/dist/{chunk-C46QXZDU.js → chunk-6AP7LVJG.js} +15 -0
- package/dist/cli.js +63 -5
- package/dist/index.d.ts +37 -1
- package/dist/index.js +1 -1
- package/docs/mcp-clients.md +31 -9
- package/examples/README.md +4 -4
- package/examples/bundles/okfy-docs/guides/import-local-markdown.md +2 -2
- package/examples/bundles/okfy-docs/guides/serve-over-mcp.md +1 -1
- package/examples/local-markdown/guides/import-local-markdown.md +2 -2
- package/examples/local-markdown/guides/serve-over-mcp.md +1 -1
- package/examples/local-markdown/okfy-example.json +1 -1
- package/package.json +5 -3
- package/assets/logo.svg +0 -14
package/README.md
CHANGED
|
@@ -1,27 +1,59 @@
|
|
|
1
1
|
# okfy-ai
|
|
2
2
|
|
|
3
|
-
Turn docs into agent-readable Open Knowledge Format bundles.
|
|
3
|
+
Turn docs into agent-readable Open Knowledge Format bundles, then serve them to Claude, Codex, Cursor, or any MCP client.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Use With Agents
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Create a bundle:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y okfy-ai
|
|
10
|
+
npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
|
|
11
|
+
npx -y okfy-ai validate ./stripe-checkout-okf
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Add it to an MCP client:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"stripe-okf": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "okfy-ai", "serve", "./stripe-checkout-okf", "--mcp"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
11
25
|
```
|
|
12
26
|
|
|
13
|
-
|
|
27
|
+
Ask your agent:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Use the stripe-okf MCP server. Search for Checkout Sessions, read the most relevant concepts, inspect neighbors if needed, and explain the minimum backend flow with source URLs.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Client Setup
|
|
34
|
+
|
|
35
|
+
Claude Code:
|
|
14
36
|
|
|
15
37
|
```bash
|
|
16
|
-
|
|
17
|
-
okfy demo
|
|
38
|
+
claude mcp add --transport stdio stripe-okf -- npx -y okfy-ai serve ./stripe-checkout-okf --mcp
|
|
18
39
|
```
|
|
19
40
|
|
|
20
|
-
|
|
41
|
+
Codex:
|
|
42
|
+
|
|
43
|
+
```toml
|
|
44
|
+
[mcp_servers.stripe_okf]
|
|
45
|
+
command = "npx"
|
|
46
|
+
args = ["-y", "okfy-ai", "serve", "./stripe-checkout-okf", "--mcp"]
|
|
47
|
+
startup_timeout_sec = 20
|
|
48
|
+
tool_timeout_sec = 60
|
|
49
|
+
enabled = true
|
|
50
|
+
```
|
|
21
51
|
|
|
22
|
-
|
|
52
|
+
Claude Desktop, Cursor, and other `mcpServers` clients can use the JSON config above. More setup: https://github.com/0dust/OKFy/blob/main/docs/mcp-clients.md
|
|
23
53
|
|
|
24
|
-
|
|
54
|
+
## Create Bundles
|
|
55
|
+
|
|
56
|
+
Docs website:
|
|
25
57
|
|
|
26
58
|
```bash
|
|
27
59
|
npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
|
|
@@ -29,37 +61,58 @@ npx -y okfy-ai validate ./stripe-checkout-okf
|
|
|
29
61
|
npx -y okfy-ai inspect ./stripe-checkout-okf
|
|
30
62
|
```
|
|
31
63
|
|
|
32
|
-
|
|
64
|
+
Local Markdown:
|
|
33
65
|
|
|
34
66
|
```bash
|
|
35
|
-
npx -y okfy-ai
|
|
67
|
+
npx -y okfy-ai import ./docs --out ./docs-okf --source-name "Project docs" --force
|
|
68
|
+
npx -y okfy-ai validate ./docs-okf
|
|
36
69
|
```
|
|
37
70
|
|
|
38
|
-
##
|
|
71
|
+
## Optional CLI Install
|
|
72
|
+
|
|
73
|
+
You do not need global install for MCP configs. `npx -y okfy-ai ...` is usually better because the MCP client can launch okfy directly.
|
|
74
|
+
|
|
75
|
+
Install only if you want shorter local commands:
|
|
39
76
|
|
|
40
77
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
npx -y okfy-ai serve ./docs-okf --mcp
|
|
78
|
+
npm install -g okfy-ai
|
|
79
|
+
okfy demo
|
|
44
80
|
```
|
|
45
81
|
|
|
46
|
-
|
|
82
|
+
`okfy-ai` is the npm package name. `okfy` is the installed CLI command.
|
|
83
|
+
|
|
84
|
+
Requires Node.js 20+.
|
|
85
|
+
|
|
86
|
+
After installing, this MCP config is equivalent:
|
|
47
87
|
|
|
48
88
|
```json
|
|
49
89
|
{
|
|
50
90
|
"mcpServers": {
|
|
51
91
|
"docs-okf": {
|
|
52
|
-
"command": "
|
|
53
|
-
"args": ["
|
|
92
|
+
"command": "okfy",
|
|
93
|
+
"args": ["serve", "./docs-okf", "--mcp"]
|
|
54
94
|
}
|
|
55
95
|
}
|
|
56
96
|
}
|
|
57
97
|
```
|
|
58
98
|
|
|
59
|
-
|
|
99
|
+
## Demo
|
|
60
100
|
|
|
61
|
-
```
|
|
62
|
-
|
|
101
|
+
```bash
|
|
102
|
+
npx -y okfy-ai demo
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## No-Install MCP Config
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"docs-okf": {
|
|
111
|
+
"command": "npx",
|
|
112
|
+
"args": ["-y", "okfy-ai", "serve", "./docs-okf", "--mcp"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
63
116
|
```
|
|
64
117
|
|
|
65
118
|
## CLI Commands
|
|
Binary file
|
|
Binary file
|
|
@@ -455,6 +455,7 @@ async function crawlWebsite(options) {
|
|
|
455
455
|
let skipped = 0;
|
|
456
456
|
let failed = 0;
|
|
457
457
|
const limit = pLimit(options.concurrency ?? 4);
|
|
458
|
+
options.onProgress?.({ type: "start", seed, maxPages, maxDepth });
|
|
458
459
|
while (queue.length > 0 && visited.size < maxPages) {
|
|
459
460
|
const batch = queue.splice(0, Math.min(queue.length, maxPages - visited.size));
|
|
460
461
|
const results = await Promise.all(
|
|
@@ -464,9 +465,11 @@ async function crawlWebsite(options) {
|
|
|
464
465
|
visited.add(item.url);
|
|
465
466
|
if (!shouldVisit(item.url, seed, options, robots)) {
|
|
466
467
|
skipped += 1;
|
|
468
|
+
options.onProgress?.({ type: "skipped", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
|
|
467
469
|
return;
|
|
468
470
|
}
|
|
469
471
|
planned.push(item.url);
|
|
472
|
+
options.onProgress?.({ type: "fetch", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
|
|
470
473
|
try {
|
|
471
474
|
const fetched = await fetchText(item.url);
|
|
472
475
|
const contentType = contentTypeFromHeader(fetched.contentType);
|
|
@@ -483,6 +486,7 @@ async function crawlWebsite(options) {
|
|
|
483
486
|
};
|
|
484
487
|
const doc = normalizeDocument(raw);
|
|
485
488
|
if (!options.dryRun) documents.push(doc);
|
|
489
|
+
let discovered = 0;
|
|
486
490
|
if (item.depth < maxDepth) {
|
|
487
491
|
const links = options.dryRun && contentType === "html" ? extractRawHtmlLinks(fetched.text) : doc.links;
|
|
488
492
|
for (const link of links) {
|
|
@@ -491,14 +495,24 @@ async function crawlWebsite(options) {
|
|
|
491
495
|
if (!queued.has(next) && shouldVisit(next, seed, options, robots) && queued.size < maxPages * 4) {
|
|
492
496
|
queued.add(next);
|
|
493
497
|
queue.push({ url: next, depth: item.depth + 1 });
|
|
498
|
+
discovered += 1;
|
|
494
499
|
}
|
|
495
500
|
} catch {
|
|
496
501
|
skipped += 1;
|
|
497
502
|
}
|
|
498
503
|
}
|
|
499
504
|
}
|
|
505
|
+
options.onProgress?.({
|
|
506
|
+
type: "fetched",
|
|
507
|
+
url: item.url,
|
|
508
|
+
fetched: options.dryRun ? planned.length : documents.length,
|
|
509
|
+
queued: queue.length,
|
|
510
|
+
discovered,
|
|
511
|
+
maxPages
|
|
512
|
+
});
|
|
500
513
|
} catch {
|
|
501
514
|
failed += 1;
|
|
515
|
+
options.onProgress?.({ type: "failed", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
|
|
502
516
|
}
|
|
503
517
|
})
|
|
504
518
|
)
|
|
@@ -509,6 +523,7 @@ async function crawlWebsite(options) {
|
|
|
509
523
|
return { pagesFetched: planned.length, skipped, failed, written: [], documents: [], dryRunPages: planned.slice(0, maxPages) };
|
|
510
524
|
}
|
|
511
525
|
if (documents.length === 0) throw new Error("Crawl generated zero concepts.");
|
|
526
|
+
options.onProgress?.({ type: "writing", concepts: documents.length, outDir: options.outDir });
|
|
512
527
|
const written = await writeOkfBundle(documents, {
|
|
513
528
|
outDir: options.outDir,
|
|
514
529
|
title: options.title,
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
inspectBundle,
|
|
6
6
|
serveMcpStdio,
|
|
7
7
|
validateBundle
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6AP7LVJG.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import fs from "fs";
|
|
@@ -15,6 +15,16 @@ import { Command } from "commander";
|
|
|
15
15
|
import pc from "picocolors";
|
|
16
16
|
var program = new Command();
|
|
17
17
|
var packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
18
|
+
var isTty = Boolean(process.stderr.isTTY);
|
|
19
|
+
function readPackageVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf8");
|
|
22
|
+
const parsed = JSON.parse(raw);
|
|
23
|
+
return parsed.version ?? "0.0.0";
|
|
24
|
+
} catch {
|
|
25
|
+
return "0.0.0";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
18
28
|
function collect(value, previous) {
|
|
19
29
|
previous.push(value);
|
|
20
30
|
return previous;
|
|
@@ -46,14 +56,50 @@ function printStats(stats) {
|
|
|
46
56
|
for (const [domain, count] of Object.entries(stats.sourceDomains)) console.log(` ${domain}: ${count}`);
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
|
-
|
|
59
|
+
function printStatus(message) {
|
|
60
|
+
process.stderr.write(`${message}
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
function printCrawlProgress(event) {
|
|
64
|
+
const clear = isTty ? "\r\x1B[K" : "";
|
|
65
|
+
switch (event.type) {
|
|
66
|
+
case "start":
|
|
67
|
+
process.stderr.write(`okfy crawl: starting ${event.seed} (max ${event.maxPages} pages, depth ${event.maxDepth})
|
|
68
|
+
`);
|
|
69
|
+
break;
|
|
70
|
+
case "fetch":
|
|
71
|
+
process.stderr.write(`${clear}okfy crawl: fetching ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}`);
|
|
72
|
+
if (!isTty) process.stderr.write("\n");
|
|
73
|
+
break;
|
|
74
|
+
case "fetched":
|
|
75
|
+
process.stderr.write(
|
|
76
|
+
`${clear}okfy crawl: fetched ${event.fetched}/${event.maxPages}, queued ${event.queued}, discovered +${event.discovered}: ${event.url}
|
|
77
|
+
`
|
|
78
|
+
);
|
|
79
|
+
break;
|
|
80
|
+
case "skipped":
|
|
81
|
+
process.stderr.write(`${clear}okfy crawl: skipped ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}
|
|
82
|
+
`);
|
|
83
|
+
break;
|
|
84
|
+
case "failed":
|
|
85
|
+
process.stderr.write(`${clear}okfy crawl: failed ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}
|
|
86
|
+
`);
|
|
87
|
+
break;
|
|
88
|
+
case "writing":
|
|
89
|
+
process.stderr.write(`${clear}okfy crawl: writing ${event.concepts} concepts to ${event.outDir}
|
|
90
|
+
`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
program.name("okfy").description("Turn docs into agent memory with Open Knowledge Format and MCP.").version(readPackageVersion());
|
|
50
95
|
program.command("crawl").argument("<url>", "Docs URL to crawl").requiredOption("--out <dir>", "Output OKF bundle directory").option("--max-pages <n>", "Maximum pages", (value) => Number(value), 100).option("--max-depth <n>", "Maximum crawl depth", (value) => Number(value), 4).option("--include <pattern>", "Include glob or regex", collect, []).option("--exclude <pattern>", "Exclude glob or regex", collect, []).option("--same-origin", "Stay on same origin", true).option("--no-same-origin", "Allow cross-origin links").option("--respect-robots", "Respect robots.txt", true).option("--no-respect-robots", "Ignore robots.txt").option("--concurrency <n>", "Fetch concurrency", (value) => Number(value), 4).option("--title <name>", "Bundle title").option("--force", "Overwrite output directory", false).option("--dry-run", "List pages that would be crawled", false).option("--allow-private-network", "Allow localhost/private IP crawl targets", false).option("--stable-timestamps", "Use a deterministic timestamp in generated frontmatter", false).action(async (url, options) => {
|
|
51
96
|
try {
|
|
52
97
|
const result = await crawlWebsite({
|
|
53
98
|
seedUrl: url,
|
|
54
99
|
outDir: options.out,
|
|
55
100
|
...options,
|
|
56
|
-
timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0
|
|
101
|
+
timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0,
|
|
102
|
+
onProgress: printCrawlProgress
|
|
57
103
|
});
|
|
58
104
|
if (options.dryRun) {
|
|
59
105
|
console.log("okfy crawl dry run");
|
|
@@ -75,6 +121,8 @@ program.command("crawl").argument("<url>", "Docs URL to crawl").requiredOption("
|
|
|
75
121
|
});
|
|
76
122
|
program.command("import").argument("<path>", "Local docs folder or file").requiredOption("--out <dir>", "Output OKF bundle directory").option("--source-name <name>", "Source name").option("--include <glob>", "Include glob", collect, []).option("--exclude <glob>", "Exclude glob", collect, []).option("--force", "Overwrite output directory", false).option("--stable-timestamps", "Use a deterministic timestamp in generated frontmatter", false).action(async (input, options) => {
|
|
77
123
|
try {
|
|
124
|
+
printStatus(`okfy import: reading ${input}`);
|
|
125
|
+
printStatus(`okfy import: writing bundle to ${options.out}`);
|
|
78
126
|
const result = await importLocal({
|
|
79
127
|
inputPath: input,
|
|
80
128
|
outDir: options.out,
|
|
@@ -85,19 +133,25 @@ program.command("import").argument("<path>", "Local docs folder or file").requir
|
|
|
85
133
|
console.log(`Source: ${input}`);
|
|
86
134
|
console.log(`Concepts: ${result.written.length} written`);
|
|
87
135
|
console.log(`Output: ${options.out}`);
|
|
136
|
+
printStatus(`okfy import: done, wrote ${result.written.length} concepts`);
|
|
88
137
|
} catch (error) {
|
|
89
138
|
console.error(pc.red(error?.message ?? "Import failed."));
|
|
90
139
|
process.exitCode = 1;
|
|
91
140
|
}
|
|
92
141
|
});
|
|
93
142
|
program.command("validate").argument("<bundle>", "OKF bundle directory").option("--json", "Print JSON report", false).action(async (bundle, options) => {
|
|
143
|
+
printStatus(`okfy validate: checking ${bundle}`);
|
|
94
144
|
const report = await validateBundle(bundle);
|
|
95
145
|
printValidation(report, options.json);
|
|
146
|
+
printStatus(`okfy validate: ${report.valid ? "valid" : "invalid"}, ${report.conceptCount} concepts`);
|
|
96
147
|
if (!report.valid) process.exitCode = 1;
|
|
97
148
|
});
|
|
98
149
|
program.command("inspect").argument("<bundle>", "OKF bundle directory").action(async (bundle) => {
|
|
99
150
|
try {
|
|
100
|
-
|
|
151
|
+
printStatus(`okfy inspect: reading ${bundle}`);
|
|
152
|
+
const stats = await inspectBundle(bundle);
|
|
153
|
+
printStats(stats);
|
|
154
|
+
printStatus(`okfy inspect: done, ${stats.conceptCount} concepts, ${stats.linkCount} links`);
|
|
101
155
|
} catch (error) {
|
|
102
156
|
console.error(pc.red(error?.message ?? "Inspect failed."));
|
|
103
157
|
process.exitCode = 1;
|
|
@@ -114,7 +168,11 @@ program.command("serve").argument("<bundle>", "OKF bundle directory").option("--
|
|
|
114
168
|
process.exitCode = 1;
|
|
115
169
|
return;
|
|
116
170
|
}
|
|
171
|
+
printStatus(`okfy serve: loading ${bundle}`);
|
|
172
|
+
printStatus(`okfy serve: starting MCP stdio server "${options.name}"`);
|
|
117
173
|
await serveMcpStdio({ bundleDir: bundle, name: options.name, maxResultChars: options.maxResultChars });
|
|
174
|
+
printStatus("okfy serve: ready on stdio (stdout is reserved for MCP JSON-RPC)");
|
|
175
|
+
printStatus("okfy serve: tools bundle_summary, search_concepts, read_concept, get_neighbors, list_types, list_tags");
|
|
118
176
|
});
|
|
119
177
|
function resolveDemoBundle() {
|
|
120
178
|
const relativeBundle = "examples/bundles/okfy-docs";
|
|
@@ -137,7 +195,7 @@ program.command("demo").description("Run offline demo against committed example
|
|
|
137
195
|
console.log("MCP config:");
|
|
138
196
|
console.log(
|
|
139
197
|
JSON.stringify(
|
|
140
|
-
{ mcpServers: { "okfy-docs": { command: "npx", args: ["okfy-ai", "serve", bundle, "--mcp"] } } },
|
|
198
|
+
{ mcpServers: { "okfy-docs": { command: "npx", args: ["-y", "okfy-ai", "serve", bundle, "--mcp"] } } },
|
|
141
199
|
null,
|
|
142
200
|
2
|
|
143
201
|
)
|
package/dist/index.d.ts
CHANGED
|
@@ -86,6 +86,42 @@ type CrawlOptions = {
|
|
|
86
86
|
dryRun?: boolean;
|
|
87
87
|
allowPrivateNetwork?: boolean;
|
|
88
88
|
timestamp?: string;
|
|
89
|
+
onProgress?: (event: CrawlProgressEvent) => void;
|
|
90
|
+
};
|
|
91
|
+
type CrawlProgressEvent = {
|
|
92
|
+
type: "start";
|
|
93
|
+
seed: string;
|
|
94
|
+
maxPages: number;
|
|
95
|
+
maxDepth: number;
|
|
96
|
+
} | {
|
|
97
|
+
type: "fetch";
|
|
98
|
+
url: string;
|
|
99
|
+
fetched: number;
|
|
100
|
+
queued: number;
|
|
101
|
+
maxPages: number;
|
|
102
|
+
} | {
|
|
103
|
+
type: "fetched";
|
|
104
|
+
url: string;
|
|
105
|
+
fetched: number;
|
|
106
|
+
queued: number;
|
|
107
|
+
discovered: number;
|
|
108
|
+
maxPages: number;
|
|
109
|
+
} | {
|
|
110
|
+
type: "skipped";
|
|
111
|
+
url: string;
|
|
112
|
+
fetched: number;
|
|
113
|
+
queued: number;
|
|
114
|
+
maxPages: number;
|
|
115
|
+
} | {
|
|
116
|
+
type: "failed";
|
|
117
|
+
url: string;
|
|
118
|
+
fetched: number;
|
|
119
|
+
queued: number;
|
|
120
|
+
maxPages: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: "writing";
|
|
123
|
+
concepts: number;
|
|
124
|
+
outDir: string;
|
|
89
125
|
};
|
|
90
126
|
type CrawlResult = {
|
|
91
127
|
pagesFetched: number;
|
|
@@ -176,4 +212,4 @@ type WriteBundleOptions = {
|
|
|
176
212
|
};
|
|
177
213
|
declare function writeOkfBundle(docs: NormalizedDocument[], options: WriteBundleOptions): Promise<string[]>;
|
|
178
214
|
|
|
179
|
-
export { BundleSearch, type BundleStats, type Concept, type ContentType, type CrawlOptions, type CrawlResult, type ImportOptions, type KnowledgeGraph, type NormalizedDocument, type RawDocument, type SearchResult, type ServeOptions, type ValidationIssue, type ValidationReport, type WriteBundleOptions, buildGraph, crawlWebsite, createMcpServer, descriptionFromMarkdown, extractHeadings, extractInternalLinks, extractMarkdownLinks, importLocal, inferTags, inferType, inspectBundle, normalizeDocument, readBundle, readConceptFile, serveMcpStdio, validateBundle, writeOkfBundle };
|
|
215
|
+
export { BundleSearch, type BundleStats, type Concept, type ContentType, type CrawlOptions, type CrawlProgressEvent, type CrawlResult, type ImportOptions, type KnowledgeGraph, type NormalizedDocument, type RawDocument, type SearchResult, type ServeOptions, type ValidationIssue, type ValidationReport, type WriteBundleOptions, buildGraph, crawlWebsite, createMcpServer, descriptionFromMarkdown, extractHeadings, extractInternalLinks, extractMarkdownLinks, importLocal, inferTags, inferType, inspectBundle, normalizeDocument, readBundle, readConceptFile, serveMcpStdio, validateBundle, writeOkfBundle };
|
package/dist/index.js
CHANGED
package/docs/mcp-clients.md
CHANGED
|
@@ -3,19 +3,23 @@
|
|
|
3
3
|
okfy exposes OKF bundles through a local stdio MCP server:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
6
|
+
okfy serve ./tmp/okfy-docs --mcp
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Shell examples assume `npm install -g okfy-ai`, then the `okfy` CLI command.
|
|
10
|
+
|
|
11
|
+
MCP config examples use `npx -y okfy-ai` by default. That is normal for stdio MCP servers because the MCP client can launch the npm package without requiring a global install.
|
|
12
|
+
|
|
13
|
+
Use stdio for local bundles. MCP stdio means the client launches a local command as a subprocess, sends JSON-RPC messages on stdin, and reads JSON-RPC responses on stdout. okfy logs should go to stderr.
|
|
10
14
|
|
|
11
15
|
## Prepare a Bundle
|
|
12
16
|
|
|
13
17
|
Offline fixture:
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
|
|
21
|
+
okfy validate ./tmp/okfy-docs
|
|
22
|
+
okfy inspect ./tmp/okfy-docs
|
|
19
23
|
```
|
|
20
24
|
|
|
21
25
|
Expected output:
|
|
@@ -29,15 +33,22 @@ Broken links: 0
|
|
|
29
33
|
Docs-site crawl:
|
|
30
34
|
|
|
31
35
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
okfy crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
|
|
37
|
+
okfy validate ./stripe-checkout-okf
|
|
38
|
+
okfy serve ./stripe-checkout-okf --mcp
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
## Claude Code
|
|
38
42
|
|
|
39
43
|
Add okfy as a local stdio server:
|
|
40
44
|
|
|
45
|
+
```bash
|
|
46
|
+
claude mcp add --transport stdio okfy-docs -- okfy serve ./tmp/okfy-docs --mcp
|
|
47
|
+
claude mcp list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
No-install equivalent:
|
|
51
|
+
|
|
41
52
|
```bash
|
|
42
53
|
claude mcp add --transport stdio okfy-docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
|
|
43
54
|
claude mcp list
|
|
@@ -85,9 +96,10 @@ Troubleshooting:
|
|
|
85
96
|
|
|
86
97
|
- `spawn npx ENOENT`: install Node.js >=20 and ensure `npx` is on `PATH`.
|
|
87
98
|
- Server pending: run `/mcp`; approve project-scoped `.mcp.json` if prompted.
|
|
88
|
-
- Empty tools: run `
|
|
99
|
+
- Empty tools: run `okfy validate ./tmp/okfy-docs`; invalid bundles should fail before serving.
|
|
89
100
|
- Output too large: lower `--max-result-chars`, or ask the agent to search before reading concepts.
|
|
90
101
|
- Wrong bundle path: use an absolute path in config if client starts from another working directory.
|
|
102
|
+
- Already installed globally: use `"command": "okfy"` and args `["serve", "./tmp/okfy-docs", "--mcp"]`.
|
|
91
103
|
|
|
92
104
|
## Claude Desktop
|
|
93
105
|
|
|
@@ -133,6 +145,7 @@ Troubleshooting:
|
|
|
133
145
|
- Server exits immediately: run exact command in terminal and fix bundle validation errors.
|
|
134
146
|
- No okfy tools visible: restart Claude Desktop after config changes.
|
|
135
147
|
- Relative path fails: use absolute bundle path.
|
|
148
|
+
- Already installed globally: use `"command": "okfy"` and args `["serve", "/absolute/path/to/okfy/tmp/okfy-docs", "--mcp"]`.
|
|
136
149
|
|
|
137
150
|
## Codex
|
|
138
151
|
|
|
@@ -169,6 +182,13 @@ npx -y okfy-ai serve ./tmp/okfy-docs --mcp
|
|
|
169
182
|
|
|
170
183
|
CLI alternative:
|
|
171
184
|
|
|
185
|
+
```bash
|
|
186
|
+
codex mcp add okfy_docs -- okfy serve ./tmp/okfy-docs --mcp
|
|
187
|
+
codex mcp --help
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
No-install CLI alternative:
|
|
191
|
+
|
|
172
192
|
```bash
|
|
173
193
|
codex mcp add okfy_docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
|
|
174
194
|
codex mcp --help
|
|
@@ -204,6 +224,7 @@ Troubleshooting:
|
|
|
204
224
|
- Tool timeout: increase `tool_timeout_sec` for large bundles.
|
|
205
225
|
- Relative path wrong: set `cwd` or use absolute bundle path.
|
|
206
226
|
- Need current server list: run `/mcp` in TUI.
|
|
227
|
+
- Already installed globally: use `command = "okfy"` and `args = ["serve", "./tmp/okfy-docs", "--mcp"]`.
|
|
207
228
|
|
|
208
229
|
## Generic MCP stdio
|
|
209
230
|
|
|
@@ -255,6 +276,7 @@ Troubleshooting:
|
|
|
255
276
|
- `tools/list` empty: confirm `okfy serve` was started with `--mcp`.
|
|
256
277
|
- Search returns weak matches: run `okfy inspect` and verify titles, descriptions, and tags were generated.
|
|
257
278
|
- Agent reads too much: ask it to call `search_concepts` first and `read_concept` with `max_chars`.
|
|
279
|
+
- Already installed globally: use `"command": "okfy"` and args `["serve", "./tmp/okfy-docs", "--mcp"]`.
|
|
258
280
|
|
|
259
281
|
## Available okfy MCP Tools
|
|
260
282
|
|
package/examples/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Purpose: deterministic offline input for `okfy import`.
|
|
|
63
63
|
Source command:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
|
|
66
|
+
okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Expected concept count:
|
|
@@ -81,14 +81,14 @@ valid
|
|
|
81
81
|
Validate:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
okfy validate ./tmp/okfy-docs
|
|
85
|
+
okfy inspect ./tmp/okfy-docs
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
Serve through MCP:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
|
|
91
|
+
okfy serve ./tmp/okfy-docs --mcp
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
Suggested agent questions:
|
|
@@ -14,8 +14,8 @@ timestamp: "2026-06-14T00:00:00.000Z"
|
|
|
14
14
|
Use `okfy import` when docs already live in a local project checkout, wiki export, Obsidian vault, or static-site source folder.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
|
|
18
|
+
okfy validate ./tmp/okfy-docs
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Expected result:
|
|
@@ -15,7 +15,7 @@ timestamp: "2026-06-14T00:00:00.000Z"
|
|
|
15
15
|
After generating an OKF bundle, serve it over stdio MCP:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
18
|
+
okfy serve ./tmp/okfy-docs --mcp
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Agents should not read the whole bundle first. The efficient flow is:
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Use `okfy import` when docs already live in a local project checkout, wiki export, Obsidian vault, or static-site source folder.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
|
|
7
|
+
okfy validate ./tmp/okfy-docs
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
Expected result:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sourceCommand": "
|
|
2
|
+
"sourceCommand": "okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps",
|
|
3
3
|
"expectedConceptCount": 9,
|
|
4
4
|
"expectedValidationStatus": "valid",
|
|
5
5
|
"suggestedAgentQuestions": [
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okfy-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Convert docs into Open Knowledge Format bundles and serve them to MCP agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"okfy": "dist/cli.js"
|
|
7
|
+
"okfy": "dist/cli.js",
|
|
8
|
+
"okfy-ai": "dist/cli.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
11
12
|
"README.md",
|
|
12
13
|
"LICENSE",
|
|
13
|
-
"assets/logo.
|
|
14
|
+
"assets/logo-dark.png",
|
|
15
|
+
"assets/logo-light.png",
|
|
14
16
|
"assets/demo.gif",
|
|
15
17
|
"docs/mcp-clients.md",
|
|
16
18
|
"examples"
|
package/assets/logo.svg
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="220" viewBox="0 0 720 220" role="img" aria-labelledby="title desc">
|
|
2
|
-
<title id="title">okfy</title>
|
|
3
|
-
<desc id="desc">okfy wordmark with linked knowledge nodes</desc>
|
|
4
|
-
<rect width="720" height="220" rx="28" fill="#f7f5ef"/>
|
|
5
|
-
<g transform="translate(54 44)">
|
|
6
|
-
<path d="M34 28h74c22 0 40 18 40 40v20c0 22-18 40-40 40H34C12 128-6 110-6 88V68c0-22 18-40 40-40Z" fill="#20342d"/>
|
|
7
|
-
<circle cx="36" cy="78" r="18" fill="#d6f36a"/>
|
|
8
|
-
<circle cx="106" cy="58" r="12" fill="#f7f5ef"/>
|
|
9
|
-
<circle cx="108" cy="100" r="12" fill="#f7f5ef"/>
|
|
10
|
-
<path d="M52 73 94 61M53 84l43 13" stroke="#d6f36a" stroke-width="9" stroke-linecap="round"/>
|
|
11
|
-
<path d="M190 124V31h32v36h10l26-36h37l-35 46 38 47h-39l-28-36h-9v36h-32Zm123 0V31h77v27h-45v15h39v26h-39v25h-32Zm95 0 34-52-32-41h38l15 21 15-21h36l-33 43 38 50h-39l-19-26-18 26h-35Zm127 0V92l-36-61h37l16 32 17-32h35l-37 61v32h-32Z" fill="#20342d"/>
|
|
12
|
-
</g>
|
|
13
|
-
<text x="360" y="184" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="20" letter-spacing="1.5" fill="#5f5a4f">OPEN KNOWLEDGE FORMAT FOR AGENTS</text>
|
|
14
|
-
</svg>
|