@moonsjs/mcp 0.1.2 → 0.1.4
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 +4 -2
- package/dist/cli.js +1 -1
- package/dist/{index-weym72tx.js → index-vh7a3f8x.js} +36 -8
- package/dist/index-vh7a3f8x.js.map +11 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/server.ts +10 -0
- package/src/tools.ts +41 -4
- package/dist/index-weym72tx.js.map +0 -11
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server (stdio) untuk framework [**Moons**](https://github.com/moonsjs/docs):
|
|
4
4
|
memberi agen AI jalan pintas terverifikasi — tabel route, registry serverFn
|
|
5
|
-
(hash), `moons check`,
|
|
5
|
+
(hash), `moons check`, pencarian dokumen konteks framework, dan penjelasan kode
|
|
6
|
+
error §15.
|
|
6
7
|
|
|
7
|
-
Tool: `list_routes`, `get_fn_registry`, `run_check`, `search_docs
|
|
8
|
+
Tool: `list_routes`, `get_fn_registry`, `run_check`, `search_docs`,
|
|
9
|
+
`explain_error`.
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
bunx --package @moonsjs/mcp moons-mcp # bin `moons-mcp` ≠ nama package → --package
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/tools.ts
|
|
2
|
+
import { MOONS_ERROR_CODES, findErrorEntry } from "@moonsjs/core";
|
|
2
3
|
import {
|
|
3
4
|
collectFns,
|
|
4
5
|
collectRoutes,
|
|
@@ -8,13 +9,18 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
8
9
|
import { createRequire } from "node:module";
|
|
9
10
|
import { dirname, join } from "node:path";
|
|
10
11
|
var CONTEXT_URL2 = "https://raw.githubusercontent.com/moonsjs/docs/main/docs/ai/CONTEXT.md";
|
|
11
|
-
|
|
12
|
+
var CONTEXT_URLS2 = [
|
|
13
|
+
"https://docs.devmoon.net/raw/ai/CONTEXT.md",
|
|
14
|
+
"https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md",
|
|
15
|
+
CONTEXT_URL2
|
|
16
|
+
];
|
|
17
|
+
function localContextPath2(cliEntry) {
|
|
12
18
|
return join(dirname(cliEntry), "..", "docs", "ai", "CONTEXT.md");
|
|
13
19
|
}
|
|
14
20
|
function readLocalContext() {
|
|
15
21
|
try {
|
|
16
22
|
const entry = createRequire(import.meta.url).resolve("@moonsjs/cli");
|
|
17
|
-
const path =
|
|
23
|
+
const path = localContextPath2(entry);
|
|
18
24
|
if (existsSync(path)) {
|
|
19
25
|
return { source: `local:${path}`, text: readFileSync(path, "utf8") };
|
|
20
26
|
}
|
|
@@ -26,11 +32,19 @@ async function defaultLoadContext() {
|
|
|
26
32
|
if (local !== null) {
|
|
27
33
|
return local;
|
|
28
34
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
let lastError = null;
|
|
36
|
+
for (const url of CONTEXT_URLS2) {
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(url);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`HTTP ${response.status}`);
|
|
41
|
+
}
|
|
42
|
+
return { source: url, text: await response.text() };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
lastError = error;
|
|
45
|
+
}
|
|
32
46
|
}
|
|
33
|
-
|
|
47
|
+
throw new Error(`gagal mengambil konteks dari ${CONTEXT_URLS2.join(" dan ")}: ${String(lastError)}`);
|
|
34
48
|
}
|
|
35
49
|
function searchMarkdown2(text, query, limit = 20) {
|
|
36
50
|
const needle = query.trim().toLowerCase();
|
|
@@ -71,6 +85,16 @@ function createTools2(options) {
|
|
|
71
85
|
cached = await loadContext();
|
|
72
86
|
}
|
|
73
87
|
return { source: cached.source, query, matches: searchMarkdown2(cached.text, query) };
|
|
88
|
+
},
|
|
89
|
+
explainError: async (code) => {
|
|
90
|
+
const normalized = code.trim().toUpperCase();
|
|
91
|
+
const entry = findErrorEntry(normalized);
|
|
92
|
+
return {
|
|
93
|
+
code: normalized,
|
|
94
|
+
found: entry !== undefined,
|
|
95
|
+
...entry === undefined ? {} : { entry },
|
|
96
|
+
validCodes: [...MOONS_ERROR_CODES]
|
|
97
|
+
};
|
|
74
98
|
}
|
|
75
99
|
};
|
|
76
100
|
}
|
|
@@ -108,9 +132,13 @@ function createMcpServer2(options) {
|
|
|
108
132
|
description: "Search the Moons agent context pack (docs/ai/CONTEXT.md) by substring; returns matching lines with their sections.",
|
|
109
133
|
inputSchema: { query: z.string().describe("substring to search (case-insensitive)") }
|
|
110
134
|
}, ({ query }) => guard(() => tools.searchDocs(query)));
|
|
135
|
+
server.registerTool("explain_error", {
|
|
136
|
+
description: "Explain a Moons error code (format [moons] E_...): trigger, details, and fix suggestions. Returns found=false + validCodes for unknown codes.",
|
|
137
|
+
inputSchema: { code: z.string().describe("error code, e.g. E_SERVER_IMPORT_IN_CLIENT") }
|
|
138
|
+
}, ({ code }) => guard(() => tools.explainError(code)));
|
|
111
139
|
return server;
|
|
112
140
|
}
|
|
113
141
|
|
|
114
|
-
export { CONTEXT_URL2, searchMarkdown2, createTools2, createMcpServer2 };
|
|
142
|
+
export { CONTEXT_URL2, CONTEXT_URLS2, localContextPath2, searchMarkdown2, createTools2, createMcpServer2 };
|
|
115
143
|
|
|
116
|
-
//# debugId=
|
|
144
|
+
//# debugId=4DC9435A96ACE45F64756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tools.ts", "../src/server.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { MOONS_ERROR_CODES, findErrorEntry, type ErrorCatalogEntry } from \"@moonsjs/core\";\nimport {\n collectFns,\n collectRoutes,\n executeCheck,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\nexport const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/docs/ai/CONTEXT.md\";\n\n/** Rantai fallback remote: situs docs → pages.dev → GitHub raw (mirror). */\nexport const CONTEXT_URLS: readonly string[] = [\n \"https://docs.devmoon.net/raw/ai/CONTEXT.md\",\n \"https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md\",\n CONTEXT_URL,\n];\n\n/** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */\nexport function localContextPath(cliEntry: string): string {\n return join(dirname(cliEntry), \"..\", \"docs\", \"ai\", \"CONTEXT.md\");\n}\n\nfunction readLocalContext(): { source: string; text: string } | null {\n try {\n const entry = createRequire(import.meta.url).resolve(\"@moonsjs/cli\");\n const path = localContextPath(entry);\n if (existsSync(path)) {\n return { source: `local:${path}`, text: readFileSync(path, \"utf8\") };\n }\n } catch {\n // fallthrough ke remote\n }\n return null;\n}\n\nasync function defaultLoadContext(): Promise<{ source: string; text: string }> {\n const local = readLocalContext();\n if (local !== null) {\n return local;\n }\n let lastError: unknown = null;\n for (const url of CONTEXT_URLS) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n return { source: url, text: await response.text() };\n } catch (error) {\n lastError = error;\n }\n }\n throw new Error(\n `gagal mengambil konteks dari ${CONTEXT_URLS.join(\" dan \")}: ${String(lastError)}`,\n );\n}\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 ExplainErrorResult {\n code: string;\n found: boolean;\n entry?: ErrorCatalogEntry;\n validCodes: string[];\n}\n\nexport interface Toolset {\n listRoutes: () => Promise<RoutesJson>;\n getFnRegistry: () => Promise<FnsJson>;\n runCheck: () => Promise<CheckJson>;\n searchDocs: (query: string) => Promise<SearchDocsResult>;\n /** Addendum B.6: kode error §15 → pemicu + saran. */\n explainError: (code: string) => Promise<ExplainErrorResult>;\n}\n\nexport interface ToolsetOptions {\n cwd: string;\n fetchContext?: ContextFetcher;\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 loadContext =\n options.fetchContext === undefined\n ? defaultLoadContext\n : async (): Promise<{ source: string; text: string }> => ({\n source: CONTEXT_URL,\n text: await (options.fetchContext as ContextFetcher)(),\n });\n let cached: { source: string; text: 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 loadContext();\n }\n return { source: cached.source, query, matches: searchMarkdown(cached.text, query) };\n },\n explainError: async (code: string): Promise<ExplainErrorResult> => {\n const normalized = code.trim().toUpperCase();\n const entry = findErrorEntry(normalized);\n return {\n code: normalized,\n found: entry !== undefined,\n ...(entry === undefined ? {} : { entry }),\n validCodes: [...MOONS_ERROR_CODES],\n };\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 server.registerTool(\n \"explain_error\",\n {\n description:\n \"Explain a Moons error code (format [moons] E_...): trigger, details, and fix suggestions. Returns found=false + validCodes for unknown codes.\",\n inputSchema: { code: z.string().describe(\"error code, e.g. E_SERVER_IMPORT_IN_CLIENT\") },\n },\n ({ code }) => guard(() => tools.explainError(code)),\n );\n\n return server;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "4DC9435A96ACE45F64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONTEXT_URL2,
|
|
3
|
+
CONTEXT_URLS2,
|
|
4
|
+
localContextPath2,
|
|
3
5
|
searchMarkdown2,
|
|
4
6
|
createTools2,
|
|
5
7
|
createMcpServer2
|
|
6
|
-
} from "./index-
|
|
8
|
+
} from "./index-vh7a3f8x.js";
|
|
7
9
|
export {
|
|
8
10
|
CONTEXT_URL2 as CONTEXT_URL,
|
|
11
|
+
CONTEXT_URLS2 as CONTEXT_URLS,
|
|
9
12
|
createMcpServer2 as createMcpServer,
|
|
10
13
|
createTools2 as createTools,
|
|
14
|
+
localContextPath2 as localContextPath,
|
|
11
15
|
searchMarkdown2 as searchMarkdown
|
|
12
16
|
};
|
|
13
17
|
|
|
14
|
-
//# debugId=
|
|
18
|
+
//# debugId=590626D8D16E0DDB64756E2164756E21
|
package/dist/index.js.map
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { createTools, searchMarkdown, CONTEXT_URL } from "./tools";
|
|
1
|
+
export { createTools, searchMarkdown, CONTEXT_URL, CONTEXT_URLS, localContextPath } from "./tools";
|
|
2
2
|
export type {
|
|
3
3
|
ContextFetcher,
|
|
4
4
|
DocMatch,
|
|
5
|
+
ExplainErrorResult,
|
|
5
6
|
SearchDocsResult,
|
|
6
7
|
Toolset,
|
|
7
8
|
ToolsetOptions,
|
package/src/server.ts
CHANGED
|
@@ -65,5 +65,15 @@ export function createMcpServer(options: ToolsetOptions): McpServer {
|
|
|
65
65
|
({ query }) => guard(() => tools.searchDocs(query)),
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
+
server.registerTool(
|
|
69
|
+
"explain_error",
|
|
70
|
+
{
|
|
71
|
+
description:
|
|
72
|
+
"Explain a Moons error code (format [moons] E_...): trigger, details, and fix suggestions. Returns found=false + validCodes for unknown codes.",
|
|
73
|
+
inputSchema: { code: z.string().describe("error code, e.g. E_SERVER_IMPORT_IN_CLIENT") },
|
|
74
|
+
},
|
|
75
|
+
({ code }) => guard(() => tools.explainError(code)),
|
|
76
|
+
);
|
|
77
|
+
|
|
68
78
|
return server;
|
|
69
79
|
}
|
package/src/tools.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MOONS_ERROR_CODES, findErrorEntry, type ErrorCatalogEntry } from "@moonsjs/core";
|
|
1
2
|
import {
|
|
2
3
|
collectFns,
|
|
3
4
|
collectRoutes,
|
|
@@ -16,6 +17,13 @@ export type ContextFetcher = () => Promise<string>;
|
|
|
16
17
|
export const CONTEXT_URL =
|
|
17
18
|
"https://raw.githubusercontent.com/moonsjs/docs/main/docs/ai/CONTEXT.md";
|
|
18
19
|
|
|
20
|
+
/** Rantai fallback remote: situs docs → pages.dev → GitHub raw (mirror). */
|
|
21
|
+
export const CONTEXT_URLS: readonly string[] = [
|
|
22
|
+
"https://docs.devmoon.net/raw/ai/CONTEXT.md",
|
|
23
|
+
"https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md",
|
|
24
|
+
CONTEXT_URL,
|
|
25
|
+
];
|
|
26
|
+
|
|
19
27
|
/** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */
|
|
20
28
|
export function localContextPath(cliEntry: string): string {
|
|
21
29
|
return join(dirname(cliEntry), "..", "docs", "ai", "CONTEXT.md");
|
|
@@ -39,11 +47,21 @@ async function defaultLoadContext(): Promise<{ source: string; text: string }> {
|
|
|
39
47
|
if (local !== null) {
|
|
40
48
|
return local;
|
|
41
49
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
let lastError: unknown = null;
|
|
51
|
+
for (const url of CONTEXT_URLS) {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`HTTP ${response.status}`);
|
|
56
|
+
}
|
|
57
|
+
return { source: url, text: await response.text() };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
lastError = error;
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
|
-
|
|
62
|
+
throw new Error(
|
|
63
|
+
`gagal mengambil konteks dari ${CONTEXT_URLS.join(" dan ")}: ${String(lastError)}`,
|
|
64
|
+
);
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
export interface DocMatch {
|
|
@@ -60,11 +78,20 @@ export interface SearchDocsResult {
|
|
|
60
78
|
matches: DocMatch[];
|
|
61
79
|
}
|
|
62
80
|
|
|
81
|
+
export interface ExplainErrorResult {
|
|
82
|
+
code: string;
|
|
83
|
+
found: boolean;
|
|
84
|
+
entry?: ErrorCatalogEntry;
|
|
85
|
+
validCodes: string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
export interface Toolset {
|
|
64
89
|
listRoutes: () => Promise<RoutesJson>;
|
|
65
90
|
getFnRegistry: () => Promise<FnsJson>;
|
|
66
91
|
runCheck: () => Promise<CheckJson>;
|
|
67
92
|
searchDocs: (query: string) => Promise<SearchDocsResult>;
|
|
93
|
+
/** Addendum B.6: kode error §15 → pemicu + saran. */
|
|
94
|
+
explainError: (code: string) => Promise<ExplainErrorResult>;
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
export interface ToolsetOptions {
|
|
@@ -121,5 +148,15 @@ export function createTools(options: ToolsetOptions): Toolset {
|
|
|
121
148
|
}
|
|
122
149
|
return { source: cached.source, query, matches: searchMarkdown(cached.text, query) };
|
|
123
150
|
},
|
|
151
|
+
explainError: async (code: string): Promise<ExplainErrorResult> => {
|
|
152
|
+
const normalized = code.trim().toUpperCase();
|
|
153
|
+
const entry = findErrorEntry(normalized);
|
|
154
|
+
return {
|
|
155
|
+
code: normalized,
|
|
156
|
+
found: entry !== undefined,
|
|
157
|
+
...(entry === undefined ? {} : { entry }),
|
|
158
|
+
validCodes: [...MOONS_ERROR_CODES],
|
|
159
|
+
};
|
|
160
|
+
},
|
|
124
161
|
};
|
|
125
162
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
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\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\nexport const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/docs/ai/CONTEXT.md\";\n\n/** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */\nexport function localContextPath(cliEntry: string): string {\n return join(dirname(cliEntry), \"..\", \"docs\", \"ai\", \"CONTEXT.md\");\n}\n\nfunction readLocalContext(): { source: string; text: string } | null {\n try {\n const entry = createRequire(import.meta.url).resolve(\"@moonsjs/cli\");\n const path = localContextPath(entry);\n if (existsSync(path)) {\n return { source: `local:${path}`, text: readFileSync(path, \"utf8\") };\n }\n } catch {\n // fallthrough ke remote\n }\n return null;\n}\n\nasync function defaultLoadContext(): Promise<{ source: string; text: string }> {\n const local = readLocalContext();\n if (local !== null) {\n return local;\n }\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 { source: CONTEXT_URL, text: await response.text() };\n}\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\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 loadContext =\n options.fetchContext === undefined\n ? defaultLoadContext\n : async (): Promise<{ source: string; text: string }> => ({\n source: CONTEXT_URL,\n text: await (options.fetchContext as ContextFetcher)(),\n });\n let cached: { source: string; text: 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 loadContext();\n }\n return { source: cached.source, query, matches: searchMarkdown(cached.text, 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": "22A03F28E6EC57F864756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|