@moonsjs/mcp 0.0.0-stage → 0.1.0
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/dist/cli.js +11 -0
- package/dist/cli.js.map +10 -0
- package/dist/index-hp290nyb.js +95 -0
- package/dist/index-hp290nyb.js.map +11 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +9 -0
- package/package.json +42 -4
- package/src/cli.ts +6 -0
- package/src/ctx-fallback.d.ts +9 -0
- package/src/index.ts +9 -0
- package/src/server.ts +69 -0
- package/src/tools.ts +96 -0
- package/README.md +0 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import {
|
|
3
|
+
createMcpServer2
|
|
4
|
+
} from "./index-hp290nyb.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
var server = createMcpServer2({ cwd: process.cwd() });
|
|
9
|
+
await server.connect(new StdioServerTransport);
|
|
10
|
+
|
|
11
|
+
//# debugId=7EE8F785146C32D364756E2164756E21
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cli.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"#!/usr/bin/env bun\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createMcpServer } from \"./server\";\n\nconst server = createMcpServer({ cwd: process.cwd() });\nawait server.connect(new StdioServerTransport());\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;AACA;AAGA,IAAM,SAAS,iBAAgB,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC;AACrD,MAAM,OAAO,QAAQ,IAAI,oBAAsB;",
|
|
8
|
+
"debugId": "7EE8F785146C32D364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/tools.ts
|
|
2
|
+
import {
|
|
3
|
+
collectFns,
|
|
4
|
+
collectRoutes,
|
|
5
|
+
executeCheck
|
|
6
|
+
} from "@moonsjs/cli";
|
|
7
|
+
var CONTEXT_URL2 = "https://raw.githubusercontent.com/RamonymousDev/Moons/main/docs/ai/CONTEXT.md";
|
|
8
|
+
function defaultFetcher() {
|
|
9
|
+
return async () => {
|
|
10
|
+
const response = await fetch(CONTEXT_URL2);
|
|
11
|
+
if (!response.ok) {
|
|
12
|
+
throw new Error(`gagal mengambil ${CONTEXT_URL2} (HTTP ${response.status})`);
|
|
13
|
+
}
|
|
14
|
+
return await response.text();
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function searchMarkdown2(text, query, limit = 20) {
|
|
18
|
+
const needle = query.trim().toLowerCase();
|
|
19
|
+
if (needle.length === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
const lines = text.split(`
|
|
23
|
+
`);
|
|
24
|
+
const matches = [];
|
|
25
|
+
let section = "(top)";
|
|
26
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
27
|
+
const line = lines[index];
|
|
28
|
+
const heading = /^#{1,6}\s+(.*)$/.exec(line);
|
|
29
|
+
if (heading !== null) {
|
|
30
|
+
section = (heading[1] ?? "").trim();
|
|
31
|
+
}
|
|
32
|
+
if (line.toLowerCase().includes(needle)) {
|
|
33
|
+
matches.push({ section, line: index + 1, text: line.trim() });
|
|
34
|
+
if (matches.length >= limit) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return matches;
|
|
40
|
+
}
|
|
41
|
+
function createTools2(options) {
|
|
42
|
+
const fetchContext = options.fetchContext ?? defaultFetcher();
|
|
43
|
+
let cached = null;
|
|
44
|
+
return {
|
|
45
|
+
listRoutes: () => collectRoutes(options.cwd),
|
|
46
|
+
getFnRegistry: () => Promise.resolve(collectFns(options.cwd)),
|
|
47
|
+
runCheck: () => executeCheck(options.cwd, { quiet: true }),
|
|
48
|
+
searchDocs: async (query) => {
|
|
49
|
+
if (cached === null) {
|
|
50
|
+
cached = await fetchContext();
|
|
51
|
+
}
|
|
52
|
+
return { source: CONTEXT_URL2, query, matches: searchMarkdown2(cached, query) };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/server.ts
|
|
58
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
59
|
+
import { z } from "zod";
|
|
60
|
+
function jsonContent(value, isError = false) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
63
|
+
...isError ? { isError: true } : {}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function guard(run) {
|
|
67
|
+
try {
|
|
68
|
+
return jsonContent(await run());
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
return jsonContent({ ok: false, error: message }, true);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function createMcpServer2(options) {
|
|
75
|
+
const tools = createTools2(options);
|
|
76
|
+
const server = new McpServer({ name: "moons", version: "0.1.0" });
|
|
77
|
+
server.registerTool("list_routes", {
|
|
78
|
+
description: "Route table of the Moons app (path, file, key, kind, params, layouts) — scanned, no build needed."
|
|
79
|
+
}, () => guard(() => tools.listRoutes()));
|
|
80
|
+
server.registerTool("get_fn_registry", {
|
|
81
|
+
description: "serverFn registry of the Moons app (file#export → hash, line) plus the registry map written to .moons/fn-registry.json."
|
|
82
|
+
}, () => guard(() => tools.getFnRegistry()));
|
|
83
|
+
server.registerTool("run_check", {
|
|
84
|
+
description: "Definition of done: typegen → tsc --noEmit → build client → build server. Returns { ok, steps }."
|
|
85
|
+
}, () => guard(() => tools.runCheck()));
|
|
86
|
+
server.registerTool("search_docs", {
|
|
87
|
+
description: "Search the Moons agent context pack (docs/ai/CONTEXT.md) by substring; returns matching lines with their sections.",
|
|
88
|
+
inputSchema: { query: z.string().describe("substring to search (case-insensitive)") }
|
|
89
|
+
}, ({ query }) => guard(() => tools.searchDocs(query)));
|
|
90
|
+
return server;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { CONTEXT_URL2, searchMarkdown2, createTools2, createMcpServer2 };
|
|
94
|
+
|
|
95
|
+
//# debugId=9E9BBAFBC5A8DB0664756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tools.ts", "../src/server.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import {\n collectFns,\n collectRoutes,\n executeCheck,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\n\n/** Sumber teks konteks framework (default: GitHub raw; injectable untuk test). */\nexport type ContextFetcher = () => Promise<string>;\n\nexport const CONTEXT_URL =\n \"https://raw.githubusercontent.com/RamonymousDev/Moons/main/docs/ai/CONTEXT.md\";\n\nexport interface DocMatch {\n /** Judul section terakhir sebelum baris ini. */\n section: string;\n /** Nomor baris (1-based) di CONTEXT.md. */\n line: number;\n text: string;\n}\n\nexport interface SearchDocsResult {\n source: string;\n query: string;\n matches: DocMatch[];\n}\n\nexport interface Toolset {\n listRoutes: () => Promise<RoutesJson>;\n getFnRegistry: () => Promise<FnsJson>;\n runCheck: () => Promise<CheckJson>;\n searchDocs: (query: string) => Promise<SearchDocsResult>;\n}\n\nexport interface ToolsetOptions {\n cwd: string;\n fetchContext?: ContextFetcher;\n}\n\nfunction defaultFetcher(): ContextFetcher {\n return async () => {\n const response = await fetch(CONTEXT_URL);\n if (!response.ok) {\n throw new Error(`gagal mengambil ${CONTEXT_URL} (HTTP ${response.status})`);\n }\n return await response.text();\n };\n}\n\n/** Pencarian substring case-insensitive atas markdown; returns baris + section. */\nexport function searchMarkdown(text: string, query: string, limit = 20): DocMatch[] {\n const needle = query.trim().toLowerCase();\n if (needle.length === 0) {\n return [];\n }\n\n const lines = text.split(\"\\n\");\n const matches: DocMatch[] = [];\n let section = \"(top)\";\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index] as string;\n const heading = /^#{1,6}\\s+(.*)$/.exec(line);\n if (heading !== null) {\n section = (heading[1] ?? \"\").trim();\n }\n if (line.toLowerCase().includes(needle)) {\n matches.push({ section, line: index + 1, text: line.trim() });\n if (matches.length >= limit) {\n break;\n }\n }\n }\n\n return matches;\n}\n\n/** Toolset MCP (Addendum A.7) — deterministik, tanpa panggilan LLM. */\nexport function createTools(options: ToolsetOptions): Toolset {\n const fetchContext = options.fetchContext ?? defaultFetcher();\n let cached: string | null = null;\n\n return {\n listRoutes: () => collectRoutes(options.cwd),\n getFnRegistry: () => Promise.resolve(collectFns(options.cwd)),\n runCheck: () => executeCheck(options.cwd, { quiet: true }),\n searchDocs: async (query: string): Promise<SearchDocsResult> => {\n if (cached === null) {\n cached = await fetchContext();\n }\n return { source: CONTEXT_URL, query, matches: searchMarkdown(cached, query) };\n },\n };\n}\n",
|
|
6
|
+
"import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { createTools, type ToolsetOptions } from \"./tools\";\n\nfunction jsonContent(value: unknown, isError = false): {\n content: Array<{ type: \"text\"; text: string }>;\n isError?: boolean;\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n ...(isError ? { isError: true } : {}),\n };\n}\n\nasync function guard(\n run: () => Promise<unknown>,\n): Promise<{ content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n return jsonContent(await run());\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return jsonContent({ ok: false, error: message }, true);\n }\n}\n\n/** Bangun MCP server `moons` (stdio) untuk satu project (Addendum A.7). */\nexport function createMcpServer(options: ToolsetOptions): McpServer {\n const tools = createTools(options);\n const server = new McpServer({ name: \"moons\", version: \"0.1.0\" });\n\n server.registerTool(\n \"list_routes\",\n {\n description:\n \"Route table of the Moons app (path, file, key, kind, params, layouts) — scanned, no build needed.\",\n },\n () => guard(() => tools.listRoutes()),\n );\n\n server.registerTool(\n \"get_fn_registry\",\n {\n description:\n \"serverFn registry of the Moons app (file#export → hash, line) plus the registry map written to .moons/fn-registry.json.\",\n },\n () => guard(() => tools.getFnRegistry()),\n );\n\n server.registerTool(\n \"run_check\",\n {\n description:\n \"Definition of done: typegen → tsc --noEmit → build client → build server. Returns { ok, steps }.\",\n },\n () => guard(() => tools.runCheck()),\n );\n\n server.registerTool(\n \"search_docs\",\n {\n description:\n \"Search the Moons agent context pack (docs/ai/CONTEXT.md) by substring; returns matching lines with their sections.\",\n inputSchema: { query: z.string().describe(\"substring to search (case-insensitive)\") },\n },\n ({ query }) => guard(() => tools.searchDocs(query)),\n );\n\n return server;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "9E9BBAFBC5A8DB0664756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTEXT_URL2,
|
|
3
|
+
searchMarkdown2,
|
|
4
|
+
createTools2,
|
|
5
|
+
createMcpServer2
|
|
6
|
+
} from "./index-hp290nyb.js";
|
|
7
|
+
export {
|
|
8
|
+
CONTEXT_URL2 as CONTEXT_URL,
|
|
9
|
+
createMcpServer2 as createMcpServer,
|
|
10
|
+
createTools2 as createTools,
|
|
11
|
+
searchMarkdown2 as searchMarkdown
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//# debugId=D02D2DA632CDA0F864756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonsjs/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/RamonymousDev/Moons.git",
|
|
10
|
+
"directory": "packages/mcp"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/RamonymousDev/Moons#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/RamonymousDev/Moons/issues"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"bin": {
|
|
21
|
+
"moons-mcp": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./src/index.ts",
|
|
26
|
+
"bun": "./src/index.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"prepack": "bun run build",
|
|
32
|
+
"build": "rm -rf dist && tsc -p tsconfig.json --emitDeclarationOnly && bun build src/index.ts src/cli.ts --outdir dist --target node --format esm --splitting --sourcemap=external --external @moonsjs/cli --external '@moonsjs/cli/*' --external @moonsjs/core --external '@moonsjs/core/*' --external @modelcontextprotocol/sdk --external '@modelcontextprotocol/sdk/*' --external zod"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
36
|
+
"@moonsjs/cli": "^0.1.0",
|
|
37
|
+
"@moonsjs/core": "^0.1.0",
|
|
38
|
+
"zod": "^4.6.7"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/bun": "^1.4.3",
|
|
42
|
+
"typescript": "^7.0.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/cli.ts
ADDED
package/src/index.ts
ADDED
package/src/server.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createTools, type ToolsetOptions } from "./tools";
|
|
4
|
+
|
|
5
|
+
function jsonContent(value: unknown, isError = false): {
|
|
6
|
+
content: Array<{ type: "text"; text: string }>;
|
|
7
|
+
isError?: boolean;
|
|
8
|
+
} {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
11
|
+
...(isError ? { isError: true } : {}),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function guard(
|
|
16
|
+
run: () => Promise<unknown>,
|
|
17
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
18
|
+
try {
|
|
19
|
+
return jsonContent(await run());
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
return jsonContent({ ok: false, error: message }, true);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Bangun MCP server `moons` (stdio) untuk satu project (Addendum A.7). */
|
|
27
|
+
export function createMcpServer(options: ToolsetOptions): McpServer {
|
|
28
|
+
const tools = createTools(options);
|
|
29
|
+
const server = new McpServer({ name: "moons", version: "0.1.0" });
|
|
30
|
+
|
|
31
|
+
server.registerTool(
|
|
32
|
+
"list_routes",
|
|
33
|
+
{
|
|
34
|
+
description:
|
|
35
|
+
"Route table of the Moons app (path, file, key, kind, params, layouts) — scanned, no build needed.",
|
|
36
|
+
},
|
|
37
|
+
() => guard(() => tools.listRoutes()),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
server.registerTool(
|
|
41
|
+
"get_fn_registry",
|
|
42
|
+
{
|
|
43
|
+
description:
|
|
44
|
+
"serverFn registry of the Moons app (file#export → hash, line) plus the registry map written to .moons/fn-registry.json.",
|
|
45
|
+
},
|
|
46
|
+
() => guard(() => tools.getFnRegistry()),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
server.registerTool(
|
|
50
|
+
"run_check",
|
|
51
|
+
{
|
|
52
|
+
description:
|
|
53
|
+
"Definition of done: typegen → tsc --noEmit → build client → build server. Returns { ok, steps }.",
|
|
54
|
+
},
|
|
55
|
+
() => guard(() => tools.runCheck()),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
server.registerTool(
|
|
59
|
+
"search_docs",
|
|
60
|
+
{
|
|
61
|
+
description:
|
|
62
|
+
"Search the Moons agent context pack (docs/ai/CONTEXT.md) by substring; returns matching lines with their sections.",
|
|
63
|
+
inputSchema: { query: z.string().describe("substring to search (case-insensitive)") },
|
|
64
|
+
},
|
|
65
|
+
({ query }) => guard(() => tools.searchDocs(query)),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return server;
|
|
69
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectFns,
|
|
3
|
+
collectRoutes,
|
|
4
|
+
executeCheck,
|
|
5
|
+
type CheckJson,
|
|
6
|
+
type FnsJson,
|
|
7
|
+
type RoutesJson,
|
|
8
|
+
} from "@moonsjs/cli";
|
|
9
|
+
|
|
10
|
+
/** Sumber teks konteks framework (default: GitHub raw; injectable untuk test). */
|
|
11
|
+
export type ContextFetcher = () => Promise<string>;
|
|
12
|
+
|
|
13
|
+
export const CONTEXT_URL =
|
|
14
|
+
"https://raw.githubusercontent.com/RamonymousDev/Moons/main/docs/ai/CONTEXT.md";
|
|
15
|
+
|
|
16
|
+
export interface DocMatch {
|
|
17
|
+
/** Judul section terakhir sebelum baris ini. */
|
|
18
|
+
section: string;
|
|
19
|
+
/** Nomor baris (1-based) di CONTEXT.md. */
|
|
20
|
+
line: number;
|
|
21
|
+
text: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SearchDocsResult {
|
|
25
|
+
source: string;
|
|
26
|
+
query: string;
|
|
27
|
+
matches: DocMatch[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Toolset {
|
|
31
|
+
listRoutes: () => Promise<RoutesJson>;
|
|
32
|
+
getFnRegistry: () => Promise<FnsJson>;
|
|
33
|
+
runCheck: () => Promise<CheckJson>;
|
|
34
|
+
searchDocs: (query: string) => Promise<SearchDocsResult>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ToolsetOptions {
|
|
38
|
+
cwd: string;
|
|
39
|
+
fetchContext?: ContextFetcher;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function defaultFetcher(): ContextFetcher {
|
|
43
|
+
return async () => {
|
|
44
|
+
const response = await fetch(CONTEXT_URL);
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`gagal mengambil ${CONTEXT_URL} (HTTP ${response.status})`);
|
|
47
|
+
}
|
|
48
|
+
return await response.text();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Pencarian substring case-insensitive atas markdown; returns baris + section. */
|
|
53
|
+
export function searchMarkdown(text: string, query: string, limit = 20): DocMatch[] {
|
|
54
|
+
const needle = query.trim().toLowerCase();
|
|
55
|
+
if (needle.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const lines = text.split("\n");
|
|
60
|
+
const matches: DocMatch[] = [];
|
|
61
|
+
let section = "(top)";
|
|
62
|
+
|
|
63
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
64
|
+
const line = lines[index] as string;
|
|
65
|
+
const heading = /^#{1,6}\s+(.*)$/.exec(line);
|
|
66
|
+
if (heading !== null) {
|
|
67
|
+
section = (heading[1] ?? "").trim();
|
|
68
|
+
}
|
|
69
|
+
if (line.toLowerCase().includes(needle)) {
|
|
70
|
+
matches.push({ section, line: index + 1, text: line.trim() });
|
|
71
|
+
if (matches.length >= limit) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return matches;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Toolset MCP (Addendum A.7) — deterministik, tanpa panggilan LLM. */
|
|
81
|
+
export function createTools(options: ToolsetOptions): Toolset {
|
|
82
|
+
const fetchContext = options.fetchContext ?? defaultFetcher();
|
|
83
|
+
let cached: string | null = null;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
listRoutes: () => collectRoutes(options.cwd),
|
|
87
|
+
getFnRegistry: () => Promise.resolve(collectFns(options.cwd)),
|
|
88
|
+
runCheck: () => executeCheck(options.cwd, { quiet: true }),
|
|
89
|
+
searchDocs: async (query: string): Promise<SearchDocsResult> => {
|
|
90
|
+
if (cached === null) {
|
|
91
|
+
cached = await fetchContext();
|
|
92
|
+
}
|
|
93
|
+
return { source: CONTEXT_URL, query, matches: searchMarkdown(cached, query) };
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
package/README.md
DELETED