okfy-ai 0.1.2

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 (34) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/assets/demo.gif +0 -0
  4. package/assets/logo.svg +14 -0
  5. package/dist/chunk-C46QXZDU.js +1013 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +151 -0
  8. package/dist/index.d.ts +179 -0
  9. package/dist/index.js +40 -0
  10. package/docs/mcp-clients.md +278 -0
  11. package/examples/README.md +98 -0
  12. package/examples/bundles/okfy-docs/concepts/index.md +14 -0
  13. package/examples/bundles/okfy-docs/concepts/okf-bundle.md +33 -0
  14. package/examples/bundles/okfy-docs/concepts/progressive-disclosure.md +26 -0
  15. package/examples/bundles/okfy-docs/guides/import-local-markdown.md +31 -0
  16. package/examples/bundles/okfy-docs/guides/index.md +14 -0
  17. package/examples/bundles/okfy-docs/guides/serve-over-mcp.md +29 -0
  18. package/examples/bundles/okfy-docs/index.md +22 -0
  19. package/examples/bundles/okfy-docs/okfy-example.json +10 -0
  20. package/examples/bundles/okfy-docs/reference/index.md +13 -0
  21. package/examples/bundles/okfy-docs/reference/mcp-tools.md +36 -0
  22. package/examples/bundles/stripe-checkout-small/index.md +21 -0
  23. package/examples/bundles/stripe-checkout-small/okfy-example.json +11 -0
  24. package/examples/bundles/stripe-checkout-small/quickstart.md +26 -0
  25. package/examples/bundles/stripe-checkout-small/sessions.md +20 -0
  26. package/examples/bundles/stripe-checkout-small/webhooks.md +19 -0
  27. package/examples/local-markdown/concepts/okf-bundle.md +19 -0
  28. package/examples/local-markdown/concepts/progressive-disclosure.md +15 -0
  29. package/examples/local-markdown/guides/import-local-markdown.md +20 -0
  30. package/examples/local-markdown/guides/serve-over-mcp.md +17 -0
  31. package/examples/local-markdown/index.md +11 -0
  32. package/examples/local-markdown/okfy-example.json +10 -0
  33. package/examples/local-markdown/reference/mcp-tools.md +25 -0
  34. package/package.json +71 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ crawlWebsite,
4
+ importLocal,
5
+ inspectBundle,
6
+ serveMcpStdio,
7
+ validateBundle
8
+ } from "./chunk-C46QXZDU.js";
9
+
10
+ // src/cli.ts
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ import { Command } from "commander";
15
+ import pc from "picocolors";
16
+ var program = new Command();
17
+ var packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
18
+ function collect(value, previous) {
19
+ previous.push(value);
20
+ return previous;
21
+ }
22
+ function printValidation(report, json) {
23
+ if (json) {
24
+ console.log(JSON.stringify(report, null, 2));
25
+ return;
26
+ }
27
+ console.log(report.valid ? pc.green("OKF bundle valid") : pc.red("OKF bundle invalid"));
28
+ console.log(`Concepts: ${report.conceptCount}`);
29
+ for (const item of report.issues) {
30
+ const color = item.severity === "error" ? pc.red : pc.yellow;
31
+ console.log(`${color(item.severity.toUpperCase())} ${item.code}${item.path ? ` ${item.path}` : ""}: ${item.message}`);
32
+ }
33
+ }
34
+ function printStats(stats) {
35
+ console.log(`Title: ${stats.title}`);
36
+ console.log(`Concepts: ${stats.conceptCount}`);
37
+ console.log(`Links: ${stats.linkCount}`);
38
+ console.log(`Broken links: ${stats.brokenLinks}`);
39
+ console.log(`Orphans: ${stats.orphanConcepts.length}`);
40
+ console.log("Types:");
41
+ for (const [type, count] of Object.entries(stats.typeDistribution)) console.log(` ${type}: ${count}`);
42
+ console.log("Top linked concepts:");
43
+ for (const item of stats.topLinkedConcepts.slice(0, 5)) console.log(` ${item.id}: ${item.count}`);
44
+ if (Object.keys(stats.sourceDomains).length) {
45
+ console.log("Source domains:");
46
+ for (const [domain, count] of Object.entries(stats.sourceDomains)) console.log(` ${domain}: ${count}`);
47
+ }
48
+ }
49
+ program.name("okfy").description("Turn docs into agent memory with Open Knowledge Format and MCP.").version("0.1.0");
50
+ 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
+ try {
52
+ const result = await crawlWebsite({
53
+ seedUrl: url,
54
+ outDir: options.out,
55
+ ...options,
56
+ timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0
57
+ });
58
+ if (options.dryRun) {
59
+ console.log("okfy crawl dry run");
60
+ for (const page of result.dryRunPages ?? []) console.log(page);
61
+ return;
62
+ }
63
+ console.log("okfy crawl");
64
+ console.log(`Seed: ${url}`);
65
+ console.log(`Pages: ${result.pagesFetched} fetched, ${result.skipped} skipped, ${result.failed} failed`);
66
+ console.log(`Concepts: ${result.written.length} written`);
67
+ console.log(`Output: ${options.out}`);
68
+ console.log("\nNext:");
69
+ console.log(` okfy validate ${options.out}`);
70
+ console.log(` okfy serve ${options.out} --mcp`);
71
+ } catch (error) {
72
+ console.error(pc.red(error?.message ?? "Crawl failed."));
73
+ process.exitCode = 1;
74
+ }
75
+ });
76
+ 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
+ try {
78
+ const result = await importLocal({
79
+ inputPath: input,
80
+ outDir: options.out,
81
+ ...options,
82
+ timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0
83
+ });
84
+ console.log("okfy import");
85
+ console.log(`Source: ${input}`);
86
+ console.log(`Concepts: ${result.written.length} written`);
87
+ console.log(`Output: ${options.out}`);
88
+ } catch (error) {
89
+ console.error(pc.red(error?.message ?? "Import failed."));
90
+ process.exitCode = 1;
91
+ }
92
+ });
93
+ program.command("validate").argument("<bundle>", "OKF bundle directory").option("--json", "Print JSON report", false).action(async (bundle, options) => {
94
+ const report = await validateBundle(bundle);
95
+ printValidation(report, options.json);
96
+ if (!report.valid) process.exitCode = 1;
97
+ });
98
+ program.command("inspect").argument("<bundle>", "OKF bundle directory").action(async (bundle) => {
99
+ try {
100
+ printStats(await inspectBundle(bundle));
101
+ } catch (error) {
102
+ console.error(pc.red(error?.message ?? "Inspect failed."));
103
+ process.exitCode = 1;
104
+ }
105
+ });
106
+ program.command("serve").argument("<bundle>", "OKF bundle directory").option("--mcp", "Start MCP server", false).option("--transport <transport>", "Transport: stdio", "stdio").option("--name <server-name>", "MCP server name", "okfy").option("--max-result-chars <n>", "Maximum characters per tool result", (value) => Number(value), 12e3).action(async (bundle, options) => {
107
+ if (!options.mcp) {
108
+ console.error(pc.red("Only --mcp mode is supported in v0.1."));
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+ if (options.transport !== "stdio") {
113
+ console.error(pc.red("Only stdio transport is supported in v0.1."));
114
+ process.exitCode = 1;
115
+ return;
116
+ }
117
+ await serveMcpStdio({ bundleDir: bundle, name: options.name, maxResultChars: options.maxResultChars });
118
+ });
119
+ function resolveDemoBundle() {
120
+ const relativeBundle = "examples/bundles/okfy-docs";
121
+ if (fs.existsSync(relativeBundle)) return relativeBundle;
122
+ return path.join(packageRoot, relativeBundle);
123
+ }
124
+ program.command("demo").description("Run offline demo against committed example bundle").action(async () => {
125
+ const bundle = resolveDemoBundle();
126
+ console.log("okfy demo");
127
+ console.log(`Offline bundle: ${bundle}`);
128
+ const report = await validateBundle(bundle);
129
+ printValidation(report, false);
130
+ if (!report.valid) {
131
+ process.exitCode = 1;
132
+ return;
133
+ }
134
+ console.log("");
135
+ printStats(await inspectBundle(bundle));
136
+ console.log("");
137
+ console.log("MCP config:");
138
+ console.log(
139
+ JSON.stringify(
140
+ { mcpServers: { "okfy-docs": { command: "npx", args: ["okfy-ai", "serve", bundle, "--mcp"] } } },
141
+ null,
142
+ 2
143
+ )
144
+ );
145
+ console.log("");
146
+ console.log("Ask an agent:");
147
+ console.log("1. Search okfy docs for crawler security defaults, then cite source concepts.");
148
+ console.log("2. Read the MCP setup concept and explain the stdio config.");
149
+ console.log("3. Find importer concepts and list supported input formats.");
150
+ });
151
+ program.parseAsync(process.argv);
@@ -0,0 +1,179 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+
3
+ type ContentType = "html" | "markdown" | "mdx" | "text";
4
+ type RawDocument = {
5
+ sourceId: string;
6
+ url?: string;
7
+ filePath?: string;
8
+ contentType: ContentType;
9
+ raw: string;
10
+ discoveredAt: string;
11
+ };
12
+ type NormalizedDocument = {
13
+ sourceId: string;
14
+ title: string;
15
+ markdown: string;
16
+ resource?: string;
17
+ sourcePath?: string;
18
+ outputPath?: string;
19
+ headings: Array<{
20
+ depth: number;
21
+ text: string;
22
+ slug: string;
23
+ }>;
24
+ links: Array<{
25
+ href: string;
26
+ text: string;
27
+ }>;
28
+ tags: string[];
29
+ type: string;
30
+ };
31
+ type Concept = {
32
+ id: string;
33
+ path: string;
34
+ frontmatter: Record<string, unknown>;
35
+ type: string;
36
+ title?: string;
37
+ description?: string;
38
+ resource?: string;
39
+ tags: string[];
40
+ body: string;
41
+ };
42
+ type KnowledgeGraph = {
43
+ concepts: Map<string, Concept>;
44
+ outbound: Map<string, string[]>;
45
+ backlinks: Map<string, string[]>;
46
+ };
47
+ type ValidationIssue = {
48
+ severity: "error" | "warning";
49
+ code: string;
50
+ message: string;
51
+ path?: string;
52
+ };
53
+ type ValidationReport = {
54
+ valid: boolean;
55
+ issues: ValidationIssue[];
56
+ conceptCount: number;
57
+ };
58
+ type BundleStats = {
59
+ title: string;
60
+ conceptCount: number;
61
+ typeDistribution: Record<string, number>;
62
+ tagDistribution: Record<string, number>;
63
+ linkCount: number;
64
+ brokenLinks: number;
65
+ orphanConcepts: string[];
66
+ topLinkedConcepts: Array<{
67
+ id: string;
68
+ title?: string;
69
+ count: number;
70
+ }>;
71
+ sourceDomains: Record<string, number>;
72
+ };
73
+
74
+ type CrawlOptions = {
75
+ seedUrl: string;
76
+ outDir: string;
77
+ maxPages?: number;
78
+ maxDepth?: number;
79
+ include?: string[];
80
+ exclude?: string[];
81
+ sameOrigin?: boolean;
82
+ respectRobots?: boolean;
83
+ concurrency?: number;
84
+ title?: string;
85
+ force?: boolean;
86
+ dryRun?: boolean;
87
+ allowPrivateNetwork?: boolean;
88
+ timestamp?: string;
89
+ };
90
+ type CrawlResult = {
91
+ pagesFetched: number;
92
+ skipped: number;
93
+ failed: number;
94
+ written: string[];
95
+ documents: NormalizedDocument[];
96
+ dryRunPages?: string[];
97
+ };
98
+ declare function crawlWebsite(options: CrawlOptions): Promise<CrawlResult>;
99
+
100
+ declare function extractInternalLinks(concept: Concept): string[];
101
+ declare function buildGraph(conceptsByAnyKey: Map<string, Concept>): KnowledgeGraph;
102
+
103
+ type ImportOptions = {
104
+ inputPath: string;
105
+ outDir: string;
106
+ sourceName?: string;
107
+ include?: string[];
108
+ exclude?: string[];
109
+ force?: boolean;
110
+ timestamp?: string;
111
+ };
112
+ declare function importLocal(options: ImportOptions): Promise<{
113
+ written: string[];
114
+ documents: NormalizedDocument[];
115
+ }>;
116
+
117
+ type ServeOptions = {
118
+ bundleDir: string;
119
+ name?: string;
120
+ maxResultChars?: number;
121
+ };
122
+ declare function createMcpServer(options: ServeOptions): Promise<Server>;
123
+ declare function serveMcpStdio(options: ServeOptions): Promise<void>;
124
+
125
+ declare function extractHeadings(markdown: string): Array<{
126
+ depth: number;
127
+ text: string;
128
+ slug: string;
129
+ }>;
130
+ declare function extractMarkdownLinks(markdown: string): Array<{
131
+ href: string;
132
+ text: string;
133
+ }>;
134
+ declare function inferType(title: string, sourceId: string, markdown: string): string;
135
+ declare function inferTags(title: string, sourceId: string, headings: Array<{
136
+ text: string;
137
+ }>): string[];
138
+ declare function normalizeDocument(raw: RawDocument): NormalizedDocument;
139
+ declare function descriptionFromMarkdown(markdown: string): string;
140
+
141
+ declare function readConceptFile(bundleDir: string, absolutePath: string): Promise<Concept>;
142
+ declare function readBundle(bundleDir: string): Promise<Map<string, Concept>>;
143
+
144
+ type SearchResult = {
145
+ id: string;
146
+ title?: string;
147
+ type: string;
148
+ description?: string;
149
+ tags: string[];
150
+ resource?: string;
151
+ snippet: string;
152
+ score: number;
153
+ };
154
+ declare class BundleSearch {
155
+ readonly graph: KnowledgeGraph;
156
+ private readonly index;
157
+ constructor(conceptsByAnyKey: Map<string, Concept>);
158
+ static fromBundle(bundleDir: string): Promise<BundleSearch>;
159
+ search(query: string, options?: {
160
+ type?: string;
161
+ tags?: string[];
162
+ limit?: number;
163
+ }): SearchResult[];
164
+ getConcept(idOrPath: string): Concept | undefined;
165
+ }
166
+
167
+ declare function validateBundle(bundleDir: string): Promise<ValidationReport>;
168
+ declare function inspectBundle(bundleDir: string): Promise<BundleStats>;
169
+
170
+ type WriteBundleOptions = {
171
+ outDir: string;
172
+ title?: string;
173
+ sourceName?: string;
174
+ force?: boolean;
175
+ timestamp?: string;
176
+ };
177
+ declare function writeOkfBundle(docs: NormalizedDocument[], options: WriteBundleOptions): Promise<string[]>;
178
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ import {
2
+ BundleSearch,
3
+ buildGraph,
4
+ crawlWebsite,
5
+ createMcpServer,
6
+ descriptionFromMarkdown,
7
+ extractHeadings,
8
+ extractInternalLinks,
9
+ extractMarkdownLinks,
10
+ importLocal,
11
+ inferTags,
12
+ inferType,
13
+ inspectBundle,
14
+ normalizeDocument,
15
+ readBundle,
16
+ readConceptFile,
17
+ serveMcpStdio,
18
+ validateBundle,
19
+ writeOkfBundle
20
+ } from "./chunk-C46QXZDU.js";
21
+ export {
22
+ BundleSearch,
23
+ buildGraph,
24
+ crawlWebsite,
25
+ createMcpServer,
26
+ descriptionFromMarkdown,
27
+ extractHeadings,
28
+ extractInternalLinks,
29
+ extractMarkdownLinks,
30
+ importLocal,
31
+ inferTags,
32
+ inferType,
33
+ inspectBundle,
34
+ normalizeDocument,
35
+ readBundle,
36
+ readConceptFile,
37
+ serveMcpStdio,
38
+ validateBundle,
39
+ writeOkfBundle
40
+ };
@@ -0,0 +1,278 @@
1
+ # MCP Client Setup
2
+
3
+ okfy exposes OKF bundles through a local stdio MCP server:
4
+
5
+ ```bash
6
+ npx -y okfy-ai serve ./tmp/okfy-docs --mcp
7
+ ```
8
+
9
+ Use stdio for local bundles. MCP stdio means the client launches `okfy` as a subprocess, sends JSON-RPC messages on stdin, and reads JSON-RPC responses on stdout. okfy logs should go to stderr.
10
+
11
+ ## Prepare a Bundle
12
+
13
+ Offline fixture:
14
+
15
+ ```bash
16
+ npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force
17
+ npx -y okfy-ai validate ./tmp/okfy-docs
18
+ npx -y okfy-ai inspect ./tmp/okfy-docs
19
+ ```
20
+
21
+ Expected output:
22
+
23
+ ```text
24
+ Concepts: 9
25
+ Validation: valid
26
+ Broken links: 0
27
+ ```
28
+
29
+ Docs-site crawl:
30
+
31
+ ```bash
32
+ npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
33
+ npx -y okfy-ai validate ./stripe-checkout-okf
34
+ npx -y okfy-ai serve ./stripe-checkout-okf --mcp
35
+ ```
36
+
37
+ ## Claude Code
38
+
39
+ Add okfy as a local stdio server:
40
+
41
+ ```bash
42
+ claude mcp add --transport stdio okfy-docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
43
+ claude mcp list
44
+ ```
45
+
46
+ Open Claude Code and check status:
47
+
48
+ ```text
49
+ /mcp
50
+ ```
51
+
52
+ Project-scoped config, saved as `.mcp.json` at project root:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "okfy-docs": {
58
+ "type": "stdio",
59
+ "command": "npx",
60
+ "args": ["-y", "okfy-ai", "serve", "./tmp/okfy-docs", "--mcp"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Example prompt:
67
+
68
+ ```text
69
+ Use the okfy-docs MCP server. Search for local Markdown import, read the best matching concept, inspect its neighbors, then explain the workflow with citations.
70
+ ```
71
+
72
+ Expected tool-call sequence:
73
+
74
+ ```text
75
+ ToolSearch or MCP server discovery
76
+ bundle_summary
77
+ search_concepts({ "query": "local Markdown import", "limit": 5 })
78
+ read_concept({ "id": "<best-result-id>" })
79
+ get_neighbors({ "id": "<best-result-id>", "depth": 1 })
80
+ read_concept({ "id": "<neighbor-id>", "max_chars": 4000 })
81
+ final answer with cited resource fields
82
+ ```
83
+
84
+ Troubleshooting:
85
+
86
+ - `spawn npx ENOENT`: install Node.js >=20 and ensure `npx` is on `PATH`.
87
+ - Server pending: run `/mcp`; approve project-scoped `.mcp.json` if prompted.
88
+ - Empty tools: run `npx -y okfy-ai validate ./tmp/okfy-docs`; invalid bundles should fail before serving.
89
+ - Output too large: lower `--max-result-chars`, or ask the agent to search before reading concepts.
90
+ - Wrong bundle path: use an absolute path in config if client starts from another working directory.
91
+
92
+ ## Claude Desktop
93
+
94
+ Claude Desktop uses MCP server JSON. Add this entry to `claude_desktop_config.json`:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "okfy-docs": {
100
+ "command": "npx",
101
+ "args": ["-y", "okfy-ai", "serve", "/absolute/path/to/okfy/tmp/okfy-docs", "--mcp"]
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Exact command represented by the config:
108
+
109
+ ```bash
110
+ npx -y okfy-ai serve /absolute/path/to/okfy/tmp/okfy-docs --mcp
111
+ ```
112
+
113
+ Restart Claude Desktop after editing config.
114
+
115
+ Example prompt:
116
+
117
+ ```text
118
+ Use okfy-docs. Find concepts about MCP tools, read the relevant concept, then tell me which tool to call first when answering a docs question.
119
+ ```
120
+
121
+ Expected tool-call sequence:
122
+
123
+ ```text
124
+ bundle_summary
125
+ search_concepts({ "query": "MCP tools" })
126
+ read_concept({ "id": "<mcp-tools-id>" })
127
+ answer with cited resource fields
128
+ ```
129
+
130
+ Troubleshooting:
131
+
132
+ - Desktop cannot find `npx`: replace `"command": "npx"` with full path from `which npx`.
133
+ - Server exits immediately: run exact command in terminal and fix bundle validation errors.
134
+ - No okfy tools visible: restart Claude Desktop after config changes.
135
+ - Relative path fails: use absolute bundle path.
136
+
137
+ ## Codex
138
+
139
+ Codex supports stdio MCP servers through `config.toml`.
140
+
141
+ User config path:
142
+
143
+ ```text
144
+ ~/.codex/config.toml
145
+ ```
146
+
147
+ Trusted project config path:
148
+
149
+ ```text
150
+ .codex/config.toml
151
+ ```
152
+
153
+ Add:
154
+
155
+ ```toml
156
+ [mcp_servers.okfy_docs]
157
+ command = "npx"
158
+ args = ["-y", "okfy-ai", "serve", "./tmp/okfy-docs", "--mcp"]
159
+ startup_timeout_sec = 20
160
+ tool_timeout_sec = 60
161
+ enabled = true
162
+ ```
163
+
164
+ Exact command represented by the config:
165
+
166
+ ```bash
167
+ npx -y okfy-ai serve ./tmp/okfy-docs --mcp
168
+ ```
169
+
170
+ CLI alternative:
171
+
172
+ ```bash
173
+ codex mcp add okfy_docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
174
+ codex mcp --help
175
+ ```
176
+
177
+ In Codex TUI, inspect active servers:
178
+
179
+ ```text
180
+ /mcp
181
+ ```
182
+
183
+ Example prompt:
184
+
185
+ ```text
186
+ Use the okfy_docs MCP server. Search for the concept about progressive disclosure, read it, then explain how okfy keeps agent context small.
187
+ ```
188
+
189
+ Expected tool-call sequence:
190
+
191
+ ```text
192
+ MCP server initialization
193
+ bundle_summary
194
+ search_concepts({ "query": "progressive disclosure agent context" })
195
+ read_concept({ "id": "<best-result-id>", "max_chars": 6000 })
196
+ get_neighbors({ "id": "<best-result-id>", "depth": 1 })
197
+ final answer with citations
198
+ ```
199
+
200
+ Troubleshooting:
201
+
202
+ - Config ignored: project `.codex/config.toml` loads only for trusted projects; use user config if unsure.
203
+ - Server startup timeout: increase `startup_timeout_sec` if first `npx` install is slow.
204
+ - Tool timeout: increase `tool_timeout_sec` for large bundles.
205
+ - Relative path wrong: set `cwd` or use absolute bundle path.
206
+ - Need current server list: run `/mcp` in TUI.
207
+
208
+ ## Generic MCP stdio
209
+
210
+ Use this JSON for clients that accept Claude-style `mcpServers` config:
211
+
212
+ ```json
213
+ {
214
+ "mcpServers": {
215
+ "okfy-docs": {
216
+ "command": "npx",
217
+ "args": ["-y", "okfy-ai", "serve", "./tmp/okfy-docs", "--mcp"],
218
+ "env": {}
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ Exact command:
225
+
226
+ ```bash
227
+ npx -y okfy-ai serve ./tmp/okfy-docs --mcp
228
+ ```
229
+
230
+ Expected protocol flow:
231
+
232
+ ```text
233
+ client starts subprocess
234
+ client sends initialize
235
+ server returns capabilities and instructions
236
+ client sends initialized
237
+ client calls tools/list
238
+ agent calls bundle_summary
239
+ agent calls search_concepts
240
+ agent calls read_concept
241
+ agent optionally calls get_neighbors
242
+ agent answers with resource citations
243
+ ```
244
+
245
+ Example prompt:
246
+
247
+ ```text
248
+ Use okfy-docs. Search for OKF bundle structure, read the most relevant concepts, and explain the generated files.
249
+ ```
250
+
251
+ Troubleshooting:
252
+
253
+ - stdout has logs: okfy must write only MCP JSON-RPC messages to stdout; logs belong on stderr.
254
+ - Client cannot start process: use absolute `command` path and absolute bundle path.
255
+ - `tools/list` empty: confirm `okfy serve` was started with `--mcp`.
256
+ - Search returns weak matches: run `okfy inspect` and verify titles, descriptions, and tags were generated.
257
+ - Agent reads too much: ask it to call `search_concepts` first and `read_concept` with `max_chars`.
258
+
259
+ ## Available okfy MCP Tools
260
+
261
+ ```text
262
+ search_concepts(query, type?, tags?, limit?)
263
+ read_concept(id, max_chars?)
264
+ get_neighbors(id, depth?)
265
+ list_types()
266
+ list_tags()
267
+ bundle_summary()
268
+ ```
269
+
270
+ Recommended answering pattern:
271
+
272
+ ```text
273
+ 1. Start with bundle_summary for scope.
274
+ 2. Use search_concepts for discovery.
275
+ 3. Read only top matching concepts.
276
+ 4. Use get_neighbors when relationship context matters.
277
+ 5. Cite resource fields from read_concept output.
278
+ ```