@moonsjs/mcp 0.1.8 → 0.1.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
3
  createMcpServer2
4
- } from "./index-p83g77ym.js";
4
+ } from "./index-fd4fnsn3.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -13,9 +13,11 @@ import { dirname, join, relative, sep } from "node:path";
13
13
  var CONTEXT_URL2 = "https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md";
14
14
  var CONTEXT_URLS2 = [
15
15
  "https://docs.devmoon.net/raw/ai/CONTEXT.md",
16
- "https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md",
17
16
  CONTEXT_URL2
18
17
  ];
18
+ function isContextMarkdown(text) {
19
+ return /^#\s+Moons\b/.test(text.trimStart());
20
+ }
19
21
  function localContextPath2(cliEntry) {
20
22
  return join(dirname(cliEntry), "..", "docs", "ai", "CONTEXT.md");
21
23
  }
@@ -24,7 +26,10 @@ function readLocalContext() {
24
26
  const entry = createRequire(import.meta.url).resolve("@moonsjs/cli");
25
27
  const path = localContextPath2(entry);
26
28
  if (existsSync(path)) {
27
- return { source: `local:${path}`, text: readFileSync(path, "utf8") };
29
+ const text = readFileSync(path, "utf8");
30
+ if (isContextMarkdown(text)) {
31
+ return { source: `local:${path}`, text };
32
+ }
28
33
  }
29
34
  } catch {}
30
35
  return null;
@@ -41,12 +46,16 @@ async function defaultLoadContext() {
41
46
  if (!response.ok) {
42
47
  throw new Error(`HTTP ${response.status}`);
43
48
  }
44
- return { source: url, text: await response.text() };
49
+ const text = await response.text();
50
+ if (!isContextMarkdown(text)) {
51
+ throw new Error(`unexpected context payload (not Markdown) from ${url}`);
52
+ }
53
+ return { source: url, text };
45
54
  } catch (error) {
46
55
  lastError = error;
47
56
  }
48
57
  }
49
- throw new Error(`gagal mengambil konteks dari ${CONTEXT_URLS2.join(" dan ")}: ${String(lastError)}`);
58
+ throw new Error(`failed to load context from ${CONTEXT_URLS2.join(" and ")}: ${String(lastError)}`);
50
59
  }
51
60
  function searchMarkdown2(text, query, limit = 20) {
52
61
  const needle = query.trim().toLowerCase();
@@ -106,18 +115,18 @@ function createTools2(options) {
106
115
  scaffoldRoute: async (input) => {
107
116
  const kind = input.kind ?? "page";
108
117
  if (kind !== "page" && kind !== "api") {
109
- throw new Error(`kind tidak dikenal: ${String(kind)}`);
118
+ throw new Error(`unknown kind: ${String(kind)}`);
110
119
  }
111
120
  const route = input.route.trim().replace(/^\/+|\/+$/g, "");
112
121
  if (route.length === 0) {
113
- throw new Error("`route` tidak boleh kosong (contoh: orders/[id])");
122
+ throw new Error("`route` must not be empty (example: orders/[id])");
114
123
  }
115
124
  for (const segment of route.split("/")) {
116
125
  if (segment.length === 0 || segment === "." || segment === "..") {
117
- throw new Error(`segmen route tidak valid: \`${segment}\``);
126
+ throw new Error(`invalid route segment: \`${segment}\``);
118
127
  }
119
128
  if (/[\\<>:"|?*]/.test(segment)) {
120
- throw new Error(`segmen route memuat karakter terlarang: \`${segment}\``);
129
+ throw new Error(`route segment contains forbidden characters: \`${segment}\``);
121
130
  }
122
131
  }
123
132
  const dir = kind === "page" ? "src/routes" : "src/api";
@@ -125,7 +134,7 @@ function createTools2(options) {
125
134
  const file = join(options.cwd, dir, `${route}${ext}`);
126
135
  const relativeFile = relative(options.cwd, file).split(sep).join("/");
127
136
  if (existsSync(file)) {
128
- throw new Error(`file sudah ada: ${relativeFile} (tidak menimpa)`);
137
+ throw new Error(`file already exists: ${relativeFile} (not overwriting)`);
129
138
  }
130
139
  mkdirSync(dirname(file), { recursive: true });
131
140
  const content = kind === "page" ? renderPageRoute(route, input.loader === true) : renderApiRoute(route);
@@ -140,7 +149,7 @@ function createTools2(options) {
140
149
  file: ".moons/dev.log",
141
150
  lines: clamped,
142
151
  exists: false,
143
- text: "belum ada log dev — jalankan `moons dev` dulu"
152
+ text: "no dev log yet — run `moons dev` first"
144
153
  };
145
154
  }
146
155
  const text = readFileSync(file, "utf8");
@@ -159,7 +168,7 @@ function createTools2(options) {
159
168
  function renderPageRoute(route, withLoader) {
160
169
  if (!withLoader) {
161
170
  return [
162
- `// Route: /${route} — dibuat oleh MCP scaffold_route.`,
171
+ `// Route: /${route} — created by MCP scaffold_route.`,
163
172
  "export default function Page() {",
164
173
  ` return <p>/${route}</p>;`,
165
174
  "}",
@@ -168,7 +177,7 @@ function renderPageRoute(route, withLoader) {
168
177
  `);
169
178
  }
170
179
  return [
171
- `// Route: /${route} — dibuat oleh MCP scaffold_route.`,
180
+ `// Route: /${route} — created by MCP scaffold_route.`,
172
181
  'import { useLoaderData } from "@moonsjs/react";',
173
182
  "",
174
183
  "export const loader = async () => ({ items: [] as string[] });",
@@ -183,7 +192,7 @@ function renderPageRoute(route, withLoader) {
183
192
  }
184
193
  function renderApiRoute(route) {
185
194
  return [
186
- `// Endpoint: /api/${route} — dibuat oleh MCP scaffold_route.`,
195
+ `// Endpoint: /api/${route} — created by MCP scaffold_route.`,
187
196
  'import type { ApiHandlerArgs } from "@moonsjs/server";',
188
197
  "",
189
198
  "export async function GET({ params }: ApiHandlerArgs): Promise<Response> {",
@@ -260,4 +269,4 @@ function createMcpServer2(options) {
260
269
 
261
270
  export { CONTEXT_URL2, CONTEXT_URLS2, localContextPath2, searchMarkdown2, createTools2, createMcpServer2 };
262
271
 
263
- //# debugId=A2599D2C96EA6CB864756E2164756E21
272
+ //# debugId=9D4C7C9F7270964764756E2164756E21
@@ -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 executeAudit,\n executeCheck,\n runTypegen,\n type AuditJson,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, relative, sep } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\n export const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md\";\n\n/** Rantai fallback remote: situs docs → GitHub raw (repo docs). */\nexport const CONTEXT_URLS: readonly string[] = [\n \"https://docs.devmoon.net/raw/ai/CONTEXT.md\",\n CONTEXT_URL,\n];\n\n/**\n * Accept only the Markdown context. A redirect or an error page can return\n * HTTP 200 with HTML; that must not become the context an agent searches.\n */\nfunction isContextMarkdown(text: string): boolean {\n return /^#\\s+Moons\\b/.test(text.trimStart());\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 const text = readFileSync(path, \"utf8\");\n if (isContextMarkdown(text)) {\n return { source: `local:${path}`, text };\n }\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 const text = await response.text();\n if (!isContextMarkdown(text)) {\n throw new Error(`unexpected context payload (not Markdown) from ${url}`);\n }\n return { source: url, text };\n } catch (error) {\n lastError = error;\n }\n }\n throw new Error(\n `failed to load context from ${CONTEXT_URLS.join(\" and \")}: ${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 /** Addendum H: audit keamanan (RPC/env/cookie/CSP/dist) machine-readable. */\n audit: () => Promise<AuditJson>;\n /** Addendum H: jalankan typegen (`.moons/*.d.ts`). */\n runTypegen: () => Promise<{ ok: true; outDir: string }>;\n /** Addendum L.4: buat file route mengikuti konvensi §11 (tanpa menimpa). */\n scaffoldRoute: (input: {\n route: string;\n kind?: \"page\" | \"api\";\n loader?: boolean;\n }) => Promise<ScaffoldRouteResult>;\n /** Addendum L.4: baca N baris terakhir `.moons/dev.log` (dikelola `moons dev`). */\n tailDevLogs: (lines: number) => Promise<TailDevLogsResult>;\n}\n\nexport interface ScaffoldRouteResult {\n file: string;\n kind: \"page\" | \"api\";\n created: true;\n}\n\nexport interface TailDevLogsResult {\n file: string;\n lines: number;\n exists: boolean;\n text: string;\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 audit: () => executeAudit(options.cwd, { env: process.env }),\n runTypegen: async () => {\n await runTypegen(options.cwd);\n return { ok: true as const, outDir: \".moons\" };\n },\n scaffoldRoute: async (input) => {\n const kind = input.kind ?? \"page\";\n if (kind !== \"page\" && kind !== \"api\") {\n throw new Error(`unknown kind: ${String(kind)}`);\n }\n const route = input.route.trim().replace(/^\\/+|\\/+$/g, \"\");\n if (route.length === 0) {\n throw new Error(\"`route` must not be empty (example: orders/[id])\");\n }\n for (const segment of route.split(\"/\")) {\n if (segment.length === 0 || segment === \".\" || segment === \"..\") {\n throw new Error(`invalid route segment: \\`${segment}\\``);\n }\n if (/[\\\\<>:\"|?*]/.test(segment)) {\n throw new Error(`route segment contains forbidden characters: \\`${segment}\\``);\n }\n }\n const dir = kind === \"page\" ? \"src/routes\" : \"src/api\";\n const ext = kind === \"page\" ? \".tsx\" : \".ts\";\n const file = join(options.cwd, dir, `${route}${ext}`);\n const relativeFile = relative(options.cwd, file).split(sep).join(\"/\");\n if (existsSync(file)) {\n throw new Error(`file already exists: ${relativeFile} (not overwriting)`);\n }\n mkdirSync(dirname(file), { recursive: true });\n const content =\n kind === \"page\"\n ? renderPageRoute(route, input.loader === true)\n : renderApiRoute(route);\n writeFileSync(file, content);\n return { file: relativeFile, kind, created: true as const };\n },\n tailDevLogs: async (lines) => {\n const clamped = Math.min(Math.max(Math.trunc(lines), 1), 500);\n const file = join(options.cwd, \".moons\", \"dev.log\");\n if (!existsSync(file)) {\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: false,\n text: \"no dev log yet — run `moons dev` first\",\n };\n }\n const text = readFileSync(file, \"utf8\");\n const all = text.length === 0 ? [] : text.replace(/\\n$/, \"\").split(\"\\n\");\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: true,\n text: all.slice(-clamped).join(\"\\n\"),\n };\n },\n };\n}\n\n/** Template route page (§11): default export + loader opsional. */\nfunction renderPageRoute(route: string, withLoader: boolean): string {\n if (!withLoader) {\n return [\n `// Route: /${route} — created by MCP scaffold_route.`,\n \"export default function Page() {\",\n ` return <p>/${route}</p>;`,\n \"}\",\n \"\",\n ].join(\"\\n\");\n }\n return [\n `// Route: /${route} — created by MCP scaffold_route.`,\n 'import { useLoaderData } from \"@moonsjs/react\";',\n \"\",\n \"export const loader = async () => ({ items: [] as string[] });\",\n \"\",\n \"export default function Page() {\",\n \" const data = useLoaderData<typeof loader>();\",\n \" return <p>{data.items.length} item</p>;\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Template endpoint API (Addendum D.1): export method + params. */\nfunction renderApiRoute(route: string): string {\n return [\n `// Endpoint: /api/${route} — created by MCP scaffold_route.`,\n 'import type { ApiHandlerArgs } from \"@moonsjs/server\";',\n \"\",\n \"export async function GET({ params }: ApiHandlerArgs): Promise<Response> {\",\n \" return Response.json({ ok: true, params });\",\n \"}\",\n \"\",\n ].join(\"\\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 \"audit\",\n {\n description:\n \"Security audit: serverFn inventory, env() access map, cookie flags, CSP, and a dist/client scan for env-like secrets (Addendum H).\",\n },\n () => guard(() => tools.audit()),\n );\n\n server.registerTool(\n \"run_typegen\",\n {\n description:\n \"Run typegen: writes .moons/routes.d.ts + .moons/context.d.ts for the app in cwd.\",\n },\n () => guard(() => tools.runTypegen()),\n );\n\n server.registerTool(\n \"scaffold_route\",\n {\n description:\n \"Create a route file following Moons conventions: page → src/routes/<route>.tsx (default export + optional loader), api → src/api/<route>.ts (GET handler). Never overwrites existing files (Addendum L.4).\",\n inputSchema: {\n route: z.string().describe('route path without extension, e.g. \"orders/[id]\"'),\n kind: z.enum([\"page\", \"api\"]).optional().describe(\"default: page\"),\n loader: z\n .boolean()\n .optional()\n .describe(\"page only: include a sample loader + useLoaderData\"),\n },\n },\n ({ route, kind, loader }) =>\n guard(() =>\n tools.scaffoldRoute({\n route,\n ...(kind === undefined ? {} : { kind }),\n ...(loader === undefined ? {} : { loader }),\n }),\n ),\n );\n\n server.registerTool(\n \"tail_dev_logs\",\n {\n description:\n \"Tail the CLI-managed dev log (.moons/dev.log written by `moons dev`); default 50 lines, max 500 (Addendum L.4).\",\n inputSchema: {\n lines: z.number().int().min(1).max(500).optional().describe(\"default 50\"),\n },\n },\n ({ lines }) => guard(() => tools.tailDevLogs(lines ?? 50)),\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": ";AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA;AACA;AACA;AAKQ,IAAM,eACZ;AAGK,IAAM,gBAAkC;AAAA,EAC7C;AAAA,EACA;AACF;AAMA,SAAS,iBAAiB,CAAC,MAAuB;AAAA,EAChD,OAAO,eAAe,KAAK,KAAK,UAAU,CAAC;AAAA;AAItC,SAAS,iBAAgB,CAAC,UAA0B;AAAA,EACzD,OAAO,KAAK,QAAQ,QAAQ,GAAG,MAAM,QAAQ,MAAM,YAAY;AAAA;AAGjE,SAAS,gBAAgB,GAA4C;AAAA,EACnE,IAAI;AAAA,IACF,MAAM,QAAQ,cAAc,YAAY,GAAG,EAAE,QAAQ,cAAc;AAAA,IACnE,MAAM,OAAO,kBAAiB,KAAK;AAAA,IACnC,IAAI,WAAW,IAAI,GAAG;AAAA,MACpB,MAAM,OAAO,aAAa,MAAM,MAAM;AAAA,MACtC,IAAI,kBAAkB,IAAI,GAAG;AAAA,QAC3B,OAAO,EAAE,QAAQ,SAAS,QAAQ,KAAK;AAAA,MACzC;AAAA,IACF;AAAA,IACA,MAAM;AAAA,EAGR,OAAO;AAAA;AAGT,eAAe,kBAAkB,GAA8C;AAAA,EAC7E,MAAM,QAAQ,iBAAiB;AAAA,EAC/B,IAAI,UAAU,MAAM;AAAA,IAClB,OAAO;AAAA,EACT;AAAA,EACA,IAAI,YAAqB;AAAA,EACzB,WAAW,OAAO,eAAc;AAAA,IAC9B,IAAI;AAAA,MACF,MAAM,WAAW,MAAM,MAAM,GAAG;AAAA,MAChC,IAAI,CAAC,SAAS,IAAI;AAAA,QAChB,MAAM,IAAI,MAAM,QAAQ,SAAS,QAAQ;AAAA,MAC3C;AAAA,MACA,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,MACjC,IAAI,CAAC,kBAAkB,IAAI,GAAG;AAAA,QAC5B,MAAM,IAAI,MAAM,kDAAkD,KAAK;AAAA,MACzE;AAAA,MACA,OAAO,EAAE,QAAQ,KAAK,KAAK;AAAA,MAC3B,OAAO,OAAO;AAAA,MACd,YAAY;AAAA;AAAA,EAEhB;AAAA,EACA,MAAM,IAAI,MACR,+BAA+B,cAAa,KAAK,OAAO,MAAM,OAAO,SAAS,GAChF;AAAA;AAgEK,SAAS,eAAc,CAAC,MAAc,OAAe,QAAQ,IAAgB;AAAA,EAClF,MAAM,SAAS,MAAM,KAAK,EAAE,YAAY;AAAA,EACxC,IAAI,OAAO,WAAW,GAAG;AAAA,IACvB,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,QAAQ,KAAK,MAAM;AAAA,CAAI;AAAA,EAC7B,MAAM,UAAsB,CAAC;AAAA,EAC7B,IAAI,UAAU;AAAA,EAEd,SAAS,QAAQ,EAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AAAA,IACpD,MAAM,OAAO,MAAM;AAAA,IACnB,MAAM,UAAU,kBAAkB,KAAK,IAAI;AAAA,IAC3C,IAAI,YAAY,MAAM;AAAA,MACpB,WAAW,QAAQ,MAAM,IAAI,KAAK;AAAA,IACpC;AAAA,IACA,IAAI,KAAK,YAAY,EAAE,SAAS,MAAM,GAAG;AAAA,MACvC,QAAQ,KAAK,EAAE,SAAS,MAAM,QAAQ,GAAG,MAAM,KAAK,KAAK,EAAE,CAAC;AAAA,MAC5D,IAAI,QAAQ,UAAU,OAAO;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAIF,SAAS,YAAW,CAAC,SAAkC;AAAA,EAC5D,MAAM,cACJ,QAAQ,iBAAiB,YACrB,qBACA,aAAwD;AAAA,IACtD,QAAQ;AAAA,IACR,MAAM,MAAO,QAAQ,aAAgC;AAAA,EACvD;AAAA,EACN,IAAI,SAAkD;AAAA,EAEtD,OAAO;AAAA,IACL,YAAY,MAAM,cAAc,QAAQ,GAAG;AAAA,IAC3C,eAAe,MAAM,QAAQ,QAAQ,WAAW,QAAQ,GAAG,CAAC;AAAA,IAC5D,UAAU,MAAM,aAAa,QAAQ,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,IACzD,YAAY,OAAO,UAA6C;AAAA,MAC9D,IAAI,WAAW,MAAM;AAAA,QACnB,SAAS,MAAM,YAAY;AAAA,MAC7B;AAAA,MACA,OAAO,EAAE,QAAQ,OAAO,QAAQ,OAAO,SAAS,gBAAe,OAAO,MAAM,KAAK,EAAE;AAAA;AAAA,IAErF,cAAc,OAAO,SAA8C;AAAA,MACjE,MAAM,aAAa,KAAK,KAAK,EAAE,YAAY;AAAA,MAC3C,MAAM,QAAQ,eAAe,UAAU;AAAA,MACvC,OAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,UAAU;AAAA,WACb,UAAU,YAAY,CAAC,IAAI,EAAE,MAAM;AAAA,QACvC,YAAY,CAAC,GAAG,iBAAiB;AAAA,MACnC;AAAA;AAAA,IAEF,OAAO,MAAM,aAAa,QAAQ,KAAK,EAAE,KAAK,QAAQ,IAAI,CAAC;AAAA,IAC3D,YAAY,YAAY;AAAA,MACtB,MAAM,WAAW,QAAQ,GAAG;AAAA,MAC5B,OAAO,EAAE,IAAI,MAAe,QAAQ,SAAS;AAAA;AAAA,IAE/C,eAAe,OAAO,UAAU;AAAA,MAC9B,MAAM,OAAO,MAAM,QAAQ;AAAA,MAC3B,IAAI,SAAS,UAAU,SAAS,OAAO;AAAA,QACrC,MAAM,IAAI,MAAM,iBAAiB,OAAO,IAAI,GAAG;AAAA,MACjD;AAAA,MACA,MAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,QAAQ,cAAc,EAAE;AAAA,MACzD,IAAI,MAAM,WAAW,GAAG;AAAA,QACtB,MAAM,IAAI,MAAM,kDAAkD;AAAA,MACpE;AAAA,MACA,WAAW,WAAW,MAAM,MAAM,GAAG,GAAG;AAAA,QACtC,IAAI,QAAQ,WAAW,KAAK,YAAY,OAAO,YAAY,MAAM;AAAA,UAC/D,MAAM,IAAI,MAAM,4BAA4B,WAAW;AAAA,QACzD;AAAA,QACA,IAAI,cAAc,KAAK,OAAO,GAAG;AAAA,UAC/B,MAAM,IAAI,MAAM,kDAAkD,WAAW;AAAA,QAC/E;AAAA,MACF;AAAA,MACA,MAAM,MAAM,SAAS,SAAS,eAAe;AAAA,MAC7C,MAAM,MAAM,SAAS,SAAS,SAAS;AAAA,MACvC,MAAM,OAAO,KAAK,QAAQ,KAAK,KAAK,GAAG,QAAQ,KAAK;AAAA,MACpD,MAAM,eAAe,SAAS,QAAQ,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAAA,MACpE,IAAI,WAAW,IAAI,GAAG;AAAA,QACpB,MAAM,IAAI,MAAM,wBAAwB,gCAAgC;AAAA,MAC1E;AAAA,MACA,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,MAC5C,MAAM,UACJ,SAAS,SACL,gBAAgB,OAAO,MAAM,WAAW,IAAI,IAC5C,eAAe,KAAK;AAAA,MAC1B,cAAc,MAAM,OAAO;AAAA,MAC3B,OAAO,EAAE,MAAM,cAAc,MAAM,SAAS,KAAc;AAAA;AAAA,IAE5D,aAAa,OAAO,UAAU;AAAA,MAC5B,MAAM,UAAU,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG;AAAA,MAC5D,MAAM,OAAO,KAAK,QAAQ,KAAK,UAAU,SAAS;AAAA,MAClD,IAAI,CAAC,WAAW,IAAI,GAAG;AAAA,QACrB,OAAO;AAAA,UACL,MAAM;AAAA,UACN,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,MAAM,OAAO,aAAa,MAAM,MAAM;AAAA,MACtC,MAAM,MAAM,KAAK,WAAW,IAAI,CAAC,IAAI,KAAK,QAAQ,OAAO,EAAE,EAAE,MAAM;AAAA,CAAI;AAAA,MACvE,OAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,KAAK;AAAA,CAAI;AAAA,MACrC;AAAA;AAAA,EAEJ;AAAA;AAIF,SAAS,eAAe,CAAC,OAAe,YAA6B;AAAA,EACnE,IAAI,CAAC,YAAY;AAAA,IACf,OAAO;AAAA,MACL,cAAc;AAAA,MACd;AAAA,MACA,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,IACF,EAAE,KAAK;AAAA,CAAI;AAAA,EACb;AAAA,EACA,OAAO;AAAA,IACL,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK;AAAA,CAAI;AAAA;AAIb,SAAS,cAAc,CAAC,OAAuB;AAAA,EAC7C,OAAO;AAAA,IACL,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK;AAAA,CAAI;AAAA;;;ACzSb;AACA;AAGA,SAAS,WAAW,CAAC,OAAgB,UAAU,OAG7C;AAAA,EACA,OAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,EAAE,CAAC;AAAA,OAC5D,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,EACrC;AAAA;AAGF,eAAe,KAAK,CAClB,KACgF;AAAA,EAChF,IAAI;AAAA,IACF,OAAO,YAAY,MAAM,IAAI,CAAC;AAAA,IAC9B,OAAO,OAAO;AAAA,IACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IACrE,OAAO,YAAY,EAAE,IAAI,OAAO,OAAO,QAAQ,GAAG,IAAI;AAAA;AAAA;AAKnD,SAAS,gBAAe,CAAC,SAAoC;AAAA,EAClE,MAAM,QAAQ,aAAY,OAAO;AAAA,EACjC,MAAM,SAAS,IAAI,UAAU,EAAE,MAAM,SAAS,SAAS,QAAQ,CAAC;AAAA,EAEhE,OAAO,aACL,eACA;AAAA,IACE,aACE;AAAA,EACJ,GACA,MAAM,MAAM,MAAM,MAAM,WAAW,CAAC,CACtC;AAAA,EAEA,OAAO,aACL,mBACA;AAAA,IACE,aACE;AAAA,EACJ,GACA,MAAM,MAAM,MAAM,MAAM,cAAc,CAAC,CACzC;AAAA,EAEA,OAAO,aACL,aACA;AAAA,IACE,aACE;AAAA,EACJ,GACA,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC,CACpC;AAAA,EAEA,OAAO,aACL,eACA;AAAA,IACE,aACE;AAAA,IACF,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,wCAAwC,EAAE;AAAA,EACtF,GACA,GAAG,YAAY,MAAM,MAAM,MAAM,WAAW,KAAK,CAAC,CACpD;AAAA,EAEA,OAAO,aACL,SACA;AAAA,IACE,aACE;AAAA,EACJ,GACA,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC,CACjC;AAAA,EAEA,OAAO,aACL,eACA;AAAA,IACE,aACE;AAAA,EACJ,GACA,MAAM,MAAM,MAAM,MAAM,WAAW,CAAC,CACtC;AAAA,EAEA,OAAO,aACL,kBACA;AAAA,IACE,aACE;AAAA,IACF,aAAa;AAAA,MACX,OAAO,EAAE,OAAO,EAAE,SAAS,kDAAkD;AAAA,MAC7E,MAAM,EAAE,KAAK,CAAC,QAAQ,KAAK,CAAC,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MACjE,QAAQ,EACL,QAAQ,EACR,SAAS,EACT,SAAS,oDAAoD;AAAA,IAClE;AAAA,EACF,GACA,GAAG,OAAO,MAAM,aACd,MAAM,MACJ,MAAM,cAAc;AAAA,IAClB;AAAA,OACI,SAAS,YAAY,CAAC,IAAI,EAAE,KAAK;AAAA,OACjC,WAAW,YAAY,CAAC,IAAI,EAAE,OAAO;AAAA,EAC3C,CAAC,CACH,CACJ;AAAA,EAEA,OAAO,aACL,iBACA;AAAA,IACE,aACE;AAAA,IACF,aAAa;AAAA,MACX,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,YAAY;AAAA,IAC1E;AAAA,EACF,GACA,GAAG,YAAY,MAAM,MAAM,MAAM,YAAY,SAAS,EAAE,CAAC,CAC3D;AAAA,EAEA,OAAO,aACL,iBACA;AAAA,IACE,aACE;AAAA,IACF,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,4CAA4C,EAAE;AAAA,EACzF,GACA,GAAG,WAAW,MAAM,MAAM,MAAM,aAAa,IAAI,CAAC,CACpD;AAAA,EAEA,OAAO;AAAA;",
9
+ "debugId": "9D4C7C9F7270964764756E2164756E21",
10
+ "names": []
11
+ }
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  searchMarkdown2,
6
6
  createTools2,
7
7
  createMcpServer2
8
- } from "./index-p83g77ym.js";
8
+ } from "./index-fd4fnsn3.js";
9
9
  export {
10
10
  CONTEXT_URL2 as CONTEXT_URL,
11
11
  CONTEXT_URLS2 as CONTEXT_URLS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonsjs/mcp",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
package/src/tools.ts CHANGED
@@ -20,13 +20,20 @@ export type ContextFetcher = () => Promise<string>;
20
20
  export const CONTEXT_URL =
21
21
  "https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md";
22
22
 
23
- /** Rantai fallback remote: situs docs → pages.dev → GitHub raw (repo docs). */
23
+ /** Rantai fallback remote: situs docs → GitHub raw (repo docs). */
24
24
  export const CONTEXT_URLS: readonly string[] = [
25
25
  "https://docs.devmoon.net/raw/ai/CONTEXT.md",
26
- "https://moonsjs-docs.pages.dev/raw/ai/CONTEXT.md",
27
26
  CONTEXT_URL,
28
27
  ];
29
28
 
29
+ /**
30
+ * Accept only the Markdown context. A redirect or an error page can return
31
+ * HTTP 200 with HTML; that must not become the context an agent searches.
32
+ */
33
+ function isContextMarkdown(text: string): boolean {
34
+ return /^#\s+Moons\b/.test(text.trimStart());
35
+ }
36
+
30
37
  /** Path konteks offline di paket `@moonsjs/cli`, dari entry yang di-resolve. */
31
38
  export function localContextPath(cliEntry: string): string {
32
39
  return join(dirname(cliEntry), "..", "docs", "ai", "CONTEXT.md");
@@ -37,7 +44,10 @@ function readLocalContext(): { source: string; text: string } | null {
37
44
  const entry = createRequire(import.meta.url).resolve("@moonsjs/cli");
38
45
  const path = localContextPath(entry);
39
46
  if (existsSync(path)) {
40
- return { source: `local:${path}`, text: readFileSync(path, "utf8") };
47
+ const text = readFileSync(path, "utf8");
48
+ if (isContextMarkdown(text)) {
49
+ return { source: `local:${path}`, text };
50
+ }
41
51
  }
42
52
  } catch {
43
53
  // fallthrough ke remote
@@ -57,13 +67,17 @@ async function defaultLoadContext(): Promise<{ source: string; text: string }> {
57
67
  if (!response.ok) {
58
68
  throw new Error(`HTTP ${response.status}`);
59
69
  }
60
- return { source: url, text: await response.text() };
70
+ const text = await response.text();
71
+ if (!isContextMarkdown(text)) {
72
+ throw new Error(`unexpected context payload (not Markdown) from ${url}`);
73
+ }
74
+ return { source: url, text };
61
75
  } catch (error) {
62
76
  lastError = error;
63
77
  }
64
78
  }
65
79
  throw new Error(
66
- `gagal mengambil konteks dari ${CONTEXT_URLS.join(" dan ")}: ${String(lastError)}`,
80
+ `failed to load context from ${CONTEXT_URLS.join(" and ")}: ${String(lastError)}`,
67
81
  );
68
82
  }
69
83
 
@@ -194,18 +208,18 @@ export function createTools(options: ToolsetOptions): Toolset {
194
208
  scaffoldRoute: async (input) => {
195
209
  const kind = input.kind ?? "page";
196
210
  if (kind !== "page" && kind !== "api") {
197
- throw new Error(`kind tidak dikenal: ${String(kind)}`);
211
+ throw new Error(`unknown kind: ${String(kind)}`);
198
212
  }
199
213
  const route = input.route.trim().replace(/^\/+|\/+$/g, "");
200
214
  if (route.length === 0) {
201
- throw new Error("`route` tidak boleh kosong (contoh: orders/[id])");
215
+ throw new Error("`route` must not be empty (example: orders/[id])");
202
216
  }
203
217
  for (const segment of route.split("/")) {
204
218
  if (segment.length === 0 || segment === "." || segment === "..") {
205
- throw new Error(`segmen route tidak valid: \`${segment}\``);
219
+ throw new Error(`invalid route segment: \`${segment}\``);
206
220
  }
207
221
  if (/[\\<>:"|?*]/.test(segment)) {
208
- throw new Error(`segmen route memuat karakter terlarang: \`${segment}\``);
222
+ throw new Error(`route segment contains forbidden characters: \`${segment}\``);
209
223
  }
210
224
  }
211
225
  const dir = kind === "page" ? "src/routes" : "src/api";
@@ -213,7 +227,7 @@ export function createTools(options: ToolsetOptions): Toolset {
213
227
  const file = join(options.cwd, dir, `${route}${ext}`);
214
228
  const relativeFile = relative(options.cwd, file).split(sep).join("/");
215
229
  if (existsSync(file)) {
216
- throw new Error(`file sudah ada: ${relativeFile} (tidak menimpa)`);
230
+ throw new Error(`file already exists: ${relativeFile} (not overwriting)`);
217
231
  }
218
232
  mkdirSync(dirname(file), { recursive: true });
219
233
  const content =
@@ -231,7 +245,7 @@ export function createTools(options: ToolsetOptions): Toolset {
231
245
  file: ".moons/dev.log",
232
246
  lines: clamped,
233
247
  exists: false,
234
- text: "belum ada log dev — jalankan `moons dev` dulu",
248
+ text: "no dev log yet — run `moons dev` first",
235
249
  };
236
250
  }
237
251
  const text = readFileSync(file, "utf8");
@@ -250,7 +264,7 @@ export function createTools(options: ToolsetOptions): Toolset {
250
264
  function renderPageRoute(route: string, withLoader: boolean): string {
251
265
  if (!withLoader) {
252
266
  return [
253
- `// Route: /${route} — dibuat oleh MCP scaffold_route.`,
267
+ `// Route: /${route} — created by MCP scaffold_route.`,
254
268
  "export default function Page() {",
255
269
  ` return <p>/${route}</p>;`,
256
270
  "}",
@@ -258,7 +272,7 @@ function renderPageRoute(route: string, withLoader: boolean): string {
258
272
  ].join("\n");
259
273
  }
260
274
  return [
261
- `// Route: /${route} — dibuat oleh MCP scaffold_route.`,
275
+ `// Route: /${route} — created by MCP scaffold_route.`,
262
276
  'import { useLoaderData } from "@moonsjs/react";',
263
277
  "",
264
278
  "export const loader = async () => ({ items: [] as string[] });",
@@ -274,7 +288,7 @@ function renderPageRoute(route: string, withLoader: boolean): string {
274
288
  /** Template endpoint API (Addendum D.1): export method + params. */
275
289
  function renderApiRoute(route: string): string {
276
290
  return [
277
- `// Endpoint: /api/${route} — dibuat oleh MCP scaffold_route.`,
291
+ `// Endpoint: /api/${route} — created by MCP scaffold_route.`,
278
292
  'import type { ApiHandlerArgs } from "@moonsjs/server";',
279
293
  "",
280
294
  "export async function GET({ params }: ApiHandlerArgs): Promise<Response> {",
@@ -1,11 +0,0 @@
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 executeAudit,\n executeCheck,\n runTypegen,\n type AuditJson,\n type CheckJson,\n type FnsJson,\n type RoutesJson,\n} from \"@moonsjs/cli\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, relative, sep } from \"node:path\";\n\n/** Sumber teks konteks framework (default: lokal dulu, lalu GitHub raw). */\nexport type ContextFetcher = () => Promise<string>;\n\n export const CONTEXT_URL =\n \"https://raw.githubusercontent.com/moonsjs/docs/main/ai/CONTEXT.md\";\n\n/** Rantai fallback remote: situs docs → pages.dev → GitHub raw (repo docs). */\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 /** Addendum H: audit keamanan (RPC/env/cookie/CSP/dist) machine-readable. */\n audit: () => Promise<AuditJson>;\n /** Addendum H: jalankan typegen (`.moons/*.d.ts`). */\n runTypegen: () => Promise<{ ok: true; outDir: string }>;\n /** Addendum L.4: buat file route mengikuti konvensi §11 (tanpa menimpa). */\n scaffoldRoute: (input: {\n route: string;\n kind?: \"page\" | \"api\";\n loader?: boolean;\n }) => Promise<ScaffoldRouteResult>;\n /** Addendum L.4: baca N baris terakhir `.moons/dev.log` (dikelola `moons dev`). */\n tailDevLogs: (lines: number) => Promise<TailDevLogsResult>;\n}\n\nexport interface ScaffoldRouteResult {\n file: string;\n kind: \"page\" | \"api\";\n created: true;\n}\n\nexport interface TailDevLogsResult {\n file: string;\n lines: number;\n exists: boolean;\n text: string;\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 audit: () => executeAudit(options.cwd, { env: process.env }),\n runTypegen: async () => {\n await runTypegen(options.cwd);\n return { ok: true as const, outDir: \".moons\" };\n },\n scaffoldRoute: async (input) => {\n const kind = input.kind ?? \"page\";\n if (kind !== \"page\" && kind !== \"api\") {\n throw new Error(`kind tidak dikenal: ${String(kind)}`);\n }\n const route = input.route.trim().replace(/^\\/+|\\/+$/g, \"\");\n if (route.length === 0) {\n throw new Error(\"`route` tidak boleh kosong (contoh: orders/[id])\");\n }\n for (const segment of route.split(\"/\")) {\n if (segment.length === 0 || segment === \".\" || segment === \"..\") {\n throw new Error(`segmen route tidak valid: \\`${segment}\\``);\n }\n if (/[\\\\<>:\"|?*]/.test(segment)) {\n throw new Error(`segmen route memuat karakter terlarang: \\`${segment}\\``);\n }\n }\n const dir = kind === \"page\" ? \"src/routes\" : \"src/api\";\n const ext = kind === \"page\" ? \".tsx\" : \".ts\";\n const file = join(options.cwd, dir, `${route}${ext}`);\n const relativeFile = relative(options.cwd, file).split(sep).join(\"/\");\n if (existsSync(file)) {\n throw new Error(`file sudah ada: ${relativeFile} (tidak menimpa)`);\n }\n mkdirSync(dirname(file), { recursive: true });\n const content =\n kind === \"page\"\n ? renderPageRoute(route, input.loader === true)\n : renderApiRoute(route);\n writeFileSync(file, content);\n return { file: relativeFile, kind, created: true as const };\n },\n tailDevLogs: async (lines) => {\n const clamped = Math.min(Math.max(Math.trunc(lines), 1), 500);\n const file = join(options.cwd, \".moons\", \"dev.log\");\n if (!existsSync(file)) {\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: false,\n text: \"belum ada log dev — jalankan `moons dev` dulu\",\n };\n }\n const text = readFileSync(file, \"utf8\");\n const all = text.length === 0 ? [] : text.replace(/\\n$/, \"\").split(\"\\n\");\n return {\n file: \".moons/dev.log\",\n lines: clamped,\n exists: true,\n text: all.slice(-clamped).join(\"\\n\"),\n };\n },\n };\n}\n\n/** Template route page (§11): default export + loader opsional. */\nfunction renderPageRoute(route: string, withLoader: boolean): string {\n if (!withLoader) {\n return [\n `// Route: /${route} — dibuat oleh MCP scaffold_route.`,\n \"export default function Page() {\",\n ` return <p>/${route}</p>;`,\n \"}\",\n \"\",\n ].join(\"\\n\");\n }\n return [\n `// Route: /${route} — dibuat oleh MCP scaffold_route.`,\n 'import { useLoaderData } from \"@moonsjs/react\";',\n \"\",\n \"export const loader = async () => ({ items: [] as string[] });\",\n \"\",\n \"export default function Page() {\",\n \" const data = useLoaderData<typeof loader>();\",\n \" return <p>{data.items.length} item</p>;\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Template endpoint API (Addendum D.1): export method + params. */\nfunction renderApiRoute(route: string): string {\n return [\n `// Endpoint: /api/${route} — dibuat oleh MCP scaffold_route.`,\n 'import type { ApiHandlerArgs } from \"@moonsjs/server\";',\n \"\",\n \"export async function GET({ params }: ApiHandlerArgs): Promise<Response> {\",\n \" return Response.json({ ok: true, params });\",\n \"}\",\n \"\",\n ].join(\"\\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 \"audit\",\n {\n description:\n \"Security audit: serverFn inventory, env() access map, cookie flags, CSP, and a dist/client scan for env-like secrets (Addendum H).\",\n },\n () => guard(() => tools.audit()),\n );\n\n server.registerTool(\n \"run_typegen\",\n {\n description:\n \"Run typegen: writes .moons/routes.d.ts + .moons/context.d.ts for the app in cwd.\",\n },\n () => guard(() => tools.runTypegen()),\n );\n\n server.registerTool(\n \"scaffold_route\",\n {\n description:\n \"Create a route file following Moons conventions: page → src/routes/<route>.tsx (default export + optional loader), api → src/api/<route>.ts (GET handler). Never overwrites existing files (Addendum L.4).\",\n inputSchema: {\n route: z.string().describe('route path without extension, e.g. \"orders/[id]\"'),\n kind: z.enum([\"page\", \"api\"]).optional().describe(\"default: page\"),\n loader: z\n .boolean()\n .optional()\n .describe(\"page only: include a sample loader + useLoaderData\"),\n },\n },\n ({ route, kind, loader }) =>\n guard(() =>\n tools.scaffoldRoute({\n route,\n ...(kind === undefined ? {} : { kind }),\n ...(loader === undefined ? {} : { loader }),\n }),\n ),\n );\n\n server.registerTool(\n \"tail_dev_logs\",\n {\n description:\n \"Tail the CLI-managed dev log (.moons/dev.log written by `moons dev`); default 50 lines, max 500 (Addendum L.4).\",\n inputSchema: {\n lines: z.number().int().min(1).max(500).optional().describe(\"default 50\"),\n },\n },\n ({ lines }) => guard(() => tools.tailDevLogs(lines ?? 50)),\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": "A2599D2C96EA6CB864756E2164756E21",
10
- "names": []
11
- }