mcp-probe-kit 3.0.5 → 3.0.7
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 +458 -431
- package/build/index.js +875 -141
- package/build/lib/gitnexus-bridge.d.ts +58 -0
- package/build/lib/gitnexus-bridge.js +379 -0
- package/build/lib/template-loader.js +317 -317
- package/build/lib/tool-execution-context.d.ts +8 -0
- package/build/lib/tool-execution-context.js +20 -0
- package/build/lib/toolset-manager.d.ts +1 -1
- package/build/lib/toolset-manager.js +7 -5
- package/build/schemas/code-analysis-tools.d.ts +46 -0
- package/build/schemas/code-analysis-tools.js +47 -0
- package/build/schemas/git-tools.js +16 -16
- package/build/schemas/index.d.ts +46 -0
- package/build/tools/__tests__/code_insight.unit.test.d.ts +1 -0
- package/build/tools/__tests__/code_insight.unit.test.js +35 -0
- package/build/tools/__tests__/start_bugfix.unit.test.js +14 -14
- package/build/tools/__tests__/start_ui.unit.test.js +11 -11
- package/build/tools/add_feature.js +79 -79
- package/build/tools/ask_user.js +5 -5
- package/build/tools/code_insight.d.ts +8 -0
- package/build/tools/code_insight.js +129 -0
- package/build/tools/index.d.ts +1 -0
- package/build/tools/index.js +1 -0
- package/build/tools/interview.js +9 -9
- package/build/tools/start_bugfix.d.ts +2 -1
- package/build/tools/start_bugfix.js +170 -126
- package/build/tools/start_feature.d.ts +2 -1
- package/build/tools/start_feature.js +156 -112
- package/build/tools/start_onboard.d.ts +2 -1
- package/build/tools/start_onboard.js +57 -51
- package/build/tools/start_product.d.ts +2 -1
- package/build/tools/start_product.js +9 -1
- package/build/tools/start_ralph.d.ts +2 -1
- package/build/tools/start_ralph.js +9 -3
- package/build/tools/start_ui.d.ts +2 -1
- package/build/tools/start_ui.js +102 -88
- package/build/tools/ui-ux-tools.d.ts +2 -1
- package/build/tools/ui-ux-tools.js +19 -3
- package/build/utils/ui-sync.d.ts +6 -2
- package/build/utils/ui-sync.js +125 -29
- package/docs/assets/font/MaterialSymbolsOutlined.codepoints +4102 -0
- package/docs/assets/font/MaterialSymbolsOutlined.ttf +0 -0
- package/docs/assets/font/noto-sans-sc-400.ttf +0 -0
- package/docs/assets/font/noto-sans-sc-700.ttf +0 -0
- package/docs/assets/font/noto-sans-sc-900.ttf +0 -0
- package/docs/assets/js/i18n.js +122 -21
- package/docs/assets/js/tailwind.js +83 -83
- package/docs/data/tools.js +419 -399
- package/docs/debug-i18n.html +163 -0
- package/docs/i18n/all-tools/en.json +157 -0
- package/docs/i18n/all-tools/ja.json +157 -0
- package/docs/i18n/all-tools/ko.json +157 -0
- package/docs/i18n/all-tools/zh-CN.json +157 -0
- package/docs/pages/all-tools.html +514 -352
- package/docs/pages/examples.html +689 -689
- package/docs/pages/getting-started.html +589 -589
- package/docs/pages/migration.html +298 -298
- package/docs/specs/user-auth/design.md +82 -0
- package/docs/specs/user-auth/requirements.md +52 -0
- package/docs/specs/user-auth/tasks.md +55 -0
- package/package.json +5 -5
- package/docs/project-context/architecture.md +0 -0
- package/docs/project-context/how-to-develop.md +0 -313
- package/docs/project-context/how-to-test.md +0 -457
- package/docs/project-context/tech-stack.md +0 -96
- package/docs/project-context.md +0 -53
- package/docs/specs/git-work-report/design.md +0 -568
- package/docs/specs/git-work-report/requirements.md +0 -131
- package/docs/specs/git-work-report/tasks.md +0 -197
package/build/index.js
CHANGED
|
@@ -1,20 +1,408 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from "@modelcontextprotocol/sdk/experimental/index.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ProgressNotificationSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport } from "./tools/index.js";
|
|
6
9
|
import { VERSION, NAME } from "./version.js";
|
|
7
10
|
import { allToolSchemas } from "./schemas/index.js";
|
|
8
11
|
import { filterTools, getToolsetFromEnv } from "./lib/toolset-manager.js";
|
|
12
|
+
import { isAbortError, } from "./lib/tool-execution-context.js";
|
|
13
|
+
const EXTENSIONS_CAPABILITY_KEY = "io.github.mybolide/extensions";
|
|
14
|
+
const MAX_UI_APP_RESOURCES = 30;
|
|
15
|
+
const MAX_GRAPH_SNAPSHOTS = 20;
|
|
16
|
+
const DEFAULT_GRAPH_SNAPSHOT_DIR = path.resolve(process.cwd(), ".mcp-probe-kit", "graph-snapshots");
|
|
17
|
+
const uiAppResources = new Map();
|
|
18
|
+
const uiAppResourceOrder = [];
|
|
19
|
+
const graphSnapshots = new Map();
|
|
20
|
+
const graphSnapshotOrder = [];
|
|
21
|
+
function isEnvEnabled(name, fallback = false) {
|
|
22
|
+
const raw = process.env[name];
|
|
23
|
+
if (raw === undefined) {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
return /^(1|true|yes|on)$/i.test(raw.trim());
|
|
27
|
+
}
|
|
28
|
+
function resolveGraphSnapshotDir() {
|
|
29
|
+
const raw = process.env.MCP_GRAPH_SNAPSHOT_DIR?.trim();
|
|
30
|
+
if (!raw) {
|
|
31
|
+
return DEFAULT_GRAPH_SNAPSHOT_DIR;
|
|
32
|
+
}
|
|
33
|
+
return path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
|
34
|
+
}
|
|
35
|
+
const extensionsCapabilityEnabled = isEnvEnabled("MCP_ENABLE_EXTENSIONS_CAPABILITY", false);
|
|
36
|
+
const uiAppsEnabled = isEnvEnabled("MCP_ENABLE_UI_APPS", false);
|
|
37
|
+
const traceMetaKey = process.env.MCP_TRACE_META_KEY || "trace";
|
|
38
|
+
const graphSnapshotDir = resolveGraphSnapshotDir();
|
|
39
|
+
const serverCapabilities = {
|
|
40
|
+
tools: {},
|
|
41
|
+
resources: {},
|
|
42
|
+
tasks: {
|
|
43
|
+
list: {},
|
|
44
|
+
cancel: {},
|
|
45
|
+
requests: {
|
|
46
|
+
tools: {
|
|
47
|
+
call: {},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
if (extensionsCapabilityEnabled) {
|
|
53
|
+
serverCapabilities.experimental = {
|
|
54
|
+
[EXTENSIONS_CAPABILITY_KEY]: {
|
|
55
|
+
traceMetaPassthrough: true,
|
|
56
|
+
traceMetaKey,
|
|
57
|
+
uiApps: uiAppsEnabled,
|
|
58
|
+
uiAppsMetaKey: "ui.resourceUri",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function getTraceMeta(meta) {
|
|
63
|
+
if (!meta || typeof meta !== "object") {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const metaRecord = meta;
|
|
67
|
+
if (traceMetaKey in metaRecord) {
|
|
68
|
+
return metaRecord[traceMetaKey];
|
|
69
|
+
}
|
|
70
|
+
return metaRecord.trace;
|
|
71
|
+
}
|
|
72
|
+
function withTraceMeta(result, traceMeta) {
|
|
73
|
+
if (traceMeta === undefined) {
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
...result,
|
|
78
|
+
_meta: {
|
|
79
|
+
...(result._meta ?? {}),
|
|
80
|
+
[traceMetaKey]: traceMeta,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function escapeHtml(value) {
|
|
85
|
+
return value
|
|
86
|
+
.replace(/&/g, "&")
|
|
87
|
+
.replace(/</g, "<")
|
|
88
|
+
.replace(/>/g, ">")
|
|
89
|
+
.replace(/"/g, """)
|
|
90
|
+
.replace(/'/g, "'");
|
|
91
|
+
}
|
|
92
|
+
function isUiTool(name) {
|
|
93
|
+
return [
|
|
94
|
+
"ui_design_system",
|
|
95
|
+
"ui_search",
|
|
96
|
+
"sync_ui_data",
|
|
97
|
+
"start_ui",
|
|
98
|
+
"start_product",
|
|
99
|
+
].includes(name);
|
|
100
|
+
}
|
|
101
|
+
function buildUiResourceHtml(name, args, result) {
|
|
102
|
+
const structured = result.structuredContent
|
|
103
|
+
? JSON.stringify(result.structuredContent, null, 2)
|
|
104
|
+
: "{}";
|
|
105
|
+
const argJson = JSON.stringify(args ?? {}, null, 2);
|
|
106
|
+
const textBlocks = Array.isArray(result.content)
|
|
107
|
+
? result.content
|
|
108
|
+
.map((item) => {
|
|
109
|
+
if (!item || typeof item !== "object") {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
const text = item.text;
|
|
113
|
+
return typeof text === "string" ? text : "";
|
|
114
|
+
})
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join("\n\n")
|
|
117
|
+
: "";
|
|
118
|
+
const now = new Date().toISOString();
|
|
119
|
+
return `<!doctype html>
|
|
120
|
+
<html lang="zh-CN">
|
|
121
|
+
<head>
|
|
122
|
+
<meta charset="utf-8">
|
|
123
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
124
|
+
<title>${escapeHtml(name)} · MCP Apps</title>
|
|
125
|
+
<style>
|
|
126
|
+
:root { color-scheme: light; }
|
|
127
|
+
body { font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0; background: #f4f7fb; color: #1e2a35; }
|
|
128
|
+
.wrap { max-width: 960px; margin: 0 auto; padding: 24px; }
|
|
129
|
+
.card { background: #fff; border-radius: 14px; padding: 18px; box-shadow: 0 4px 18px rgba(30,42,53,.08); margin-bottom: 16px; }
|
|
130
|
+
h1 { margin: 0 0 8px; font-size: 24px; }
|
|
131
|
+
h2 { margin: 0 0 10px; font-size: 16px; color: #2f4a65; }
|
|
132
|
+
pre { white-space: pre-wrap; word-break: break-word; background: #0f1720; color: #d9e7f7; border-radius: 10px; padding: 12px; font-size: 12px; line-height: 1.45; }
|
|
133
|
+
.meta { color: #4f6880; font-size: 12px; }
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="wrap">
|
|
138
|
+
<div class="card">
|
|
139
|
+
<h1>${escapeHtml(name)}</h1>
|
|
140
|
+
<div class="meta">Generated at ${escapeHtml(now)} · MCP Apps preview</div>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="card">
|
|
143
|
+
<h2>Text Output</h2>
|
|
144
|
+
<pre>${escapeHtml(textBlocks || "(no text output)")}</pre>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="card">
|
|
147
|
+
<h2>Structured Content</h2>
|
|
148
|
+
<pre>${escapeHtml(structured)}</pre>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="card">
|
|
151
|
+
<h2>Arguments</h2>
|
|
152
|
+
<pre>${escapeHtml(argJson)}</pre>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</body>
|
|
156
|
+
</html>`;
|
|
157
|
+
}
|
|
158
|
+
function putUiAppResource(toolName, args, result) {
|
|
159
|
+
const uid = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
160
|
+
const uri = `ui://mcp-probe-kit/${toolName}/${uid}`;
|
|
161
|
+
const entry = {
|
|
162
|
+
uri,
|
|
163
|
+
name: `UI Preview · ${toolName}`,
|
|
164
|
+
description: `MCP Apps preview generated by ${toolName}`,
|
|
165
|
+
mimeType: "text/html",
|
|
166
|
+
text: buildUiResourceHtml(toolName, args, result),
|
|
167
|
+
createdAt: new Date().toISOString(),
|
|
168
|
+
};
|
|
169
|
+
uiAppResources.set(uri, entry);
|
|
170
|
+
uiAppResourceOrder.push(uri);
|
|
171
|
+
while (uiAppResourceOrder.length > MAX_UI_APP_RESOURCES) {
|
|
172
|
+
const oldest = uiAppResourceOrder.shift();
|
|
173
|
+
if (oldest) {
|
|
174
|
+
uiAppResources.delete(oldest);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return uri;
|
|
178
|
+
}
|
|
179
|
+
function withUiResourceMeta(result, resourceUri) {
|
|
180
|
+
const currentUi = result._meta?.ui;
|
|
181
|
+
const currentUiRecord = currentUi && typeof currentUi === "object"
|
|
182
|
+
? currentUi
|
|
183
|
+
: {};
|
|
184
|
+
return {
|
|
185
|
+
...result,
|
|
186
|
+
_meta: {
|
|
187
|
+
...(result._meta ?? {}),
|
|
188
|
+
ui: {
|
|
189
|
+
...currentUiRecord,
|
|
190
|
+
resourceUri,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function withGraphSnapshotMeta(result, snapshot) {
|
|
196
|
+
const currentGraphMeta = result._meta?.graph;
|
|
197
|
+
const currentGraphMetaRecord = currentGraphMeta && typeof currentGraphMeta === "object"
|
|
198
|
+
? currentGraphMeta
|
|
199
|
+
: {};
|
|
200
|
+
return {
|
|
201
|
+
...result,
|
|
202
|
+
_meta: {
|
|
203
|
+
...(result._meta ?? {}),
|
|
204
|
+
graph: {
|
|
205
|
+
...currentGraphMetaRecord,
|
|
206
|
+
snapshotUri: snapshot.uri,
|
|
207
|
+
snapshotId: snapshot.id,
|
|
208
|
+
status: snapshot.status,
|
|
209
|
+
createdAt: snapshot.createdAt,
|
|
210
|
+
jsonFilePath: snapshot.jsonFilePath ?? null,
|
|
211
|
+
markdownFilePath: snapshot.markdownFilePath ?? null,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function trimText(value, maxLen) {
|
|
217
|
+
if (value.length <= maxLen) {
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
return `${value.slice(0, maxLen - 3)}...`;
|
|
221
|
+
}
|
|
222
|
+
function toPosixPath(value) {
|
|
223
|
+
return value.replace(/\\/g, "/");
|
|
224
|
+
}
|
|
225
|
+
function makeSafeFileSegment(value) {
|
|
226
|
+
return value
|
|
227
|
+
.toLowerCase()
|
|
228
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
229
|
+
.replace(/-+/g, "-")
|
|
230
|
+
.replace(/^-|-$/g, "")
|
|
231
|
+
.slice(0, 48) || "snapshot";
|
|
232
|
+
}
|
|
233
|
+
function ensureGraphSnapshotDir() {
|
|
234
|
+
if (!fs.existsSync(graphSnapshotDir)) {
|
|
235
|
+
fs.mkdirSync(graphSnapshotDir, { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function renderGraphSnapshotMarkdown(snapshot) {
|
|
239
|
+
return [
|
|
240
|
+
"# Graph Snapshot",
|
|
241
|
+
"",
|
|
242
|
+
`- id: ${snapshot.id}`,
|
|
243
|
+
`- tool: ${snapshot.toolName}`,
|
|
244
|
+
`- status: ${snapshot.status}`,
|
|
245
|
+
`- createdAt: ${snapshot.createdAt}`,
|
|
246
|
+
`- summary: ${snapshot.summary}`,
|
|
247
|
+
"",
|
|
248
|
+
"## Payload",
|
|
249
|
+
"```json",
|
|
250
|
+
JSON.stringify(snapshot.payload, null, 2),
|
|
251
|
+
"```",
|
|
252
|
+
"",
|
|
253
|
+
].join("\n");
|
|
254
|
+
}
|
|
255
|
+
function persistGraphSnapshot(snapshot) {
|
|
256
|
+
try {
|
|
257
|
+
ensureGraphSnapshotDir();
|
|
258
|
+
const safeTool = makeSafeFileSegment(snapshot.toolName);
|
|
259
|
+
const baseName = `${snapshot.id}-${safeTool}`;
|
|
260
|
+
const jsonPath = path.join(graphSnapshotDir, `${baseName}.json`);
|
|
261
|
+
const markdownPath = path.join(graphSnapshotDir, `${baseName}.md`);
|
|
262
|
+
const jsonText = JSON.stringify({
|
|
263
|
+
id: snapshot.id,
|
|
264
|
+
uri: snapshot.uri,
|
|
265
|
+
toolName: snapshot.toolName,
|
|
266
|
+
createdAt: snapshot.createdAt,
|
|
267
|
+
status: snapshot.status,
|
|
268
|
+
summary: snapshot.summary,
|
|
269
|
+
payload: snapshot.payload,
|
|
270
|
+
}, null, 2);
|
|
271
|
+
fs.writeFileSync(jsonPath, jsonText, "utf-8");
|
|
272
|
+
fs.writeFileSync(markdownPath, renderGraphSnapshotMarkdown(snapshot), "utf-8");
|
|
273
|
+
return {
|
|
274
|
+
...snapshot,
|
|
275
|
+
jsonFilePath: toPosixPath(jsonPath),
|
|
276
|
+
markdownFilePath: toPosixPath(markdownPath),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
281
|
+
console.error(`[MCP Probe Kit] graph snapshot persist failed: ${message}`);
|
|
282
|
+
return snapshot;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function sanitizeGraphPayload(payload) {
|
|
286
|
+
if (!payload || typeof payload !== "object") {
|
|
287
|
+
return payload;
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(payload)) {
|
|
290
|
+
return payload.slice(0, 20).map((item) => sanitizeGraphPayload(item));
|
|
291
|
+
}
|
|
292
|
+
const record = payload;
|
|
293
|
+
const next = {};
|
|
294
|
+
for (const [key, value] of Object.entries(record)) {
|
|
295
|
+
if (typeof value === "string") {
|
|
296
|
+
next[key] = trimText(value, 6000);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (key === "executions" && Array.isArray(value)) {
|
|
300
|
+
next[key] = value.slice(0, 8).map((item) => {
|
|
301
|
+
if (!item || typeof item !== "object") {
|
|
302
|
+
return item;
|
|
303
|
+
}
|
|
304
|
+
const exec = item;
|
|
305
|
+
return {
|
|
306
|
+
...exec,
|
|
307
|
+
text: typeof exec.text === "string" ? trimText(exec.text, 6000) : exec.text,
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
next[key] = sanitizeGraphPayload(value);
|
|
313
|
+
}
|
|
314
|
+
return next;
|
|
315
|
+
}
|
|
316
|
+
function readGraphPayload(toolName, result) {
|
|
317
|
+
if (result.isError) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
if (toolName === "code_insight" && result.structuredContent && typeof result.structuredContent === "object") {
|
|
321
|
+
const structured = result.structuredContent;
|
|
322
|
+
const status = typeof structured.status === "string" ? structured.status : "ok";
|
|
323
|
+
const summary = typeof structured.summary === "string"
|
|
324
|
+
? structured.summary
|
|
325
|
+
: "code_insight 图谱结果";
|
|
326
|
+
return {
|
|
327
|
+
status,
|
|
328
|
+
summary,
|
|
329
|
+
payload: sanitizeGraphPayload(structured),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if ((toolName === "start_feature" || toolName === "start_bugfix")
|
|
333
|
+
&& result.structuredContent
|
|
334
|
+
&& typeof result.structuredContent === "object") {
|
|
335
|
+
const structured = result.structuredContent;
|
|
336
|
+
const metadata = structured.metadata;
|
|
337
|
+
if (!metadata || typeof metadata !== "object") {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const graphContext = metadata.graphContext;
|
|
341
|
+
if (!graphContext || typeof graphContext !== "object") {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const graphRecord = graphContext;
|
|
345
|
+
const status = graphRecord.available === false ? "degraded" : "ok";
|
|
346
|
+
const summary = typeof graphRecord.summary === "string"
|
|
347
|
+
? graphRecord.summary
|
|
348
|
+
: `${toolName} 图谱上下文`;
|
|
349
|
+
return {
|
|
350
|
+
status,
|
|
351
|
+
summary,
|
|
352
|
+
payload: sanitizeGraphPayload({
|
|
353
|
+
graphContext,
|
|
354
|
+
plan: metadata.plan ?? null,
|
|
355
|
+
}),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
function rememberGraphSnapshot(toolName, result) {
|
|
361
|
+
const graph = readGraphPayload(toolName, result);
|
|
362
|
+
if (!graph) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
const id = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
366
|
+
const uri = `probe://graph/${id}`;
|
|
367
|
+
const snapshot = persistGraphSnapshot({
|
|
368
|
+
id,
|
|
369
|
+
uri,
|
|
370
|
+
toolName,
|
|
371
|
+
createdAt: new Date().toISOString(),
|
|
372
|
+
status: graph.status,
|
|
373
|
+
summary: graph.summary,
|
|
374
|
+
payload: graph.payload,
|
|
375
|
+
});
|
|
376
|
+
graphSnapshots.set(id, snapshot);
|
|
377
|
+
graphSnapshotOrder.push(id);
|
|
378
|
+
while (graphSnapshotOrder.length > MAX_GRAPH_SNAPSHOTS) {
|
|
379
|
+
const oldest = graphSnapshotOrder.shift();
|
|
380
|
+
if (oldest) {
|
|
381
|
+
graphSnapshots.delete(oldest);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return snapshot;
|
|
385
|
+
}
|
|
386
|
+
function decorateResult(toolName, args, raw, traceMeta) {
|
|
387
|
+
let result = withTraceMeta(raw, traceMeta);
|
|
388
|
+
const snapshot = rememberGraphSnapshot(toolName, result);
|
|
389
|
+
if (snapshot) {
|
|
390
|
+
result = withGraphSnapshotMeta(result, snapshot);
|
|
391
|
+
}
|
|
392
|
+
if (uiAppsEnabled && isUiTool(toolName) && !result.isError) {
|
|
393
|
+
const resourceUri = putUiAppResource(toolName, args, result);
|
|
394
|
+
result = withUiResourceMeta(result, resourceUri);
|
|
395
|
+
}
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
9
398
|
// 创建MCP服务器实例
|
|
10
399
|
const server = new Server({
|
|
11
400
|
name: NAME,
|
|
12
401
|
version: VERSION,
|
|
13
402
|
}, {
|
|
14
|
-
capabilities:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
403
|
+
capabilities: serverCapabilities,
|
|
404
|
+
taskStore: new InMemoryTaskStore(),
|
|
405
|
+
taskMessageQueue: new InMemoryTaskMessageQueue(),
|
|
18
406
|
});
|
|
19
407
|
// 定义工具列表 - 从 schemas 导入,并根据工具集过滤
|
|
20
408
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -25,86 +413,281 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
25
413
|
tools: filteredTools,
|
|
26
414
|
};
|
|
27
415
|
});
|
|
416
|
+
async function executeTool(name, args, context) {
|
|
417
|
+
switch (name) {
|
|
418
|
+
case "init_project":
|
|
419
|
+
return await initProject(args);
|
|
420
|
+
case "gencommit":
|
|
421
|
+
return await gencommit(args);
|
|
422
|
+
case "code_review":
|
|
423
|
+
return await codeReview(args);
|
|
424
|
+
case "code_insight":
|
|
425
|
+
return await codeInsight(args, context);
|
|
426
|
+
case "gentest":
|
|
427
|
+
return await gentest(args);
|
|
428
|
+
case "refactor":
|
|
429
|
+
return await refactor(args);
|
|
430
|
+
case "init_project_context":
|
|
431
|
+
return await initProjectContext(args);
|
|
432
|
+
case "add_feature":
|
|
433
|
+
return await addFeature(args);
|
|
434
|
+
case "fix_bug":
|
|
435
|
+
return await fixBug(args);
|
|
436
|
+
case "estimate":
|
|
437
|
+
return await estimate(args);
|
|
438
|
+
case "start_feature":
|
|
439
|
+
return await startFeature(args, context);
|
|
440
|
+
case "start_bugfix":
|
|
441
|
+
return await startBugfix(args, context);
|
|
442
|
+
case "start_onboard":
|
|
443
|
+
return await startOnboard(args, context);
|
|
444
|
+
case "start_ralph":
|
|
445
|
+
return await startRalph(args, context);
|
|
446
|
+
case "interview":
|
|
447
|
+
return await interview(args);
|
|
448
|
+
case "ask_user":
|
|
449
|
+
return await askUser(args);
|
|
450
|
+
case "ui_design_system":
|
|
451
|
+
return await uiDesignSystem(args);
|
|
452
|
+
case "ui_search":
|
|
453
|
+
return await uiSearch(args);
|
|
454
|
+
case "sync_ui_data":
|
|
455
|
+
return await syncUiData(args, context);
|
|
456
|
+
case "start_ui":
|
|
457
|
+
return await startUi(args, context);
|
|
458
|
+
case "start_product":
|
|
459
|
+
return await startProduct((args ?? {}), context);
|
|
460
|
+
case "git_work_report":
|
|
461
|
+
return await gitWorkReport(args);
|
|
462
|
+
default:
|
|
463
|
+
throw new Error(`未知工具: ${name}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function makeToolError(errorMessage) {
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: `错误: ${errorMessage}`,
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
isError: true,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function isTerminalTaskStatus(status) {
|
|
478
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
479
|
+
}
|
|
28
480
|
// 处理工具调用
|
|
29
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
481
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
30
482
|
const { name, arguments: args } = request.params;
|
|
483
|
+
const taskRequest = request.params.task;
|
|
484
|
+
const traceMeta = getTraceMeta(extra._meta);
|
|
485
|
+
const emitProgress = async (progress, message) => {
|
|
486
|
+
const progressToken = extra._meta?.progressToken;
|
|
487
|
+
if (progressToken === undefined) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
await extra.sendNotification(ProgressNotificationSchema.parse({
|
|
492
|
+
method: "notifications/progress",
|
|
493
|
+
params: {
|
|
494
|
+
progressToken,
|
|
495
|
+
progress,
|
|
496
|
+
total: 100,
|
|
497
|
+
message,
|
|
498
|
+
...(traceMeta === undefined
|
|
499
|
+
? {}
|
|
500
|
+
: {
|
|
501
|
+
_meta: {
|
|
502
|
+
[traceMetaKey]: traceMeta,
|
|
503
|
+
},
|
|
504
|
+
}),
|
|
505
|
+
},
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
const err = error instanceof Error ? error.message : String(error);
|
|
510
|
+
console.error(`[MCP Probe Kit] progress notification failed: ${err}`);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
if (taskRequest) {
|
|
514
|
+
if (!extra.taskStore) {
|
|
515
|
+
return withTraceMeta(makeToolError("服务器未启用任务存储,无法创建任务"), traceMeta);
|
|
516
|
+
}
|
|
517
|
+
const task = await extra.taskStore.createTask({
|
|
518
|
+
ttl: extra.taskRequestedTtl ?? taskRequest.ttl,
|
|
519
|
+
});
|
|
520
|
+
const taskAbortController = new AbortController();
|
|
521
|
+
const cancelWatcher = setInterval(() => {
|
|
522
|
+
void (async () => {
|
|
523
|
+
try {
|
|
524
|
+
const latestTask = await extra.taskStore?.getTask(task.taskId);
|
|
525
|
+
if (latestTask?.status === "cancelled" && !taskAbortController.signal.aborted) {
|
|
526
|
+
taskAbortController.abort();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// ignore watcher errors
|
|
531
|
+
}
|
|
532
|
+
})();
|
|
533
|
+
}, 400);
|
|
534
|
+
const onRequestAbort = () => taskAbortController.abort();
|
|
535
|
+
extra.signal.addEventListener("abort", onRequestAbort, { once: true });
|
|
536
|
+
const taskContext = {
|
|
537
|
+
signal: taskAbortController.signal,
|
|
538
|
+
traceMeta,
|
|
539
|
+
reportProgress: async (progress, message) => {
|
|
540
|
+
const normalized = Math.max(0, Math.min(100, Math.round(progress)));
|
|
541
|
+
await emitProgress(normalized, message);
|
|
542
|
+
try {
|
|
543
|
+
await extra.taskStore?.updateTaskStatus(task.taskId, "working", `[${normalized}%] ${message}`);
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
// task may have already reached terminal status
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
// 后台执行任务,不阻塞当前请求,立即返回 taskId 给客户端轮询。
|
|
551
|
+
void (async () => {
|
|
552
|
+
try {
|
|
553
|
+
await taskContext.reportProgress?.(5, `开始执行工具: ${name}`);
|
|
554
|
+
const rawResult = await executeTool(name, args, taskContext);
|
|
555
|
+
if (!rawResult || typeof rawResult !== "object") {
|
|
556
|
+
throw new Error(`工具 ${name} 返回了无效响应`);
|
|
557
|
+
}
|
|
558
|
+
const result = decorateResult(name, args, rawResult, traceMeta);
|
|
559
|
+
const latestTask = await extra.taskStore?.getTask(task.taskId);
|
|
560
|
+
if (!latestTask || isTerminalTaskStatus(latestTask.status)) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const status = result && typeof result === "object" && "isError" in result && result.isError
|
|
564
|
+
? "failed"
|
|
565
|
+
: "completed";
|
|
566
|
+
await extra.taskStore?.storeTaskResult(task.taskId, status, result);
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
if (isAbortError(error)) {
|
|
570
|
+
const latestTask = await extra.taskStore?.getTask(task.taskId);
|
|
571
|
+
if (!latestTask || isTerminalTaskStatus(latestTask.status)) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
await extra.taskStore?.storeTaskResult(task.taskId, "failed", withTraceMeta(makeToolError(`工具执行已取消: ${name}`), traceMeta));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
578
|
+
const latestTask = await extra.taskStore?.getTask(task.taskId);
|
|
579
|
+
if (!latestTask || isTerminalTaskStatus(latestTask.status)) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
await extra.taskStore?.storeTaskResult(task.taskId, "failed", withTraceMeta(makeToolError(errorMessage), traceMeta));
|
|
583
|
+
}
|
|
584
|
+
})().catch((error) => {
|
|
585
|
+
const err = error instanceof Error ? error.message : String(error);
|
|
586
|
+
console.error(`[MCP Probe Kit] task execution failed: ${err}`);
|
|
587
|
+
}).finally(() => {
|
|
588
|
+
clearInterval(cancelWatcher);
|
|
589
|
+
extra.signal.removeEventListener("abort", onRequestAbort);
|
|
590
|
+
});
|
|
591
|
+
return withTraceMeta({ task }, traceMeta);
|
|
592
|
+
}
|
|
593
|
+
const ensureNotAborted = () => {
|
|
594
|
+
if (extra.signal.aborted) {
|
|
595
|
+
throw new Error(`工具执行已取消: ${name}`);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const toolContext = {
|
|
599
|
+
signal: extra.signal,
|
|
600
|
+
traceMeta,
|
|
601
|
+
reportProgress: async (progress, message) => {
|
|
602
|
+
const normalized = Math.max(0, Math.min(100, Math.round(progress)));
|
|
603
|
+
await emitProgress(normalized, message);
|
|
604
|
+
},
|
|
605
|
+
};
|
|
31
606
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
case "code_review":
|
|
38
|
-
return await codeReview(args);
|
|
39
|
-
case "gentest":
|
|
40
|
-
return await gentest(args);
|
|
41
|
-
case "refactor":
|
|
42
|
-
return await refactor(args);
|
|
43
|
-
case "init_project_context":
|
|
44
|
-
return await initProjectContext(args);
|
|
45
|
-
case "add_feature":
|
|
46
|
-
return await addFeature(args);
|
|
47
|
-
case "fix_bug":
|
|
48
|
-
return await fixBug(args);
|
|
49
|
-
case "estimate":
|
|
50
|
-
return await estimate(args);
|
|
51
|
-
// 智能编排工具
|
|
52
|
-
case "start_feature":
|
|
53
|
-
return await startFeature(args);
|
|
54
|
-
case "start_bugfix":
|
|
55
|
-
return await startBugfix(args);
|
|
56
|
-
case "start_onboard":
|
|
57
|
-
return await startOnboard(args);
|
|
58
|
-
case "start_ralph":
|
|
59
|
-
return await startRalph(args);
|
|
60
|
-
// 访谈工具
|
|
61
|
-
case "interview":
|
|
62
|
-
return await interview(args);
|
|
63
|
-
case "ask_user":
|
|
64
|
-
return await askUser(args);
|
|
65
|
-
// UI/UX Pro Max 工具
|
|
66
|
-
case "ui_design_system":
|
|
67
|
-
return await uiDesignSystem(args);
|
|
68
|
-
case "ui_search":
|
|
69
|
-
return await uiSearch(args);
|
|
70
|
-
case "sync_ui_data":
|
|
71
|
-
return await syncUiData(args);
|
|
72
|
-
case "start_ui":
|
|
73
|
-
return await startUi(args);
|
|
74
|
-
// 产品设计工作流
|
|
75
|
-
case "start_product":
|
|
76
|
-
return await startProduct(args || {});
|
|
77
|
-
// Git 工具
|
|
78
|
-
case "git_work_report":
|
|
79
|
-
return await gitWorkReport(args);
|
|
80
|
-
default:
|
|
81
|
-
throw new Error(`未知工具: ${name}`);
|
|
607
|
+
ensureNotAborted();
|
|
608
|
+
await emitProgress(5, `开始执行工具: ${name}`);
|
|
609
|
+
const rawResult = await executeTool(name, args, toolContext);
|
|
610
|
+
if (!rawResult || typeof rawResult !== "object") {
|
|
611
|
+
throw new Error(`工具 ${name} 返回了无效响应`);
|
|
82
612
|
}
|
|
613
|
+
ensureNotAborted();
|
|
614
|
+
const result = decorateResult(name, args, rawResult, traceMeta);
|
|
615
|
+
await emitProgress(100, `工具执行完成: ${name}`);
|
|
616
|
+
return result;
|
|
83
617
|
}
|
|
84
618
|
catch (error) {
|
|
619
|
+
if (extra.signal.aborted) {
|
|
620
|
+
await emitProgress(100, `工具执行已取消: ${name}`);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
await emitProgress(100, `工具执行失败: ${name}`);
|
|
624
|
+
}
|
|
85
625
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
86
|
-
return
|
|
87
|
-
content: [
|
|
88
|
-
{
|
|
89
|
-
type: "text",
|
|
90
|
-
text: `错误: ${errorMessage}`,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
isError: true,
|
|
94
|
-
};
|
|
626
|
+
return withTraceMeta(makeToolError(errorMessage), traceMeta);
|
|
95
627
|
}
|
|
96
628
|
});
|
|
97
629
|
// 定义资源列表
|
|
98
630
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
631
|
+
const resources = [
|
|
632
|
+
{
|
|
633
|
+
uri: "probe://status",
|
|
634
|
+
name: "服务器状态",
|
|
635
|
+
description: "MCP Probe Kit 服务器当前状态",
|
|
636
|
+
mimeType: "application/json",
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
uri: "probe://graph/latest",
|
|
640
|
+
name: "图谱快照(最新)",
|
|
641
|
+
description: "最近一次 code_insight 或 start_* 生成的图谱快照",
|
|
642
|
+
mimeType: "application/json",
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
uri: "probe://graph/history",
|
|
646
|
+
name: "图谱快照(历史)",
|
|
647
|
+
description: `最近 ${graphSnapshotOrder.length} 条图谱快照摘要`,
|
|
648
|
+
mimeType: "application/json",
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
uri: "probe://graph/latest.md",
|
|
652
|
+
name: "图谱快照(最新 Markdown)",
|
|
653
|
+
description: "最近一次图谱快照的 Markdown 视图",
|
|
654
|
+
mimeType: "text/markdown",
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
uri: "probe://graph/files",
|
|
658
|
+
name: "图谱快照(文件索引)",
|
|
659
|
+
description: `图谱快照落盘目录: ${toPosixPath(graphSnapshotDir)}`,
|
|
660
|
+
mimeType: "application/json",
|
|
661
|
+
},
|
|
662
|
+
];
|
|
663
|
+
for (const id of graphSnapshotOrder.slice().reverse().slice(0, 10)) {
|
|
664
|
+
const snapshot = graphSnapshots.get(id);
|
|
665
|
+
if (!snapshot) {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
resources.push({
|
|
669
|
+
uri: snapshot.uri,
|
|
670
|
+
name: `图谱快照 · ${snapshot.toolName}`,
|
|
671
|
+
description: `${snapshot.status} · ${trimText(snapshot.summary, 120)} (${snapshot.createdAt})`,
|
|
672
|
+
mimeType: "application/json",
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
if (uiAppsEnabled) {
|
|
676
|
+
for (const uri of uiAppResourceOrder.slice().reverse()) {
|
|
677
|
+
const entry = uiAppResources.get(uri);
|
|
678
|
+
if (!entry) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
resources.push({
|
|
682
|
+
uri: entry.uri,
|
|
683
|
+
name: entry.name,
|
|
684
|
+
description: `${entry.description} (${entry.createdAt})`,
|
|
685
|
+
mimeType: entry.mimeType,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
99
689
|
return {
|
|
100
|
-
resources
|
|
101
|
-
{
|
|
102
|
-
uri: "probe://status",
|
|
103
|
-
name: "服务器状态",
|
|
104
|
-
description: "MCP Probe Kit 服务器当前状态",
|
|
105
|
-
mimeType: "application/json",
|
|
106
|
-
},
|
|
107
|
-
],
|
|
690
|
+
resources,
|
|
108
691
|
};
|
|
109
692
|
});
|
|
110
693
|
// 读取资源
|
|
@@ -124,81 +707,232 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
124
707
|
version: VERSION,
|
|
125
708
|
description: "AI 驱动的完整研发工具集",
|
|
126
709
|
},
|
|
710
|
+
extensions: {
|
|
711
|
+
enabled: extensionsCapabilityEnabled,
|
|
712
|
+
traceMetaKey,
|
|
713
|
+
uiAppsEnabled,
|
|
714
|
+
},
|
|
715
|
+
experimentalTasksStreaming: {
|
|
716
|
+
requestStream: typeof server.experimental.tasks.requestStream === "function",
|
|
717
|
+
createMessageStream: typeof server.experimental.tasks.createMessageStream === "function",
|
|
718
|
+
elicitInputStream: typeof server.experimental.tasks.elicitInputStream === "function",
|
|
719
|
+
},
|
|
720
|
+
graphSnapshots: {
|
|
721
|
+
count: graphSnapshotOrder.length,
|
|
722
|
+
snapshotDir: toPosixPath(graphSnapshotDir),
|
|
723
|
+
latest: (() => {
|
|
724
|
+
const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
|
|
725
|
+
if (!latestId) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
const latest = graphSnapshots.get(latestId);
|
|
729
|
+
if (!latest) {
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
id: latest.id,
|
|
734
|
+
uri: latest.uri,
|
|
735
|
+
toolName: latest.toolName,
|
|
736
|
+
status: latest.status,
|
|
737
|
+
summary: trimText(latest.summary, 140),
|
|
738
|
+
createdAt: latest.createdAt,
|
|
739
|
+
jsonFilePath: latest.jsonFilePath ?? null,
|
|
740
|
+
markdownFilePath: latest.markdownFilePath ?? null,
|
|
741
|
+
};
|
|
742
|
+
})(),
|
|
743
|
+
},
|
|
127
744
|
toolCount: allToolSchemas.length,
|
|
128
745
|
}, null, 2),
|
|
129
746
|
},
|
|
130
747
|
],
|
|
131
748
|
};
|
|
132
749
|
}
|
|
750
|
+
if (uri.startsWith("ui://")) {
|
|
751
|
+
const entry = uiAppResources.get(uri);
|
|
752
|
+
if (!entry) {
|
|
753
|
+
throw new Error(`未知 UI 资源: ${uri}`);
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
contents: [
|
|
757
|
+
{
|
|
758
|
+
uri: entry.uri,
|
|
759
|
+
mimeType: entry.mimeType,
|
|
760
|
+
text: entry.text,
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
if (uri === "probe://graph/latest") {
|
|
766
|
+
const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
|
|
767
|
+
if (!latestId) {
|
|
768
|
+
return {
|
|
769
|
+
contents: [
|
|
770
|
+
{
|
|
771
|
+
uri,
|
|
772
|
+
mimeType: "application/json",
|
|
773
|
+
text: JSON.stringify({
|
|
774
|
+
status: "empty",
|
|
775
|
+
message: "暂无图谱快照,请先调用 code_insight 或 start_feature/start_bugfix。",
|
|
776
|
+
}, null, 2),
|
|
777
|
+
},
|
|
778
|
+
],
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
const snapshot = graphSnapshots.get(latestId);
|
|
782
|
+
if (!snapshot) {
|
|
783
|
+
throw new Error(`图谱快照不存在: ${latestId}`);
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
contents: [
|
|
787
|
+
{
|
|
788
|
+
uri,
|
|
789
|
+
mimeType: "application/json",
|
|
790
|
+
text: JSON.stringify({
|
|
791
|
+
id: snapshot.id,
|
|
792
|
+
uri: snapshot.uri,
|
|
793
|
+
toolName: snapshot.toolName,
|
|
794
|
+
createdAt: snapshot.createdAt,
|
|
795
|
+
status: snapshot.status,
|
|
796
|
+
summary: snapshot.summary,
|
|
797
|
+
payload: snapshot.payload,
|
|
798
|
+
files: {
|
|
799
|
+
json: snapshot.jsonFilePath ?? null,
|
|
800
|
+
markdown: snapshot.markdownFilePath ?? null,
|
|
801
|
+
},
|
|
802
|
+
}, null, 2),
|
|
803
|
+
},
|
|
804
|
+
],
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (uri === "probe://graph/latest.md") {
|
|
808
|
+
const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
|
|
809
|
+
if (!latestId) {
|
|
810
|
+
return {
|
|
811
|
+
contents: [
|
|
812
|
+
{
|
|
813
|
+
uri,
|
|
814
|
+
mimeType: "text/markdown",
|
|
815
|
+
text: "# Graph Snapshot\n\n暂无图谱快照,请先调用 code_insight 或 start_feature/start_bugfix。",
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
const snapshot = graphSnapshots.get(latestId);
|
|
821
|
+
if (!snapshot) {
|
|
822
|
+
throw new Error(`图谱快照不存在: ${latestId}`);
|
|
823
|
+
}
|
|
824
|
+
const markdown = snapshot.markdownFilePath && fs.existsSync(snapshot.markdownFilePath)
|
|
825
|
+
? fs.readFileSync(snapshot.markdownFilePath, "utf-8")
|
|
826
|
+
: renderGraphSnapshotMarkdown(snapshot);
|
|
827
|
+
return {
|
|
828
|
+
contents: [
|
|
829
|
+
{
|
|
830
|
+
uri,
|
|
831
|
+
mimeType: "text/markdown",
|
|
832
|
+
text: markdown,
|
|
833
|
+
},
|
|
834
|
+
],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (uri === "probe://graph/history") {
|
|
838
|
+
const history = graphSnapshotOrder
|
|
839
|
+
.slice()
|
|
840
|
+
.reverse()
|
|
841
|
+
.map((id) => graphSnapshots.get(id))
|
|
842
|
+
.filter((item) => Boolean(item))
|
|
843
|
+
.map((item) => ({
|
|
844
|
+
id: item.id,
|
|
845
|
+
uri: item.uri,
|
|
846
|
+
toolName: item.toolName,
|
|
847
|
+
createdAt: item.createdAt,
|
|
848
|
+
status: item.status,
|
|
849
|
+
summary: trimText(item.summary, 200),
|
|
850
|
+
files: {
|
|
851
|
+
json: item.jsonFilePath ?? null,
|
|
852
|
+
markdown: item.markdownFilePath ?? null,
|
|
853
|
+
},
|
|
854
|
+
}));
|
|
855
|
+
return {
|
|
856
|
+
contents: [
|
|
857
|
+
{
|
|
858
|
+
uri,
|
|
859
|
+
mimeType: "application/json",
|
|
860
|
+
text: JSON.stringify({
|
|
861
|
+
count: history.length,
|
|
862
|
+
items: history,
|
|
863
|
+
}, null, 2),
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (uri === "probe://graph/files") {
|
|
869
|
+
const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
|
|
870
|
+
const latest = latestId ? graphSnapshots.get(latestId) ?? null : null;
|
|
871
|
+
const hasDir = fs.existsSync(graphSnapshotDir);
|
|
872
|
+
const files = hasDir
|
|
873
|
+
? fs
|
|
874
|
+
.readdirSync(graphSnapshotDir, { withFileTypes: true })
|
|
875
|
+
.filter((entry) => entry.isFile() && /\.(json|md)$/i.test(entry.name))
|
|
876
|
+
.map((entry) => toPosixPath(path.join(graphSnapshotDir, entry.name)))
|
|
877
|
+
.sort((a, b) => b.localeCompare(a))
|
|
878
|
+
.slice(0, 40)
|
|
879
|
+
: [];
|
|
880
|
+
return {
|
|
881
|
+
contents: [
|
|
882
|
+
{
|
|
883
|
+
uri,
|
|
884
|
+
mimeType: "application/json",
|
|
885
|
+
text: JSON.stringify({
|
|
886
|
+
snapshotDir: toPosixPath(graphSnapshotDir),
|
|
887
|
+
exists: hasDir,
|
|
888
|
+
latest: latest
|
|
889
|
+
? {
|
|
890
|
+
id: latest.id,
|
|
891
|
+
uri: latest.uri,
|
|
892
|
+
toolName: latest.toolName,
|
|
893
|
+
jsonFilePath: latest.jsonFilePath ?? null,
|
|
894
|
+
markdownFilePath: latest.markdownFilePath ?? null,
|
|
895
|
+
}
|
|
896
|
+
: null,
|
|
897
|
+
files,
|
|
898
|
+
}, null, 2),
|
|
899
|
+
},
|
|
900
|
+
],
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
if (uri.startsWith("probe://graph/")) {
|
|
904
|
+
const id = uri.slice("probe://graph/".length);
|
|
905
|
+
if (!id || id === "latest" || id === "history" || id === "files" || id === "latest.md") {
|
|
906
|
+
throw new Error(`未知图谱资源: ${uri}`);
|
|
907
|
+
}
|
|
908
|
+
const snapshot = graphSnapshots.get(id);
|
|
909
|
+
if (!snapshot) {
|
|
910
|
+
throw new Error(`图谱快照不存在: ${id}`);
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
contents: [
|
|
914
|
+
{
|
|
915
|
+
uri,
|
|
916
|
+
mimeType: "application/json",
|
|
917
|
+
text: JSON.stringify({
|
|
918
|
+
id: snapshot.id,
|
|
919
|
+
uri: snapshot.uri,
|
|
920
|
+
toolName: snapshot.toolName,
|
|
921
|
+
createdAt: snapshot.createdAt,
|
|
922
|
+
status: snapshot.status,
|
|
923
|
+
summary: snapshot.summary,
|
|
924
|
+
payload: snapshot.payload,
|
|
925
|
+
files: {
|
|
926
|
+
json: snapshot.jsonFilePath ?? null,
|
|
927
|
+
markdown: snapshot.markdownFilePath ?? null,
|
|
928
|
+
},
|
|
929
|
+
}, null, 2),
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
};
|
|
933
|
+
}
|
|
133
934
|
throw new Error(`未知资源: ${uri}`);
|
|
134
935
|
});
|
|
135
|
-
// ============================================
|
|
136
|
-
// Tasks API 端点 - 暂时禁用,等待 MCP SDK 正式支持
|
|
137
|
-
// ============================================
|
|
138
|
-
// 注意:当前 MCP SDK 版本不支持自定义 method,Tasks API 功能暂时禁用
|
|
139
|
-
// 相关 issue: https://github.com/modelcontextprotocol/sdk/issues/xxx
|
|
140
|
-
/*
|
|
141
|
-
// 获取任务状态
|
|
142
|
-
server.setRequestHandler({ method: "tasks/get" } as any, async (request: any) => {
|
|
143
|
-
try {
|
|
144
|
-
const { taskId } = request.params;
|
|
145
|
-
const tasksManager = getTasksManager();
|
|
146
|
-
const task = tasksManager.getTask(taskId);
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
task,
|
|
150
|
-
};
|
|
151
|
-
} catch (error) {
|
|
152
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
153
|
-
throw new Error(`Failed to get task: ${errorMessage}`);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// 获取任务结果
|
|
158
|
-
server.setRequestHandler({ method: "tasks/result" } as any, async (request: any) => {
|
|
159
|
-
try {
|
|
160
|
-
const { taskId } = request.params;
|
|
161
|
-
const tasksManager = getTasksManager();
|
|
162
|
-
const result = tasksManager.getTaskResult(taskId);
|
|
163
|
-
|
|
164
|
-
return result;
|
|
165
|
-
} catch (error) {
|
|
166
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
167
|
-
throw new Error(`Failed to get task result: ${errorMessage}`);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// 取消任务
|
|
172
|
-
server.setRequestHandler({ method: "tasks/cancel" } as any, async (request: any) => {
|
|
173
|
-
try {
|
|
174
|
-
const { taskId } = request.params;
|
|
175
|
-
const tasksManager = getTasksManager();
|
|
176
|
-
tasksManager.cancelTask(taskId);
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
_meta: {},
|
|
180
|
-
};
|
|
181
|
-
} catch (error) {
|
|
182
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
183
|
-
throw new Error(`Failed to cancel task: ${errorMessage}`);
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// 列出所有任务
|
|
188
|
-
server.setRequestHandler({ method: "tasks/list" } as any, async () => {
|
|
189
|
-
try {
|
|
190
|
-
const tasksManager = getTasksManager();
|
|
191
|
-
const tasks = tasksManager.listTasks();
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
tasks,
|
|
195
|
-
};
|
|
196
|
-
} catch (error) {
|
|
197
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
198
|
-
throw new Error(`Failed to list tasks: ${errorMessage}`);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
*/
|
|
202
936
|
// 启动服务器
|
|
203
937
|
async function main() {
|
|
204
938
|
const transport = new StdioServerTransport();
|