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.
Files changed (69) hide show
  1. package/README.md +458 -431
  2. package/build/index.js +875 -141
  3. package/build/lib/gitnexus-bridge.d.ts +58 -0
  4. package/build/lib/gitnexus-bridge.js +379 -0
  5. package/build/lib/template-loader.js +317 -317
  6. package/build/lib/tool-execution-context.d.ts +8 -0
  7. package/build/lib/tool-execution-context.js +20 -0
  8. package/build/lib/toolset-manager.d.ts +1 -1
  9. package/build/lib/toolset-manager.js +7 -5
  10. package/build/schemas/code-analysis-tools.d.ts +46 -0
  11. package/build/schemas/code-analysis-tools.js +47 -0
  12. package/build/schemas/git-tools.js +16 -16
  13. package/build/schemas/index.d.ts +46 -0
  14. package/build/tools/__tests__/code_insight.unit.test.d.ts +1 -0
  15. package/build/tools/__tests__/code_insight.unit.test.js +35 -0
  16. package/build/tools/__tests__/start_bugfix.unit.test.js +14 -14
  17. package/build/tools/__tests__/start_ui.unit.test.js +11 -11
  18. package/build/tools/add_feature.js +79 -79
  19. package/build/tools/ask_user.js +5 -5
  20. package/build/tools/code_insight.d.ts +8 -0
  21. package/build/tools/code_insight.js +129 -0
  22. package/build/tools/index.d.ts +1 -0
  23. package/build/tools/index.js +1 -0
  24. package/build/tools/interview.js +9 -9
  25. package/build/tools/start_bugfix.d.ts +2 -1
  26. package/build/tools/start_bugfix.js +170 -126
  27. package/build/tools/start_feature.d.ts +2 -1
  28. package/build/tools/start_feature.js +156 -112
  29. package/build/tools/start_onboard.d.ts +2 -1
  30. package/build/tools/start_onboard.js +57 -51
  31. package/build/tools/start_product.d.ts +2 -1
  32. package/build/tools/start_product.js +9 -1
  33. package/build/tools/start_ralph.d.ts +2 -1
  34. package/build/tools/start_ralph.js +9 -3
  35. package/build/tools/start_ui.d.ts +2 -1
  36. package/build/tools/start_ui.js +102 -88
  37. package/build/tools/ui-ux-tools.d.ts +2 -1
  38. package/build/tools/ui-ux-tools.js +19 -3
  39. package/build/utils/ui-sync.d.ts +6 -2
  40. package/build/utils/ui-sync.js +125 -29
  41. package/docs/assets/font/MaterialSymbolsOutlined.codepoints +4102 -0
  42. package/docs/assets/font/MaterialSymbolsOutlined.ttf +0 -0
  43. package/docs/assets/font/noto-sans-sc-400.ttf +0 -0
  44. package/docs/assets/font/noto-sans-sc-700.ttf +0 -0
  45. package/docs/assets/font/noto-sans-sc-900.ttf +0 -0
  46. package/docs/assets/js/i18n.js +122 -21
  47. package/docs/assets/js/tailwind.js +83 -83
  48. package/docs/data/tools.js +419 -399
  49. package/docs/debug-i18n.html +163 -0
  50. package/docs/i18n/all-tools/en.json +157 -0
  51. package/docs/i18n/all-tools/ja.json +157 -0
  52. package/docs/i18n/all-tools/ko.json +157 -0
  53. package/docs/i18n/all-tools/zh-CN.json +157 -0
  54. package/docs/pages/all-tools.html +514 -352
  55. package/docs/pages/examples.html +689 -689
  56. package/docs/pages/getting-started.html +589 -589
  57. package/docs/pages/migration.html +298 -298
  58. package/docs/specs/user-auth/design.md +82 -0
  59. package/docs/specs/user-auth/requirements.md +52 -0
  60. package/docs/specs/user-auth/tasks.md +55 -0
  61. package/package.json +5 -5
  62. package/docs/project-context/architecture.md +0 -0
  63. package/docs/project-context/how-to-develop.md +0 -313
  64. package/docs/project-context/how-to-test.md +0 -457
  65. package/docs/project-context/tech-stack.md +0 -96
  66. package/docs/project-context.md +0 -53
  67. package/docs/specs/git-work-report/design.md +0 -568
  68. package/docs/specs/git-work-report/requirements.md +0 -131
  69. 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 { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
- import { initProject, gencommit, codeReview, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport } from "./tools/index.js";
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, "&lt;")
88
+ .replace(/>/g, "&gt;")
89
+ .replace(/"/g, "&quot;")
90
+ .replace(/'/g, "&#39;");
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
- tools: {},
16
- resources: {},
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
- switch (name) {
33
- case "init_project":
34
- return await initProject(args);
35
- case "gencommit":
36
- return await gencommit(args);
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();