mcp-probe-kit 3.3.0 → 3.6.1

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 (88) hide show
  1. package/README.md +781 -765
  2. package/build/index.js +198 -191
  3. package/build/lib/__tests__/agents-md-template.unit.test.js +1 -0
  4. package/build/lib/__tests__/dev-workflow.unit.test.d.ts +1 -0
  5. package/build/lib/__tests__/dev-workflow.unit.test.js +22 -0
  6. package/build/lib/__tests__/handles.unit.test.d.ts +1 -0
  7. package/build/lib/__tests__/handles.unit.test.js +19 -0
  8. package/build/lib/__tests__/mcp-apps.unit.test.d.ts +1 -0
  9. package/build/lib/__tests__/mcp-apps.unit.test.js +52 -0
  10. package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.d.ts +1 -0
  11. package/build/lib/__tests__/mcp-tool-skill-registry.unit.test.js +18 -0
  12. package/build/lib/__tests__/memory-client.unit.test.js +105 -0
  13. package/build/lib/__tests__/spec-gate.unit.test.d.ts +1 -0
  14. package/build/lib/__tests__/spec-gate.unit.test.js +54 -0
  15. package/build/lib/__tests__/task-defaults.unit.test.d.ts +1 -0
  16. package/build/lib/__tests__/task-defaults.unit.test.js +27 -0
  17. package/build/lib/__tests__/workflow-skill-installer.unit.test.d.ts +1 -0
  18. package/build/lib/__tests__/workflow-skill-installer.unit.test.js +110 -0
  19. package/build/lib/__tests__/workspace-root.unit.test.d.ts +1 -0
  20. package/build/lib/__tests__/workspace-root.unit.test.js +31 -0
  21. package/build/lib/agents-md-template.js +10 -2
  22. package/build/lib/dev-workflow.d.ts +36 -0
  23. package/build/lib/dev-workflow.js +497 -0
  24. package/build/lib/handles.d.ts +31 -0
  25. package/build/lib/handles.js +36 -0
  26. package/build/lib/mcp-apps.d.ts +14 -0
  27. package/build/lib/mcp-apps.js +234 -0
  28. package/build/lib/mcp-tool-skill-registry.d.ts +46 -0
  29. package/build/lib/mcp-tool-skill-registry.js +256 -0
  30. package/build/lib/memory-client.d.ts +19 -0
  31. package/build/lib/memory-client.js +69 -0
  32. package/build/lib/memory-orchestration.d.ts +5 -0
  33. package/build/lib/memory-orchestration.js +20 -0
  34. package/build/lib/merge-agents-md.d.ts +2 -2
  35. package/build/lib/merge-agents-md.js +7 -4
  36. package/build/lib/output-schema-registry.d.ts +11 -0
  37. package/build/lib/output-schema-registry.js +80 -0
  38. package/build/lib/spec-gate.d.ts +32 -0
  39. package/build/lib/spec-gate.js +83 -0
  40. package/build/lib/task-defaults.d.ts +6 -0
  41. package/build/lib/task-defaults.js +20 -0
  42. package/build/lib/tool-annotations.js +3 -0
  43. package/build/lib/toolset-manager.js +2 -0
  44. package/build/lib/workflow-skill-installer.d.ts +40 -0
  45. package/build/lib/workflow-skill-installer.js +172 -0
  46. package/build/lib/workflow-skill-template.d.ts +9 -0
  47. package/build/lib/workflow-skill-template.js +85 -0
  48. package/build/lib/workflow-skill-version.d.ts +15 -0
  49. package/build/lib/workflow-skill-version.js +68 -0
  50. package/build/lib/workspace-root.d.ts +4 -0
  51. package/build/lib/workspace-root.js +33 -0
  52. package/build/schemas/basic-tools.d.ts +4 -0
  53. package/build/schemas/basic-tools.js +4 -0
  54. package/build/schemas/index.d.ts +114 -0
  55. package/build/schemas/index.js +1 -0
  56. package/build/schemas/memory-tools.d.ts +83 -0
  57. package/build/schemas/memory-tools.js +43 -0
  58. package/build/schemas/orchestration-tools.d.ts +8 -0
  59. package/build/schemas/orchestration-tools.js +8 -0
  60. package/build/schemas/output/code-insight-tools.d.ts +65 -0
  61. package/build/schemas/output/code-insight-tools.js +42 -0
  62. package/build/schemas/output/memory-tools.d.ts +576 -0
  63. package/build/schemas/output/memory-tools.js +148 -0
  64. package/build/schemas/project-tools.d.ts +19 -0
  65. package/build/schemas/project-tools.js +20 -0
  66. package/build/tools/__tests__/delete_memory_asset.unit.test.d.ts +1 -0
  67. package/build/tools/__tests__/delete_memory_asset.unit.test.js +87 -0
  68. package/build/tools/__tests__/search_memory.unit.test.js +1 -0
  69. package/build/tools/__tests__/start_bugfix.unit.test.js +34 -1
  70. package/build/tools/__tests__/update_memory_asset.unit.test.d.ts +1 -0
  71. package/build/tools/__tests__/update_memory_asset.unit.test.js +82 -0
  72. package/build/tools/__tests__/workflow.unit.test.d.ts +1 -0
  73. package/build/tools/__tests__/workflow.unit.test.js +24 -0
  74. package/build/tools/code_insight.js +5 -1
  75. package/build/tools/delete_memory_asset.d.ts +7 -0
  76. package/build/tools/delete_memory_asset.js +57 -0
  77. package/build/tools/index.d.ts +3 -0
  78. package/build/tools/index.js +3 -0
  79. package/build/tools/init_project.js +170 -158
  80. package/build/tools/search_memory.js +5 -2
  81. package/build/tools/start_bugfix.js +37 -4
  82. package/build/tools/start_feature.js +4 -3
  83. package/build/tools/start_ui.js +5 -4
  84. package/build/tools/update_memory_asset.d.ts +7 -0
  85. package/build/tools/update_memory_asset.js +98 -0
  86. package/build/tools/workflow.d.ts +13 -0
  87. package/build/tools/workflow.js +69 -0
  88. package/package.json +5 -3
package/build/index.js CHANGED
@@ -5,11 +5,17 @@ import { InMemoryTaskMessageQueue, InMemoryTaskStore, } from "@modelcontextproto
5
5
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ProgressNotificationSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
6
  import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
- import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, checkSpec, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, scanAndExtractPatterns } from "./tools/index.js";
8
+ import { initProject, gencommit, codeReview, codeInsight, gentest, refactor, initProjectContext, addFeature, fixBug, estimate, checkSpec, workflow, startFeature, startBugfix, startOnboard, startRalph, interview, askUser, uiDesignSystem, uiSearch, syncUiData, startUi, startProduct, gitWorkReport, searchMemory, readMemoryAsset, memorizeAsset, deleteMemoryAsset, updateMemoryAsset, scanAndExtractPatterns } from "./tools/index.js";
9
9
  import { VERSION, NAME } from "./version.js";
10
10
  import { allToolSchemas } from "./schemas/index.js";
11
11
  import { filterTools, getToolsetFromEnv } from "./lib/toolset-manager.js";
12
12
  import { withToolAnnotations } from "./lib/tool-annotations.js";
13
+ import { withOutputSchema } from "./lib/output-schema-registry.js";
14
+ import { shouldAutoEscalateToTask } from "./lib/task-defaults.js";
15
+ import { attachHandles } from "./lib/handles.js";
16
+ import { buildMcpAppHtml, isMcpUiAppTool } from "./lib/mcp-apps.js";
17
+ import { ensureMcpProbeKitBootstrapForToolCall, } from "./lib/workflow-skill-installer.js";
18
+ import { resolveWorkspaceRoot } from "./lib/workspace-root.js";
13
19
  import { isAbortError, } from "./lib/tool-execution-context.js";
14
20
  const EXTENSIONS_CAPABILITY_KEY = "io.github.mybolide/extensions";
15
21
  const MAX_UI_APP_RESOURCES = 30;
@@ -82,80 +88,6 @@ function withTraceMeta(result, traceMeta) {
82
88
  },
83
89
  };
84
90
  }
85
- function escapeHtml(value) {
86
- return value
87
- .replace(/&/g, "&")
88
- .replace(/</g, "&lt;")
89
- .replace(/>/g, "&gt;")
90
- .replace(/"/g, "&quot;")
91
- .replace(/'/g, "&#39;");
92
- }
93
- function isUiTool(name) {
94
- return [
95
- "ui_design_system",
96
- "ui_search",
97
- "sync_ui_data",
98
- "start_ui",
99
- "start_product",
100
- ].includes(name);
101
- }
102
- function buildUiResourceHtml(name, args, result) {
103
- const structured = result.structuredContent
104
- ? JSON.stringify(result.structuredContent, null, 2)
105
- : "{}";
106
- const argJson = JSON.stringify(args ?? {}, null, 2);
107
- const textBlocks = Array.isArray(result.content)
108
- ? result.content
109
- .map((item) => {
110
- if (!item || typeof item !== "object") {
111
- return "";
112
- }
113
- const text = item.text;
114
- return typeof text === "string" ? text : "";
115
- })
116
- .filter(Boolean)
117
- .join("\n\n")
118
- : "";
119
- const now = new Date().toISOString();
120
- return `<!doctype html>
121
- <html lang="zh-CN">
122
- <head>
123
- <meta charset="utf-8">
124
- <meta name="viewport" content="width=device-width, initial-scale=1">
125
- <title>${escapeHtml(name)} · MCP Apps</title>
126
- <style>
127
- :root { color-scheme: light; }
128
- body { font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0; background: #f4f7fb; color: #1e2a35; }
129
- .wrap { max-width: 960px; margin: 0 auto; padding: 24px; }
130
- .card { background: #fff; border-radius: 14px; padding: 18px; box-shadow: 0 4px 18px rgba(30,42,53,.08); margin-bottom: 16px; }
131
- h1 { margin: 0 0 8px; font-size: 24px; }
132
- h2 { margin: 0 0 10px; font-size: 16px; color: #2f4a65; }
133
- 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; }
134
- .meta { color: #4f6880; font-size: 12px; }
135
- </style>
136
- </head>
137
- <body>
138
- <div class="wrap">
139
- <div class="card">
140
- <h1>${escapeHtml(name)}</h1>
141
- <div class="meta">Generated at ${escapeHtml(now)} · MCP Apps preview</div>
142
- </div>
143
- <div class="card">
144
- <h2>Text Output</h2>
145
- <pre>${escapeHtml(textBlocks || "(no text output)")}</pre>
146
- </div>
147
- <div class="card">
148
- <h2>Structured Content</h2>
149
- <pre>${escapeHtml(structured)}</pre>
150
- </div>
151
- <div class="card">
152
- <h2>Arguments</h2>
153
- <pre>${escapeHtml(argJson)}</pre>
154
- </div>
155
- </div>
156
- </body>
157
- </html>`;
158
- }
159
91
  function putUiAppResource(toolName, args, result) {
160
92
  const uid = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
161
93
  const uri = `ui://mcp-probe-kit/${toolName}/${uid}`;
@@ -164,7 +96,7 @@ function putUiAppResource(toolName, args, result) {
164
96
  name: `UI Preview · ${toolName}`,
165
97
  description: `MCP Apps preview generated by ${toolName}`,
166
98
  mimeType: "text/html",
167
- text: buildUiResourceHtml(toolName, args, result),
99
+ text: buildMcpAppHtml(toolName, args, result),
168
100
  createdAt: new Date().toISOString(),
169
101
  };
170
102
  uiAppResources.set(uri, entry);
@@ -253,6 +185,53 @@ function renderGraphSnapshotMarkdown(snapshot) {
253
185
  "",
254
186
  ].join("\n");
255
187
  }
188
+ function buildGraphHistorySummary(summaryLimit = 200) {
189
+ const items = graphSnapshotOrder
190
+ .slice()
191
+ .reverse()
192
+ .map((id) => graphSnapshots.get(id))
193
+ .filter((item) => Boolean(item))
194
+ .map((item) => ({
195
+ id: item.id,
196
+ uri: item.uri,
197
+ toolName: item.toolName,
198
+ createdAt: item.createdAt,
199
+ status: item.status,
200
+ summary: trimText(item.summary, summaryLimit),
201
+ files: {
202
+ json: item.jsonFilePath ?? null,
203
+ markdown: item.markdownFilePath ?? null,
204
+ },
205
+ }));
206
+ return { count: items.length, items };
207
+ }
208
+ function buildGraphFilesIndex(fileLimit = 40) {
209
+ const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
210
+ const latest = latestId ? (graphSnapshots.get(latestId) ?? null) : null;
211
+ const hasDir = fs.existsSync(graphSnapshotDir);
212
+ const files = hasDir
213
+ ? fs
214
+ .readdirSync(graphSnapshotDir, { withFileTypes: true })
215
+ .filter((entry) => entry.isFile() && /\.(json|md)$/i.test(entry.name))
216
+ .map((entry) => toPosixPath(path.join(graphSnapshotDir, entry.name)))
217
+ .sort((a, b) => b.localeCompare(a))
218
+ .slice(0, fileLimit)
219
+ : [];
220
+ return {
221
+ snapshotDir: toPosixPath(graphSnapshotDir),
222
+ exists: hasDir,
223
+ latest: latest
224
+ ? {
225
+ id: latest.id,
226
+ uri: latest.uri,
227
+ toolName: latest.toolName,
228
+ jsonFilePath: latest.jsonFilePath ?? null,
229
+ markdownFilePath: latest.markdownFilePath ?? null,
230
+ }
231
+ : null,
232
+ files,
233
+ };
234
+ }
256
235
  function persistGraphSnapshot(snapshot) {
257
236
  try {
258
237
  ensureGraphSnapshotDir();
@@ -384,13 +363,49 @@ function rememberGraphSnapshot(toolName, result) {
384
363
  }
385
364
  return snapshot;
386
365
  }
387
- function decorateResult(toolName, args, raw, traceMeta) {
366
+ function withStructuredHandles(result, handles) {
367
+ if (!result.structuredContent || typeof result.structuredContent !== "object" || Array.isArray(result.structuredContent)) {
368
+ return result;
369
+ }
370
+ return {
371
+ ...result,
372
+ structuredContent: attachHandles(result.structuredContent, handles),
373
+ };
374
+ }
375
+ function withBootstrapMeta(result, bootstrap) {
376
+ if (!bootstrap) {
377
+ return result;
378
+ }
379
+ const base = result.structuredContent &&
380
+ typeof result.structuredContent === "object" &&
381
+ !Array.isArray(result.structuredContent)
382
+ ? result.structuredContent
383
+ : {};
384
+ return {
385
+ ...result,
386
+ structuredContent: {
387
+ ...base,
388
+ mcp_probe_bootstrap: {
389
+ projectRoot: bootstrap.projectRoot,
390
+ skill: bootstrap.skill,
391
+ agentsMd: bootstrap.agentsMd,
392
+ workspaceWarning: bootstrap.workspaceWarning ?? null,
393
+ },
394
+ },
395
+ };
396
+ }
397
+ function decorateResult(toolName, args, raw, traceMeta, bootstrap = null) {
388
398
  let result = withTraceMeta(raw, traceMeta);
399
+ result = withBootstrapMeta(result, bootstrap);
389
400
  const snapshot = rememberGraphSnapshot(toolName, result);
390
401
  if (snapshot) {
391
402
  result = withGraphSnapshotMeta(result, snapshot);
403
+ result = withStructuredHandles(result, {
404
+ graph_snapshot: snapshot.uri,
405
+ graph_resource: snapshot.uri,
406
+ });
392
407
  }
393
- if (uiAppsEnabled && isUiTool(toolName) && !result.isError) {
408
+ if (uiAppsEnabled && isMcpUiAppTool(toolName) && !result.isError) {
394
409
  const resourceUri = putUiAppResource(toolName, args, result);
395
410
  result = withUiResourceMeta(result, resourceUri);
396
411
  }
@@ -411,68 +426,122 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
411
426
  const filteredTools = filterTools(allToolSchemas, toolset);
412
427
  console.error(`[MCP Probe Kit] 当前工具集: ${toolset} (${filteredTools.length}/${allToolSchemas.length} 个工具)`);
413
428
  return {
414
- tools: filteredTools.map(withToolAnnotations),
429
+ tools: filteredTools.map((tool) => withOutputSchema(withToolAnnotations(tool))),
415
430
  };
416
431
  });
417
432
  async function executeTool(name, args, context) {
433
+ const bootstrap = ensureMcpProbeKitBootstrapForToolCall(name, args);
434
+ if (bootstrap?.workspaceWarning) {
435
+ console.error(`[MCP Probe Kit] ${bootstrap.workspaceWarning}`);
436
+ }
437
+ if (bootstrap?.skill.created) {
438
+ console.error(`[MCP Probe Kit] 已创建 MCP Skill: ${bootstrap.skill.skillRelPath} (v${bootstrap.skill.version})`);
439
+ }
440
+ else if (bootstrap?.skill.updated) {
441
+ console.error(`[MCP Probe Kit] 已升级 MCP Skill: ${bootstrap.skill.skillRelPath} ${bootstrap.skill.previousVersion ?? "?"} → v${bootstrap.skill.version}`);
442
+ }
443
+ if (bootstrap?.agentsMd.created) {
444
+ console.error(`[MCP Probe Kit] 已创建 AGENTS.md(含 Skill 引用): ${bootstrap.agentsMd.path}`);
445
+ }
446
+ else if (bootstrap?.agentsMd.updated) {
447
+ console.error(`[MCP Probe Kit] 已更新 AGENTS.md(添加 Skill 引用): ${bootstrap.agentsMd.path}`);
448
+ }
449
+ let result;
418
450
  switch (name) {
419
451
  case "init_project":
420
- return await initProject(args);
452
+ result = (await initProject(args));
453
+ break;
421
454
  case "gencommit":
422
- return await gencommit(args);
455
+ result = (await gencommit(args));
456
+ break;
423
457
  case "code_review":
424
- return await codeReview(args);
458
+ result = (await codeReview(args));
459
+ break;
425
460
  case "code_insight":
426
- return await codeInsight(args, context);
461
+ result = (await codeInsight(args, context));
462
+ break;
427
463
  case "gentest":
428
- return await gentest(args);
464
+ result = (await gentest(args));
465
+ break;
429
466
  case "refactor":
430
- return await refactor(args);
467
+ result = (await refactor(args));
468
+ break;
431
469
  case "init_project_context":
432
- return await initProjectContext(args);
470
+ result = (await initProjectContext(args));
471
+ break;
472
+ case "workflow":
473
+ result = (await workflow(args));
474
+ break;
433
475
  case "add_feature":
434
- return await addFeature(args);
476
+ result = (await addFeature(args));
477
+ break;
435
478
  case "check_spec":
436
- return await checkSpec(args);
479
+ result = (await checkSpec(args));
480
+ break;
437
481
  case "fix_bug":
438
- return await fixBug(args);
482
+ result = (await fixBug(args));
483
+ break;
439
484
  case "estimate":
440
- return await estimate(args);
485
+ result = (await estimate(args));
486
+ break;
441
487
  case "start_feature":
442
- return await startFeature(args, context);
488
+ result = (await startFeature(args, context));
489
+ break;
443
490
  case "start_bugfix":
444
- return await startBugfix(args, context);
491
+ result = (await startBugfix(args, context));
492
+ break;
445
493
  case "start_onboard":
446
- return await startOnboard(args, context);
494
+ result = (await startOnboard(args, context));
495
+ break;
447
496
  case "start_ralph":
448
- return await startRalph(args, context);
497
+ result = (await startRalph(args, context));
498
+ break;
449
499
  case "interview":
450
- return await interview(args);
500
+ result = (await interview(args));
501
+ break;
451
502
  case "ask_user":
452
- return await askUser(args);
503
+ result = (await askUser(args));
504
+ break;
453
505
  case "ui_design_system":
454
- return await uiDesignSystem(args);
506
+ result = (await uiDesignSystem(args));
507
+ break;
455
508
  case "ui_search":
456
- return await uiSearch(args);
509
+ result = (await uiSearch(args));
510
+ break;
457
511
  case "sync_ui_data":
458
- return await syncUiData(args, context);
512
+ result = (await syncUiData(args, context));
513
+ break;
459
514
  case "start_ui":
460
- return await startUi(args, context);
515
+ result = (await startUi(args, context));
516
+ break;
461
517
  case "start_product":
462
- return await startProduct((args ?? {}), context);
518
+ result = (await startProduct((args ?? {}), context));
519
+ break;
463
520
  case "git_work_report":
464
- return await gitWorkReport(args);
521
+ result = (await gitWorkReport(args));
522
+ break;
465
523
  case "search_memory":
466
- return await searchMemory(args);
524
+ result = (await searchMemory(args));
525
+ break;
467
526
  case "read_memory_asset":
468
- return await readMemoryAsset(args);
527
+ result = (await readMemoryAsset(args));
528
+ break;
469
529
  case "memorize_asset":
470
- return await memorizeAsset(args);
530
+ result = (await memorizeAsset(args));
531
+ break;
532
+ case "delete_memory_asset":
533
+ result = (await deleteMemoryAsset(args));
534
+ break;
535
+ case "update_memory_asset":
536
+ result = (await updateMemoryAsset(args));
537
+ break;
471
538
  case "scan_and_extract_patterns":
472
- return await scanAndExtractPatterns(args);
539
+ result = (await scanAndExtractPatterns(args));
540
+ break;
473
541
  default:
474
542
  throw new Error(`未知工具: ${name}`);
475
543
  }
544
+ return { bootstrap, result };
476
545
  }
477
546
  function makeToolError(errorMessage) {
478
547
  return {
@@ -491,8 +560,11 @@ function isTerminalTaskStatus(status) {
491
560
  // 处理工具调用
492
561
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
493
562
  const { name, arguments: args } = request.params;
494
- const taskRequest = request.params.task;
563
+ let taskRequest = request.params.task;
495
564
  const traceMeta = getTraceMeta(extra._meta);
565
+ if (shouldAutoEscalateToTask(name, Boolean(taskRequest))) {
566
+ taskRequest = taskRequest ?? {};
567
+ }
496
568
  const emitProgress = async (progress, message) => {
497
569
  const progressToken = extra._meta?.progressToken;
498
570
  if (progressToken === undefined) {
@@ -562,11 +634,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
562
634
  void (async () => {
563
635
  try {
564
636
  await taskContext.reportProgress?.(5, `开始执行工具: ${name}`);
565
- const rawResult = await executeTool(name, args, taskContext);
637
+ const { bootstrap, result: rawResult } = await executeTool(name, args, taskContext);
566
638
  if (!rawResult || typeof rawResult !== "object") {
567
639
  throw new Error(`工具 ${name} 返回了无效响应`);
568
640
  }
569
- const result = decorateResult(name, args, rawResult, traceMeta);
641
+ const result = decorateResult(name, args, rawResult, traceMeta, bootstrap);
570
642
  const latestTask = await extra.taskStore?.getTask(task.taskId);
571
643
  if (!latestTask || isTerminalTaskStatus(latestTask.status)) {
572
644
  return;
@@ -617,12 +689,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
617
689
  try {
618
690
  ensureNotAborted();
619
691
  await emitProgress(5, `开始执行工具: ${name}`);
620
- const rawResult = await executeTool(name, args, toolContext);
692
+ const { bootstrap, result: rawResult } = await executeTool(name, args, toolContext);
621
693
  if (!rawResult || typeof rawResult !== "object") {
622
694
  throw new Error(`工具 ${name} 返回了无效响应`);
623
695
  }
624
696
  ensureNotAborted();
625
- const result = decorateResult(name, args, rawResult, traceMeta);
697
+ const result = decorateResult(name, args, rawResult, traceMeta, bootstrap);
626
698
  await emitProgress(100, `工具执行完成: ${name}`);
627
699
  return result;
628
700
  }
@@ -637,7 +709,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
637
709
  return withTraceMeta(makeToolError(errorMessage), traceMeta);
638
710
  }
639
711
  });
640
- // 定义资源列表
712
+ // 定义资源列表(Cursor 设置页会展示为条目;仅保留 status + graph/latest,其余 URI 仍可 ReadResource)
641
713
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
642
714
  const resources = [
643
715
  {
@@ -648,41 +720,11 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
648
720
  },
649
721
  {
650
722
  uri: "probe://graph/latest",
651
- name: "图谱快照(最新)",
652
- description: "最近一次 code_insight 或 start_* 生成的图谱快照",
653
- mimeType: "application/json",
654
- },
655
- {
656
- uri: "probe://graph/history",
657
- name: "图谱快照(历史)",
658
- description: `最近 ${graphSnapshotOrder.length} 条图谱快照摘要`,
659
- mimeType: "application/json",
660
- },
661
- {
662
- uri: "probe://graph/latest.md",
663
- name: "图谱快照(最新 Markdown)",
664
- description: "最近一次图谱快照的 Markdown 视图",
665
- mimeType: "text/markdown",
666
- },
667
- {
668
- uri: "probe://graph/files",
669
- name: "图谱快照(文件索引)",
670
- description: `图谱快照落盘目录: ${toPosixPath(graphSnapshotDir)}`,
723
+ name: "图谱快照",
724
+ description: "最新图谱快照(含历史摘要与落盘索引;Markdown 可读 probe://graph/latest.md)",
671
725
  mimeType: "application/json",
672
726
  },
673
727
  ];
674
- for (const id of graphSnapshotOrder.slice().reverse().slice(0, 10)) {
675
- const snapshot = graphSnapshots.get(id);
676
- if (!snapshot) {
677
- continue;
678
- }
679
- resources.push({
680
- uri: snapshot.uri,
681
- name: `图谱快照 · ${snapshot.toolName}`,
682
- description: `${snapshot.status} · ${trimText(snapshot.summary, 120)} (${snapshot.createdAt})`,
683
- mimeType: "application/json",
684
- });
685
- }
686
728
  if (uiAppsEnabled) {
687
729
  for (const uri of uiAppResourceOrder.slice().reverse()) {
688
730
  const entry = uiAppResources.get(uri);
@@ -810,6 +852,13 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
810
852
  json: snapshot.jsonFilePath ?? null,
811
853
  markdown: snapshot.markdownFilePath ?? null,
812
854
  },
855
+ history: buildGraphHistorySummary(),
856
+ fileIndex: buildGraphFilesIndex(),
857
+ relatedUris: {
858
+ markdown: "probe://graph/latest.md",
859
+ history: "probe://graph/history",
860
+ files: "probe://graph/files",
861
+ },
813
862
  }, null, 2),
814
863
  },
815
864
  ],
@@ -846,67 +895,24 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
846
895
  };
847
896
  }
848
897
  if (uri === "probe://graph/history") {
849
- const history = graphSnapshotOrder
850
- .slice()
851
- .reverse()
852
- .map((id) => graphSnapshots.get(id))
853
- .filter((item) => Boolean(item))
854
- .map((item) => ({
855
- id: item.id,
856
- uri: item.uri,
857
- toolName: item.toolName,
858
- createdAt: item.createdAt,
859
- status: item.status,
860
- summary: trimText(item.summary, 200),
861
- files: {
862
- json: item.jsonFilePath ?? null,
863
- markdown: item.markdownFilePath ?? null,
864
- },
865
- }));
898
+ const history = buildGraphHistorySummary();
866
899
  return {
867
900
  contents: [
868
901
  {
869
902
  uri,
870
903
  mimeType: "application/json",
871
- text: JSON.stringify({
872
- count: history.length,
873
- items: history,
874
- }, null, 2),
904
+ text: JSON.stringify(history, null, 2),
875
905
  },
876
906
  ],
877
907
  };
878
908
  }
879
909
  if (uri === "probe://graph/files") {
880
- const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
881
- const latest = latestId ? graphSnapshots.get(latestId) ?? null : null;
882
- const hasDir = fs.existsSync(graphSnapshotDir);
883
- const files = hasDir
884
- ? fs
885
- .readdirSync(graphSnapshotDir, { withFileTypes: true })
886
- .filter((entry) => entry.isFile() && /\.(json|md)$/i.test(entry.name))
887
- .map((entry) => toPosixPath(path.join(graphSnapshotDir, entry.name)))
888
- .sort((a, b) => b.localeCompare(a))
889
- .slice(0, 40)
890
- : [];
891
910
  return {
892
911
  contents: [
893
912
  {
894
913
  uri,
895
914
  mimeType: "application/json",
896
- text: JSON.stringify({
897
- snapshotDir: toPosixPath(graphSnapshotDir),
898
- exists: hasDir,
899
- latest: latest
900
- ? {
901
- id: latest.id,
902
- uri: latest.uri,
903
- toolName: latest.toolName,
904
- jsonFilePath: latest.jsonFilePath ?? null,
905
- markdownFilePath: latest.markdownFilePath ?? null,
906
- }
907
- : null,
908
- files,
909
- }, null, 2),
915
+ text: JSON.stringify(buildGraphFilesIndex(), null, 2),
910
916
  },
911
917
  ],
912
918
  };
@@ -948,7 +954,8 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
948
954
  async function main() {
949
955
  const transport = new StdioServerTransport();
950
956
  await server.connect(transport);
951
- console.error("MCP Probe Kit 服务器已启动");
957
+ const workspace = resolveWorkspaceRoot("");
958
+ console.error(`MCP Probe Kit v${VERSION} 已启动 | workspace=${workspace}`);
952
959
  }
953
960
  // 启动服务器
954
961
  main().catch((error) => {
@@ -20,6 +20,7 @@ describe('generateAgentsMdInner', () => {
20
20
  expect(md).toContain('记忆');
21
21
  expect(md).toContain('start_bugfix');
22
22
  expect(md).toContain('memorize_asset');
23
+ expect(md).toContain('mcp-probe-kit/SKILL.md');
23
24
  expect(md).toContain('bugfix');
24
25
  expect(md).toContain('search_memory');
25
26
  expect(md).toContain('自动注入');
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildDevWorkflow, detectWorkflowScenario } from '../dev-workflow.js';
3
+ describe('dev-workflow', () => {
4
+ test('识别 bugfix 场景', () => {
5
+ const result = detectWorkflowScenario('登录报错 NullReference 需要排查修复');
6
+ expect(result.scenario).toBe('bugfix');
7
+ });
8
+ test('识别 feature 场景', () => {
9
+ const result = detectWorkflowScenario('开发用户认证新功能');
10
+ expect(result.scenario).toBe('feature');
11
+ });
12
+ test('bugfix 计划首工具为 start_bugfix', () => {
13
+ const plan = buildDevWorkflow('TypeError in checkout');
14
+ expect(plan.firstTool).toBe('start_bugfix');
15
+ expect(plan.phases.some((p) => p.steps.some((s) => s.tool === 'start_bugfix'))).toBe(true);
16
+ });
17
+ test('显式 scenario 覆盖推断', () => {
18
+ const plan = buildDevWorkflow('随便', { scenario: 'ui' });
19
+ expect(plan.scenario).toBe('ui');
20
+ expect(plan.firstTool).toBe('start_ui');
21
+ });
22
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { attachHandles, buildMemoryAssetHandles, mergeHandles, } from '../../lib/handles.js';
3
+ describe('handles', () => {
4
+ test('buildMemoryAssetHandles 生成 read 默认工具名', () => {
5
+ const handles = buildMemoryAssetHandles([{ id: 'a-1', name: 'demo' }]);
6
+ expect(handles).toEqual([{ id: 'a-1', name: 'demo', tool: 'read_memory_asset' }]);
7
+ });
8
+ test('attachHandles 合并已有 handles', () => {
9
+ const result = attachHandles({ handles: { graph_resource: 'probe://graph/latest' } }, { memory_assets: buildMemoryAssetHandles([{ id: 'x' }]) });
10
+ expect(result.handles.graph_resource).toBe('probe://graph/latest');
11
+ expect(result.handles.memory_assets).toHaveLength(1);
12
+ });
13
+ test('mergeHandles 后者覆盖 graph 字段', () => {
14
+ expect(mergeHandles({ graph_resource: 'a' }, { graph_resource: 'b', graph_snapshot: 'probe://graph/s1' })).toEqual({
15
+ graph_resource: 'b',
16
+ graph_snapshot: 'probe://graph/s1',
17
+ });
18
+ });
19
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildCodeInsightAppHtml, buildSearchMemoryAppHtml, isMcpUiAppTool, } from '../mcp-apps.js';
3
+ describe('mcp-apps', () => {
4
+ test('search_memory 与 code_insight 属于 UI App 工具', () => {
5
+ expect(isMcpUiAppTool('search_memory')).toBe(true);
6
+ expect(isMcpUiAppTool('code_insight')).toBe(true);
7
+ expect(isMcpUiAppTool('gencommit')).toBe(false);
8
+ });
9
+ test('search_memory App 渲染命中卡片与 handles', () => {
10
+ const html = buildSearchMemoryAppHtml({ query: 'proxy bug' }, {
11
+ structuredContent: {
12
+ query: 'proxy bug',
13
+ count: 1,
14
+ results: [
15
+ {
16
+ id: 'asset-1',
17
+ name: 'feishu-proxy',
18
+ type: 'bugfix',
19
+ score: 0.88,
20
+ summary: 'proxy mismatch',
21
+ content: '【修复】done',
22
+ tags: ['bugfix'],
23
+ },
24
+ ],
25
+ handles: {
26
+ memory_assets: [{ id: 'asset-1', tool: 'read_memory_asset' }],
27
+ },
28
+ },
29
+ });
30
+ expect(html).toContain('search_memory');
31
+ expect(html).toContain('feishu-proxy');
32
+ expect(html).toContain('asset-1');
33
+ expect(html).toContain('read_memory_asset');
34
+ expect(html).toContain('proxy mismatch');
35
+ });
36
+ test('code_insight App 渲染状态与执行表', () => {
37
+ const html = buildCodeInsightAppHtml({ mode: 'auto' }, {
38
+ structuredContent: {
39
+ status: 'ok',
40
+ provider: 'gitnexus',
41
+ summary: 'login flow analyzed',
42
+ mode: { requested: 'auto', resolved: 'query' },
43
+ executions: [{ tool: 'query', ok: true, text: 'found login handler' }],
44
+ handles: { graph_resource: 'probe://graph/latest' },
45
+ },
46
+ });
47
+ expect(html).toContain('code_insight');
48
+ expect(html).toContain('login flow analyzed');
49
+ expect(html).toContain('probe://graph/latest');
50
+ expect(html).toContain('found login handler');
51
+ });
52
+ });