@kumikijs/mcp 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/README.ja.md +41 -0
- package/README.md +41 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +16 -0
- package/dist/src-CUWuO58x.js +294 -0
- package/package.json +64 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @kumikijs/mcp
|
|
2
|
+
|
|
3
|
+
[English](./README.md) · 日本語
|
|
4
|
+
|
|
5
|
+
Kumiki コンパイラと AI 編集ツールチェインを **MCP(Model Context Protocol)サーバー**として公開する。エディタや AI エージェントから、Kumiki プログラムの検査・ビルド・ナビゲーション・編集・仕様参照を行える。
|
|
6
|
+
|
|
7
|
+
## ツール
|
|
8
|
+
|
|
9
|
+
| ツール | 用途 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `kumiki_check` | `source` または `path` をパース + 型検査し、診断を返す |
|
|
12
|
+
| `kumiki_build` | 自己完結 JS モジュールへコンパイル(runtime インライン) |
|
|
13
|
+
| `kumiki_smoke` | headless DOM に mount して UI を操作し、ランタイム例外・空描画・未処理 rejection を検出(check/build では捕まらない層) |
|
|
14
|
+
| `kumiki_run_scenario` | シナリオ(操作列 + 状態アサーション)でアプリを駆動し、毎ステップの slot 状態・DOM・エラー・emit を trace として返す。人を介さない生成→実行→観測→修正ループの土台 |
|
|
15
|
+
| `kumiki_list` | ファイル内の定義一覧(layer で絞り込み可) |
|
|
16
|
+
| `kumiki_view` | 定義 1 件の表示(`withDeps` で依存も) |
|
|
17
|
+
| `kumiki_refs` | 定義への参照箇所を検索 |
|
|
18
|
+
| `kumiki_add` / `kumiki_replace` / `kumiki_remove` / `kumiki_rename` | 定義の編集 |
|
|
19
|
+
| `kumiki_fix` | 修復可能な診断の自動パッチ案を提示 |
|
|
20
|
+
| `kumiki_spec_search` / `kumiki_spec_list` / `kumiki_spec_get` | 正規仕様(spec/)の検索・一覧・取得 |
|
|
21
|
+
|
|
22
|
+
## 起動
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm --filter @kumikijs/mcp start
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
MCP クライアント(例: Claude Code)の設定例:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"kumiki": {
|
|
34
|
+
"command": "node",
|
|
35
|
+
"args": ["--import", "tsx", "packages/mcp/src/server.ts"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`spec/` の場所は通常リポジトリ構成から自動解決される。別配置の場合は環境変数 `KUMIKI_SPEC_DIR` で上書きする。
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @kumikijs/mcp
|
|
2
|
+
|
|
3
|
+
English · [日本語](./README.ja.md)
|
|
4
|
+
|
|
5
|
+
Exposes the Kumiki compiler and the AI editing toolchain as an **MCP (Model Context Protocol) server**. From editors or AI agents, you can check, build, navigate, edit, and look up the spec for Kumiki programs.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Purpose |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `kumiki_check` | Parse + type-check `source` or `path` and return diagnostics |
|
|
12
|
+
| `kumiki_build` | Compile to a self-contained JS module (runtime inlined) |
|
|
13
|
+
| `kumiki_smoke` | Mount to a headless DOM and operate the UI to detect runtime exceptions, empty rendering, and unhandled rejections (the layer check/build don't catch) |
|
|
14
|
+
| `kumiki_run_scenario` | Drive the app with a scenario (operation sequence + state assertions), returning per-step slot state, DOM, errors, and emits as a trace. The substrate for the human-free generate → run → observe → fix loop |
|
|
15
|
+
| `kumiki_list` | List definitions in a file (filterable by layer) |
|
|
16
|
+
| `kumiki_view` | Show a single definition (with dependencies via `withDeps`) |
|
|
17
|
+
| `kumiki_refs` | Search for references to a definition |
|
|
18
|
+
| `kumiki_add` / `kumiki_replace` / `kumiki_remove` / `kumiki_rename` | Edit definitions |
|
|
19
|
+
| `kumiki_fix` | Propose auto-patches for fixable diagnostics |
|
|
20
|
+
| `kumiki_spec_search` / `kumiki_spec_list` / `kumiki_spec_get` | Search, list, and retrieve the normative spec (spec/) |
|
|
21
|
+
|
|
22
|
+
## Startup
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm --filter @kumikijs/mcp start
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Example configuration for an MCP client (e.g. Claude Code):
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"kumiki": {
|
|
34
|
+
"command": "node",
|
|
35
|
+
"args": ["--import", "tsx", "packages/mcp/src/server.ts"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The location of `spec/` is auto-resolved from the usual repository layout. For a different location, override it with the `KUMIKI_SPEC_DIR` environment variable.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as createServer } from "./src-CUWuO58x.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
//#region src/server.ts
|
|
5
|
+
async function main() {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
const transport = new StdioServerTransport();
|
|
8
|
+
await server.connect(transport);
|
|
9
|
+
console.error("Kumiki MCP server running on stdio");
|
|
10
|
+
}
|
|
11
|
+
main().catch((err) => {
|
|
12
|
+
console.error("Fatal error starting Kumiki MCP server:", err);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { addDef, findReferences, listDefs, load, planFixes, removeDef, renameDef, replaceDef, runScenarioSource, smokeSource, viewDef, viewWithDeps } from "@kumikijs/cli";
|
|
5
|
+
import { check, compile, lex, parse } from "@kumikijs/compiler";
|
|
6
|
+
import { nodeRuntimeBundleReader } from "@kumikijs/compiler/node";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/spec.ts
|
|
10
|
+
/** Resolve the repo's spec/ directory. Override with KUMIKI_SPEC_DIR. */
|
|
11
|
+
function specDir() {
|
|
12
|
+
const env = process.env.KUMIKI_SPEC_DIR;
|
|
13
|
+
if (env) return env;
|
|
14
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "spec");
|
|
15
|
+
}
|
|
16
|
+
function listSpecDocs() {
|
|
17
|
+
const dir = specDir();
|
|
18
|
+
if (!existsSync(dir)) return [];
|
|
19
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
20
|
+
}
|
|
21
|
+
function getSpecDoc(name) {
|
|
22
|
+
const path = join(specDir(), name.endsWith(".md") ? name : `${name}.md`);
|
|
23
|
+
if (!existsSync(path)) return null;
|
|
24
|
+
return readFileSync(path, "utf8");
|
|
25
|
+
}
|
|
26
|
+
function searchSpec(query, maxHits = 40) {
|
|
27
|
+
const dir = specDir();
|
|
28
|
+
const needle = query.toLowerCase();
|
|
29
|
+
const hits = [];
|
|
30
|
+
for (const doc of listSpecDocs()) {
|
|
31
|
+
const lines = readFileSync(join(dir, doc), "utf8").split(/\r?\n/);
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i] ?? "";
|
|
34
|
+
if (line.toLowerCase().includes(needle)) {
|
|
35
|
+
hits.push({
|
|
36
|
+
doc,
|
|
37
|
+
line: i + 1,
|
|
38
|
+
text: line.trim()
|
|
39
|
+
});
|
|
40
|
+
if (hits.length >= maxHits) return hits;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return hits;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/index.ts
|
|
48
|
+
function text(s) {
|
|
49
|
+
return { content: [{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: s
|
|
52
|
+
}] };
|
|
53
|
+
}
|
|
54
|
+
function readSource(input) {
|
|
55
|
+
if (typeof input.source === "string") return input.source;
|
|
56
|
+
if (input.path) return readFileSync(resolve(process.cwd(), input.path), "utf8");
|
|
57
|
+
throw new Error("provide either `source` or `path`");
|
|
58
|
+
}
|
|
59
|
+
function toDiagnostics(errors) {
|
|
60
|
+
return errors.map((e) => ({
|
|
61
|
+
code: e.code,
|
|
62
|
+
kind: e.kind,
|
|
63
|
+
message: e.message,
|
|
64
|
+
line: e.pos.line,
|
|
65
|
+
col: e.pos.col
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
/** Parse + typecheck, normalizing parse exceptions into a single diagnostic. */
|
|
69
|
+
function validate(source) {
|
|
70
|
+
try {
|
|
71
|
+
const errors = check(parse(lex(source)));
|
|
72
|
+
return {
|
|
73
|
+
ok: errors.length === 0,
|
|
74
|
+
diagnostics: toDiagnostics(errors)
|
|
75
|
+
};
|
|
76
|
+
} catch (e) {
|
|
77
|
+
const pe = e;
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
diagnostics: [{
|
|
81
|
+
code: "E0000",
|
|
82
|
+
kind: "parse-error",
|
|
83
|
+
message: pe.message ?? String(e),
|
|
84
|
+
line: pe.pos?.line ?? 0,
|
|
85
|
+
col: pe.pos?.col ?? 0
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function createServer() {
|
|
91
|
+
const server = new McpServer({
|
|
92
|
+
name: "kumiki",
|
|
93
|
+
version: "0.1.0"
|
|
94
|
+
});
|
|
95
|
+
server.registerTool("kumiki_check", {
|
|
96
|
+
title: "Check Kumiki source",
|
|
97
|
+
description: "Parse and typecheck a Kumiki program. Pass `source` (text) or `path` (file). Returns ok or a list of diagnostics with codes (see spec/errors.md).",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
source: z.string().optional().describe("Full Kumiki source text"),
|
|
100
|
+
path: z.string().optional().describe("Path to a .kumiki file (relative to cwd)")
|
|
101
|
+
}
|
|
102
|
+
}, async (input) => {
|
|
103
|
+
const result = validate(readSource(input));
|
|
104
|
+
if (result.ok) return text("ok — no diagnostics");
|
|
105
|
+
return text(JSON.stringify(result.diagnostics, null, 2));
|
|
106
|
+
});
|
|
107
|
+
server.registerTool("kumiki_build", {
|
|
108
|
+
title: "Build Kumiki source",
|
|
109
|
+
description: "Compile a Kumiki program to a self-contained JS module (runtime inlined). Pass `source` or `path`. Returns the generated JS, or diagnostics on failure.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
source: z.string().optional(),
|
|
112
|
+
path: z.string().optional(),
|
|
113
|
+
includeJs: z.boolean().optional().describe("Return full JS (default: only a summary)")
|
|
114
|
+
}
|
|
115
|
+
}, async (input) => {
|
|
116
|
+
const result = compile(readSource(input), {
|
|
117
|
+
runtimeSpecifier: "./runtime.js",
|
|
118
|
+
bundle: true,
|
|
119
|
+
readRuntimeBundle: nodeRuntimeBundleReader
|
|
120
|
+
});
|
|
121
|
+
if (result.kind === "fail") return text(`build failed:\n${JSON.stringify(toDiagnostics(result.errors), null, 2)}`);
|
|
122
|
+
if (input.includeJs) return text(result.js);
|
|
123
|
+
return text(`build ok — ${result.js.length} bytes of JS (pass includeJs=true for the source)`);
|
|
124
|
+
});
|
|
125
|
+
server.registerTool("kumiki_smoke", {
|
|
126
|
+
title: "Runtime smoke test",
|
|
127
|
+
description: "Mount a Kumiki program in a headless DOM, exercise its UI, and report runtime failures that check/build cannot catch (throws, empty render, unhandled rejections). Pass `source` or `path`. Run this after check/build — a program can compile yet error or render nothing when actually used.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
source: z.string().optional(),
|
|
130
|
+
path: z.string().optional()
|
|
131
|
+
}
|
|
132
|
+
}, async (input) => {
|
|
133
|
+
const report = await smokeSource(readSource(input));
|
|
134
|
+
if (report.ok) return text(`ok — mounted, rendered, ${report.interactions} interaction(s), no runtime errors`);
|
|
135
|
+
const lines = report.issues.map((i) => `[${i.phase}] ${i.message}${i.trigger ? ` (on ${i.trigger})` : ""}`);
|
|
136
|
+
return text(`runtime smoke failed (mounted=${report.mounted}, rendered=${report.rendered}):\n${lines.join("\n")}`);
|
|
137
|
+
});
|
|
138
|
+
server.registerTool("kumiki_run_scenario", {
|
|
139
|
+
title: "Run a scenario",
|
|
140
|
+
description: "Drive a Kumiki app through a scenario and return a per-step trace (slot state, DOM text, errors, emitted effects) plus assertion results. This is the substrate for an autonomous generate→run→observe→fix loop: write the user's requirements as scenario steps with `expect` assertions on state, run, read the trace, and patch without a human operating the app.\n\nScenario shape: { steps: [{ label?, do?, expect? }], effects?: { <name>: [{outcome, value}] } }. An action `do` is one of: {dispatch, payload?}, {clickText}, {click}, {fill, value}, {choose, value}, {navigate}. An `expect` is { noErrors?, state?: {slot: value}, domIncludes?: [..], domExcludes?: [..] } (state uses partial match; keys may be dotted paths).",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
source: z.string().optional(),
|
|
143
|
+
path: z.string().optional(),
|
|
144
|
+
scenario: z.object({
|
|
145
|
+
steps: z.array(z.record(z.string(), z.unknown())),
|
|
146
|
+
effects: z.record(z.string(), z.array(z.record(z.string(), z.unknown()))).optional(),
|
|
147
|
+
defaultEffect: z.record(z.string(), z.unknown()).optional()
|
|
148
|
+
}).describe("The scenario to run")
|
|
149
|
+
}
|
|
150
|
+
}, async (input) => {
|
|
151
|
+
const report = await runScenarioSource(readSource(input), input.scenario);
|
|
152
|
+
const lines = report.steps.map((s, i) => {
|
|
153
|
+
const status = s.errors.length === 0 && s.failures.length === 0 ? "ok" : "FAIL";
|
|
154
|
+
const head = `step ${i}${s.label ? ` (${s.label})` : ""}${s.action ? `: ${s.action}` : ""}`;
|
|
155
|
+
const sub = [...s.errors.map((e) => ` error: ${e}`), ...s.failures.map((f) => ` assert: ${f}`)];
|
|
156
|
+
const emits = s.emits.length ? ` emits: ${s.emits.map((e) => e.effect).join(", ")}` : "";
|
|
157
|
+
return [
|
|
158
|
+
`[${status}] ${head}`,
|
|
159
|
+
...sub,
|
|
160
|
+
emits
|
|
161
|
+
].filter(Boolean).join("\n");
|
|
162
|
+
});
|
|
163
|
+
const tail = report.ok ? "scenario passed" : "scenario FAILED";
|
|
164
|
+
const finalState = report.steps.at(-1)?.state ?? {};
|
|
165
|
+
return text(`${lines.join("\n")}\n\n${tail}\nfinal state: ${JSON.stringify(finalState)}`);
|
|
166
|
+
});
|
|
167
|
+
server.registerTool("kumiki_list", {
|
|
168
|
+
title: "List definitions",
|
|
169
|
+
description: "List the definitions in a .kumiki file, optionally filtered by layer.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
path: z.string().describe("Path to a .kumiki file"),
|
|
172
|
+
layer: z.enum([
|
|
173
|
+
"type",
|
|
174
|
+
"slot",
|
|
175
|
+
"effect",
|
|
176
|
+
"reducer",
|
|
177
|
+
"tile",
|
|
178
|
+
"fn",
|
|
179
|
+
"app",
|
|
180
|
+
"theme"
|
|
181
|
+
]).optional()
|
|
182
|
+
}
|
|
183
|
+
}, async ({ path, layer }) => {
|
|
184
|
+
return text(listDefs(load(resolve(process.cwd(), path)), layer).map((e) => `${e.layer}.${e.name} (lines ${e.range.startLine}-${e.range.endLine})`).join("\n") || "(no definitions)");
|
|
185
|
+
});
|
|
186
|
+
server.registerTool("kumiki_view", {
|
|
187
|
+
title: "View a definition",
|
|
188
|
+
description: "Show the source of one definition (`<layer>.<name>`), optionally with its dependencies.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
path: z.string(),
|
|
191
|
+
name: z.string().describe("Qualified name, e.g. tile.App or reducer.addTodo"),
|
|
192
|
+
withDeps: z.boolean().optional()
|
|
193
|
+
}
|
|
194
|
+
}, async ({ path, name, withDeps }) => {
|
|
195
|
+
const store = load(resolve(process.cwd(), path));
|
|
196
|
+
return text((withDeps ? viewWithDeps(store, name) : viewDef(store, name)) ?? `not found: ${name}`);
|
|
197
|
+
});
|
|
198
|
+
server.registerTool("kumiki_refs", {
|
|
199
|
+
title: "Find references",
|
|
200
|
+
description: "Find all sites that reference a definition.",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
path: z.string(),
|
|
203
|
+
name: z.string()
|
|
204
|
+
}
|
|
205
|
+
}, async ({ path, name }) => {
|
|
206
|
+
return text(findReferences(load(resolve(process.cwd(), path)), name).map((r) => `${r.qname} @ line ${r.line}`).join("\n") || "(no references)");
|
|
207
|
+
});
|
|
208
|
+
server.registerTool("kumiki_add", {
|
|
209
|
+
title: "Add a definition",
|
|
210
|
+
description: "Append a new definition to a .kumiki file.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
path: z.string(),
|
|
213
|
+
layer: z.enum([
|
|
214
|
+
"type",
|
|
215
|
+
"slot",
|
|
216
|
+
"effect",
|
|
217
|
+
"reducer",
|
|
218
|
+
"tile",
|
|
219
|
+
"fn",
|
|
220
|
+
"app",
|
|
221
|
+
"theme"
|
|
222
|
+
]),
|
|
223
|
+
name: z.string(),
|
|
224
|
+
body: z.string().describe("The definition body (without the `<layer> <name>` prefix)")
|
|
225
|
+
}
|
|
226
|
+
}, async ({ path, layer, name, body }) => {
|
|
227
|
+
addDef(resolve(process.cwd(), path), layer, name, body);
|
|
228
|
+
return text(`added ${layer}.${name}`);
|
|
229
|
+
});
|
|
230
|
+
server.registerTool("kumiki_replace", {
|
|
231
|
+
title: "Replace a definition",
|
|
232
|
+
description: "Replace the body of an existing definition.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
path: z.string(),
|
|
235
|
+
name: z.string(),
|
|
236
|
+
body: z.string()
|
|
237
|
+
}
|
|
238
|
+
}, async ({ path, name, body }) => {
|
|
239
|
+
replaceDef(resolve(process.cwd(), path), name, body);
|
|
240
|
+
return text(`replaced ${name}`);
|
|
241
|
+
});
|
|
242
|
+
server.registerTool("kumiki_remove", {
|
|
243
|
+
title: "Remove a definition",
|
|
244
|
+
description: "Remove a definition. Set cascade=true to also remove definitions that only it referenced.",
|
|
245
|
+
inputSchema: {
|
|
246
|
+
path: z.string(),
|
|
247
|
+
name: z.string(),
|
|
248
|
+
cascade: z.boolean().optional()
|
|
249
|
+
}
|
|
250
|
+
}, async ({ path, name, cascade }) => {
|
|
251
|
+
removeDef(resolve(process.cwd(), path), name, cascade ?? false);
|
|
252
|
+
return text(`removed ${name}`);
|
|
253
|
+
});
|
|
254
|
+
server.registerTool("kumiki_rename", {
|
|
255
|
+
title: "Rename a definition",
|
|
256
|
+
description: "Rename a definition and update all references.",
|
|
257
|
+
inputSchema: {
|
|
258
|
+
path: z.string(),
|
|
259
|
+
name: z.string(),
|
|
260
|
+
newName: z.string()
|
|
261
|
+
}
|
|
262
|
+
}, async ({ path, name, newName }) => {
|
|
263
|
+
renameDef(resolve(process.cwd(), path), name, newName);
|
|
264
|
+
return text(`renamed ${name} -> ${newName}`);
|
|
265
|
+
});
|
|
266
|
+
server.registerTool("kumiki_fix", {
|
|
267
|
+
title: "Plan auto-fixes",
|
|
268
|
+
description: "Typecheck a file and propose auto-patches for repairable errors (e.g. misspelled names). Returns the planned fixes; this tool does not write to disk.",
|
|
269
|
+
inputSchema: { path: z.string() }
|
|
270
|
+
}, async ({ path }) => {
|
|
271
|
+
const store = load(resolve(process.cwd(), path));
|
|
272
|
+
return text(planFixes(store, check(store.program)).map((p) => `${p.code}: ${p.description}`).join("\n") || "(no auto-fixable diagnostics)");
|
|
273
|
+
});
|
|
274
|
+
server.registerTool("kumiki_spec_search", {
|
|
275
|
+
title: "Search the spec",
|
|
276
|
+
description: "Keyword search across the normative spec/ documents. Returns doc:line matches.",
|
|
277
|
+
inputSchema: { query: z.string() }
|
|
278
|
+
}, async ({ query }) => {
|
|
279
|
+
return text(searchSpec(query).map((h) => `${h.doc}:${h.line} ${h.text}`).join("\n") || `(no matches for "${query}")`);
|
|
280
|
+
});
|
|
281
|
+
server.registerTool("kumiki_spec_list", {
|
|
282
|
+
title: "List spec documents",
|
|
283
|
+
description: "List the available normative spec/ documents.",
|
|
284
|
+
inputSchema: {}
|
|
285
|
+
}, async () => text(listSpecDocs().join("\n") || "(spec/ not found)"));
|
|
286
|
+
server.registerTool("kumiki_spec_get", {
|
|
287
|
+
title: "Get a spec document",
|
|
288
|
+
description: "Fetch the full text of one spec document (e.g. 'language' or 'errors.md').",
|
|
289
|
+
inputSchema: { doc: z.string() }
|
|
290
|
+
}, async ({ doc }) => text(getSpecDoc(doc) ?? `not found: ${doc}`));
|
|
291
|
+
return server;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
export { createServer as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kumikijs/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Kumiki MCP server — exposes the compiler (check/build/list/view/add/replace/remove/fix) and spec search as MCP tools.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"author": "kage1020",
|
|
8
|
+
"homepage": "https://github.com/kage1020/Kumiki/tree/main/packages/mcp#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/kage1020/Kumiki.git",
|
|
12
|
+
"directory": "packages/mcp"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/kage1020/Kumiki/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"kumiki",
|
|
17
|
+
"ai-first",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"compiler"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"kumiki-mcp": "./dist/server.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./src/index.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"provenance": true,
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"import": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
|
+
"lint": "biome check src",
|
|
51
|
+
"start": "tsx src/server.ts"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
+
"@kumikijs/cli": "workspace:*",
|
|
56
|
+
"@kumikijs/compiler": "workspace:*",
|
|
57
|
+
"zod": "^4.4.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "catalog:",
|
|
61
|
+
"tsx": "catalog:",
|
|
62
|
+
"typescript": "catalog:"
|
|
63
|
+
}
|
|
64
|
+
}
|