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.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/assets/demo.gif +0 -0
- package/assets/logo.svg +14 -0
- package/dist/chunk-C46QXZDU.js +1013 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +151 -0
- package/dist/index.d.ts +179 -0
- package/dist/index.js +40 -0
- package/docs/mcp-clients.md +278 -0
- package/examples/README.md +98 -0
- package/examples/bundles/okfy-docs/concepts/index.md +14 -0
- package/examples/bundles/okfy-docs/concepts/okf-bundle.md +33 -0
- package/examples/bundles/okfy-docs/concepts/progressive-disclosure.md +26 -0
- package/examples/bundles/okfy-docs/guides/import-local-markdown.md +31 -0
- package/examples/bundles/okfy-docs/guides/index.md +14 -0
- package/examples/bundles/okfy-docs/guides/serve-over-mcp.md +29 -0
- package/examples/bundles/okfy-docs/index.md +22 -0
- package/examples/bundles/okfy-docs/okfy-example.json +10 -0
- package/examples/bundles/okfy-docs/reference/index.md +13 -0
- package/examples/bundles/okfy-docs/reference/mcp-tools.md +36 -0
- package/examples/bundles/stripe-checkout-small/index.md +21 -0
- package/examples/bundles/stripe-checkout-small/okfy-example.json +11 -0
- package/examples/bundles/stripe-checkout-small/quickstart.md +26 -0
- package/examples/bundles/stripe-checkout-small/sessions.md +20 -0
- package/examples/bundles/stripe-checkout-small/webhooks.md +19 -0
- package/examples/local-markdown/concepts/okf-bundle.md +19 -0
- package/examples/local-markdown/concepts/progressive-disclosure.md +15 -0
- package/examples/local-markdown/guides/import-local-markdown.md +20 -0
- package/examples/local-markdown/guides/serve-over-mcp.md +17 -0
- package/examples/local-markdown/index.md +11 -0
- package/examples/local-markdown/okfy-example.json +10 -0
- package/examples/local-markdown/reference/mcp-tools.md +25 -0
- 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);
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
```
|