mcp-probe-kit 3.6.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.
- package/README.md +781 -773
- package/build/index.js +157 -116
- package/build/lib/__tests__/workspace-root.unit.test.d.ts +1 -0
- package/build/lib/__tests__/workspace-root.unit.test.js +31 -0
- package/build/lib/workflow-skill-installer.d.ts +2 -0
- package/build/lib/workflow-skill-installer.js +15 -1
- package/build/lib/workspace-root.d.ts +4 -0
- package/build/lib/workspace-root.js +33 -0
- package/build/schemas/basic-tools.d.ts +4 -0
- package/build/schemas/basic-tools.js +4 -0
- package/build/schemas/index.d.ts +4 -0
- package/build/tools/init_project.js +170 -158
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -14,7 +14,8 @@ import { withOutputSchema } from "./lib/output-schema-registry.js";
|
|
|
14
14
|
import { shouldAutoEscalateToTask } from "./lib/task-defaults.js";
|
|
15
15
|
import { attachHandles } from "./lib/handles.js";
|
|
16
16
|
import { buildMcpAppHtml, isMcpUiAppTool } from "./lib/mcp-apps.js";
|
|
17
|
-
import { ensureMcpProbeKitBootstrapForToolCall } from "./lib/workflow-skill-installer.js";
|
|
17
|
+
import { ensureMcpProbeKitBootstrapForToolCall, } from "./lib/workflow-skill-installer.js";
|
|
18
|
+
import { resolveWorkspaceRoot } from "./lib/workspace-root.js";
|
|
18
19
|
import { isAbortError, } from "./lib/tool-execution-context.js";
|
|
19
20
|
const EXTENSIONS_CAPABILITY_KEY = "io.github.mybolide/extensions";
|
|
20
21
|
const MAX_UI_APP_RESOURCES = 30;
|
|
@@ -184,6 +185,53 @@ function renderGraphSnapshotMarkdown(snapshot) {
|
|
|
184
185
|
"",
|
|
185
186
|
].join("\n");
|
|
186
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
|
+
}
|
|
187
235
|
function persistGraphSnapshot(snapshot) {
|
|
188
236
|
try {
|
|
189
237
|
ensureGraphSnapshotDir();
|
|
@@ -324,8 +372,31 @@ function withStructuredHandles(result, handles) {
|
|
|
324
372
|
structuredContent: attachHandles(result.structuredContent, handles),
|
|
325
373
|
};
|
|
326
374
|
}
|
|
327
|
-
function
|
|
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) {
|
|
328
398
|
let result = withTraceMeta(raw, traceMeta);
|
|
399
|
+
result = withBootstrapMeta(result, bootstrap);
|
|
329
400
|
const snapshot = rememberGraphSnapshot(toolName, result);
|
|
330
401
|
if (snapshot) {
|
|
331
402
|
result = withGraphSnapshotMeta(result, snapshot);
|
|
@@ -360,6 +431,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
360
431
|
});
|
|
361
432
|
async function executeTool(name, args, context) {
|
|
362
433
|
const bootstrap = ensureMcpProbeKitBootstrapForToolCall(name, args);
|
|
434
|
+
if (bootstrap?.workspaceWarning) {
|
|
435
|
+
console.error(`[MCP Probe Kit] ${bootstrap.workspaceWarning}`);
|
|
436
|
+
}
|
|
363
437
|
if (bootstrap?.skill.created) {
|
|
364
438
|
console.error(`[MCP Probe Kit] 已创建 MCP Skill: ${bootstrap.skill.skillRelPath} (v${bootstrap.skill.version})`);
|
|
365
439
|
}
|
|
@@ -372,70 +446,102 @@ async function executeTool(name, args, context) {
|
|
|
372
446
|
else if (bootstrap?.agentsMd.updated) {
|
|
373
447
|
console.error(`[MCP Probe Kit] 已更新 AGENTS.md(添加 Skill 引用): ${bootstrap.agentsMd.path}`);
|
|
374
448
|
}
|
|
449
|
+
let result;
|
|
375
450
|
switch (name) {
|
|
376
451
|
case "init_project":
|
|
377
|
-
|
|
452
|
+
result = (await initProject(args));
|
|
453
|
+
break;
|
|
378
454
|
case "gencommit":
|
|
379
|
-
|
|
455
|
+
result = (await gencommit(args));
|
|
456
|
+
break;
|
|
380
457
|
case "code_review":
|
|
381
|
-
|
|
458
|
+
result = (await codeReview(args));
|
|
459
|
+
break;
|
|
382
460
|
case "code_insight":
|
|
383
|
-
|
|
461
|
+
result = (await codeInsight(args, context));
|
|
462
|
+
break;
|
|
384
463
|
case "gentest":
|
|
385
|
-
|
|
464
|
+
result = (await gentest(args));
|
|
465
|
+
break;
|
|
386
466
|
case "refactor":
|
|
387
|
-
|
|
467
|
+
result = (await refactor(args));
|
|
468
|
+
break;
|
|
388
469
|
case "init_project_context":
|
|
389
|
-
|
|
470
|
+
result = (await initProjectContext(args));
|
|
471
|
+
break;
|
|
390
472
|
case "workflow":
|
|
391
|
-
|
|
473
|
+
result = (await workflow(args));
|
|
474
|
+
break;
|
|
392
475
|
case "add_feature":
|
|
393
|
-
|
|
476
|
+
result = (await addFeature(args));
|
|
477
|
+
break;
|
|
394
478
|
case "check_spec":
|
|
395
|
-
|
|
479
|
+
result = (await checkSpec(args));
|
|
480
|
+
break;
|
|
396
481
|
case "fix_bug":
|
|
397
|
-
|
|
482
|
+
result = (await fixBug(args));
|
|
483
|
+
break;
|
|
398
484
|
case "estimate":
|
|
399
|
-
|
|
485
|
+
result = (await estimate(args));
|
|
486
|
+
break;
|
|
400
487
|
case "start_feature":
|
|
401
|
-
|
|
488
|
+
result = (await startFeature(args, context));
|
|
489
|
+
break;
|
|
402
490
|
case "start_bugfix":
|
|
403
|
-
|
|
491
|
+
result = (await startBugfix(args, context));
|
|
492
|
+
break;
|
|
404
493
|
case "start_onboard":
|
|
405
|
-
|
|
494
|
+
result = (await startOnboard(args, context));
|
|
495
|
+
break;
|
|
406
496
|
case "start_ralph":
|
|
407
|
-
|
|
497
|
+
result = (await startRalph(args, context));
|
|
498
|
+
break;
|
|
408
499
|
case "interview":
|
|
409
|
-
|
|
500
|
+
result = (await interview(args));
|
|
501
|
+
break;
|
|
410
502
|
case "ask_user":
|
|
411
|
-
|
|
503
|
+
result = (await askUser(args));
|
|
504
|
+
break;
|
|
412
505
|
case "ui_design_system":
|
|
413
|
-
|
|
506
|
+
result = (await uiDesignSystem(args));
|
|
507
|
+
break;
|
|
414
508
|
case "ui_search":
|
|
415
|
-
|
|
509
|
+
result = (await uiSearch(args));
|
|
510
|
+
break;
|
|
416
511
|
case "sync_ui_data":
|
|
417
|
-
|
|
512
|
+
result = (await syncUiData(args, context));
|
|
513
|
+
break;
|
|
418
514
|
case "start_ui":
|
|
419
|
-
|
|
515
|
+
result = (await startUi(args, context));
|
|
516
|
+
break;
|
|
420
517
|
case "start_product":
|
|
421
|
-
|
|
518
|
+
result = (await startProduct((args ?? {}), context));
|
|
519
|
+
break;
|
|
422
520
|
case "git_work_report":
|
|
423
|
-
|
|
521
|
+
result = (await gitWorkReport(args));
|
|
522
|
+
break;
|
|
424
523
|
case "search_memory":
|
|
425
|
-
|
|
524
|
+
result = (await searchMemory(args));
|
|
525
|
+
break;
|
|
426
526
|
case "read_memory_asset":
|
|
427
|
-
|
|
527
|
+
result = (await readMemoryAsset(args));
|
|
528
|
+
break;
|
|
428
529
|
case "memorize_asset":
|
|
429
|
-
|
|
530
|
+
result = (await memorizeAsset(args));
|
|
531
|
+
break;
|
|
430
532
|
case "delete_memory_asset":
|
|
431
|
-
|
|
533
|
+
result = (await deleteMemoryAsset(args));
|
|
534
|
+
break;
|
|
432
535
|
case "update_memory_asset":
|
|
433
|
-
|
|
536
|
+
result = (await updateMemoryAsset(args));
|
|
537
|
+
break;
|
|
434
538
|
case "scan_and_extract_patterns":
|
|
435
|
-
|
|
539
|
+
result = (await scanAndExtractPatterns(args));
|
|
540
|
+
break;
|
|
436
541
|
default:
|
|
437
542
|
throw new Error(`未知工具: ${name}`);
|
|
438
543
|
}
|
|
544
|
+
return { bootstrap, result };
|
|
439
545
|
}
|
|
440
546
|
function makeToolError(errorMessage) {
|
|
441
547
|
return {
|
|
@@ -528,11 +634,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
528
634
|
void (async () => {
|
|
529
635
|
try {
|
|
530
636
|
await taskContext.reportProgress?.(5, `开始执行工具: ${name}`);
|
|
531
|
-
const rawResult = await executeTool(name, args, taskContext);
|
|
637
|
+
const { bootstrap, result: rawResult } = await executeTool(name, args, taskContext);
|
|
532
638
|
if (!rawResult || typeof rawResult !== "object") {
|
|
533
639
|
throw new Error(`工具 ${name} 返回了无效响应`);
|
|
534
640
|
}
|
|
535
|
-
const result = decorateResult(name, args, rawResult, traceMeta);
|
|
641
|
+
const result = decorateResult(name, args, rawResult, traceMeta, bootstrap);
|
|
536
642
|
const latestTask = await extra.taskStore?.getTask(task.taskId);
|
|
537
643
|
if (!latestTask || isTerminalTaskStatus(latestTask.status)) {
|
|
538
644
|
return;
|
|
@@ -583,12 +689,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
583
689
|
try {
|
|
584
690
|
ensureNotAborted();
|
|
585
691
|
await emitProgress(5, `开始执行工具: ${name}`);
|
|
586
|
-
const rawResult = await executeTool(name, args, toolContext);
|
|
692
|
+
const { bootstrap, result: rawResult } = await executeTool(name, args, toolContext);
|
|
587
693
|
if (!rawResult || typeof rawResult !== "object") {
|
|
588
694
|
throw new Error(`工具 ${name} 返回了无效响应`);
|
|
589
695
|
}
|
|
590
696
|
ensureNotAborted();
|
|
591
|
-
const result = decorateResult(name, args, rawResult, traceMeta);
|
|
697
|
+
const result = decorateResult(name, args, rawResult, traceMeta, bootstrap);
|
|
592
698
|
await emitProgress(100, `工具执行完成: ${name}`);
|
|
593
699
|
return result;
|
|
594
700
|
}
|
|
@@ -603,7 +709,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
603
709
|
return withTraceMeta(makeToolError(errorMessage), traceMeta);
|
|
604
710
|
}
|
|
605
711
|
});
|
|
606
|
-
//
|
|
712
|
+
// 定义资源列表(Cursor 设置页会展示为条目;仅保留 status + graph/latest,其余 URI 仍可 ReadResource)
|
|
607
713
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
608
714
|
const resources = [
|
|
609
715
|
{
|
|
@@ -614,41 +720,11 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
614
720
|
},
|
|
615
721
|
{
|
|
616
722
|
uri: "probe://graph/latest",
|
|
617
|
-
name: "
|
|
618
|
-
description: "
|
|
619
|
-
mimeType: "application/json",
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
uri: "probe://graph/history",
|
|
623
|
-
name: "图谱快照(历史)",
|
|
624
|
-
description: `最近 ${graphSnapshotOrder.length} 条图谱快照摘要`,
|
|
625
|
-
mimeType: "application/json",
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
uri: "probe://graph/latest.md",
|
|
629
|
-
name: "图谱快照(最新 Markdown)",
|
|
630
|
-
description: "最近一次图谱快照的 Markdown 视图",
|
|
631
|
-
mimeType: "text/markdown",
|
|
632
|
-
},
|
|
633
|
-
{
|
|
634
|
-
uri: "probe://graph/files",
|
|
635
|
-
name: "图谱快照(文件索引)",
|
|
636
|
-
description: `图谱快照落盘目录: ${toPosixPath(graphSnapshotDir)}`,
|
|
723
|
+
name: "图谱快照",
|
|
724
|
+
description: "最新图谱快照(含历史摘要与落盘索引;Markdown 可读 probe://graph/latest.md)",
|
|
637
725
|
mimeType: "application/json",
|
|
638
726
|
},
|
|
639
727
|
];
|
|
640
|
-
for (const id of graphSnapshotOrder.slice().reverse().slice(0, 10)) {
|
|
641
|
-
const snapshot = graphSnapshots.get(id);
|
|
642
|
-
if (!snapshot) {
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
resources.push({
|
|
646
|
-
uri: snapshot.uri,
|
|
647
|
-
name: `图谱快照 · ${snapshot.toolName}`,
|
|
648
|
-
description: `${snapshot.status} · ${trimText(snapshot.summary, 120)} (${snapshot.createdAt})`,
|
|
649
|
-
mimeType: "application/json",
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
728
|
if (uiAppsEnabled) {
|
|
653
729
|
for (const uri of uiAppResourceOrder.slice().reverse()) {
|
|
654
730
|
const entry = uiAppResources.get(uri);
|
|
@@ -776,6 +852,13 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
776
852
|
json: snapshot.jsonFilePath ?? null,
|
|
777
853
|
markdown: snapshot.markdownFilePath ?? null,
|
|
778
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
|
+
},
|
|
779
862
|
}, null, 2),
|
|
780
863
|
},
|
|
781
864
|
],
|
|
@@ -812,67 +895,24 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
812
895
|
};
|
|
813
896
|
}
|
|
814
897
|
if (uri === "probe://graph/history") {
|
|
815
|
-
const history =
|
|
816
|
-
.slice()
|
|
817
|
-
.reverse()
|
|
818
|
-
.map((id) => graphSnapshots.get(id))
|
|
819
|
-
.filter((item) => Boolean(item))
|
|
820
|
-
.map((item) => ({
|
|
821
|
-
id: item.id,
|
|
822
|
-
uri: item.uri,
|
|
823
|
-
toolName: item.toolName,
|
|
824
|
-
createdAt: item.createdAt,
|
|
825
|
-
status: item.status,
|
|
826
|
-
summary: trimText(item.summary, 200),
|
|
827
|
-
files: {
|
|
828
|
-
json: item.jsonFilePath ?? null,
|
|
829
|
-
markdown: item.markdownFilePath ?? null,
|
|
830
|
-
},
|
|
831
|
-
}));
|
|
898
|
+
const history = buildGraphHistorySummary();
|
|
832
899
|
return {
|
|
833
900
|
contents: [
|
|
834
901
|
{
|
|
835
902
|
uri,
|
|
836
903
|
mimeType: "application/json",
|
|
837
|
-
text: JSON.stringify(
|
|
838
|
-
count: history.length,
|
|
839
|
-
items: history,
|
|
840
|
-
}, null, 2),
|
|
904
|
+
text: JSON.stringify(history, null, 2),
|
|
841
905
|
},
|
|
842
906
|
],
|
|
843
907
|
};
|
|
844
908
|
}
|
|
845
909
|
if (uri === "probe://graph/files") {
|
|
846
|
-
const latestId = graphSnapshotOrder[graphSnapshotOrder.length - 1];
|
|
847
|
-
const latest = latestId ? graphSnapshots.get(latestId) ?? null : null;
|
|
848
|
-
const hasDir = fs.existsSync(graphSnapshotDir);
|
|
849
|
-
const files = hasDir
|
|
850
|
-
? fs
|
|
851
|
-
.readdirSync(graphSnapshotDir, { withFileTypes: true })
|
|
852
|
-
.filter((entry) => entry.isFile() && /\.(json|md)$/i.test(entry.name))
|
|
853
|
-
.map((entry) => toPosixPath(path.join(graphSnapshotDir, entry.name)))
|
|
854
|
-
.sort((a, b) => b.localeCompare(a))
|
|
855
|
-
.slice(0, 40)
|
|
856
|
-
: [];
|
|
857
910
|
return {
|
|
858
911
|
contents: [
|
|
859
912
|
{
|
|
860
913
|
uri,
|
|
861
914
|
mimeType: "application/json",
|
|
862
|
-
text: JSON.stringify(
|
|
863
|
-
snapshotDir: toPosixPath(graphSnapshotDir),
|
|
864
|
-
exists: hasDir,
|
|
865
|
-
latest: latest
|
|
866
|
-
? {
|
|
867
|
-
id: latest.id,
|
|
868
|
-
uri: latest.uri,
|
|
869
|
-
toolName: latest.toolName,
|
|
870
|
-
jsonFilePath: latest.jsonFilePath ?? null,
|
|
871
|
-
markdownFilePath: latest.markdownFilePath ?? null,
|
|
872
|
-
}
|
|
873
|
-
: null,
|
|
874
|
-
files,
|
|
875
|
-
}, null, 2),
|
|
915
|
+
text: JSON.stringify(buildGraphFilesIndex(), null, 2),
|
|
876
916
|
},
|
|
877
917
|
],
|
|
878
918
|
};
|
|
@@ -914,7 +954,8 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
914
954
|
async function main() {
|
|
915
955
|
const transport = new StdioServerTransport();
|
|
916
956
|
await server.connect(transport);
|
|
917
|
-
|
|
957
|
+
const workspace = resolveWorkspaceRoot("");
|
|
958
|
+
console.error(`MCP Probe Kit v${VERSION} 已启动 | workspace=${workspace}`);
|
|
918
959
|
}
|
|
919
960
|
// 启动服务器
|
|
920
961
|
main().catch((error) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
5
|
+
import { resolveFromWorkspaceFolderPathsEnv, resolveWorkspaceRoot, } from "../workspace-root.js";
|
|
6
|
+
const original = process.env.WORKSPACE_FOLDER_PATHS;
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
if (original === undefined) {
|
|
10
|
+
delete process.env.WORKSPACE_FOLDER_PATHS;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
process.env.WORKSPACE_FOLDER_PATHS = original;
|
|
14
|
+
}
|
|
15
|
+
while (tempDirs.length > 0) {
|
|
16
|
+
const dir = tempDirs.pop();
|
|
17
|
+
if (dir) {
|
|
18
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
describe("workspace-root WORKSPACE_FOLDER_PATHS", () => {
|
|
23
|
+
test("解析 JSON 数组形式", () => {
|
|
24
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ws-"));
|
|
25
|
+
tempDirs.push(root);
|
|
26
|
+
fs.writeFileSync(path.join(root, "package.json"), "{}", "utf8");
|
|
27
|
+
process.env.WORKSPACE_FOLDER_PATHS = JSON.stringify([root]);
|
|
28
|
+
expect(resolveFromWorkspaceFolderPathsEnv()).toBe(path.resolve(root));
|
|
29
|
+
expect(resolveWorkspaceRoot("")).toBe(path.resolve(root));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -17,6 +17,8 @@ export interface McpProbeKitBootstrapResult {
|
|
|
17
17
|
projectRoot: string;
|
|
18
18
|
skill: SkillEnsureResult;
|
|
19
19
|
agentsMd: AgentsMdEnsureResult;
|
|
20
|
+
/** 工作区可能解析失败(写到了 mcp-probe-kit 安装目录) */
|
|
21
|
+
workspaceWarning?: string;
|
|
20
22
|
}
|
|
21
23
|
export declare function resolveProjectRootFromToolArgs(args: unknown): string;
|
|
22
24
|
/**
|
|
@@ -5,7 +5,19 @@ import { mergeAgentsMdBlock } from "./merge-agents-md.js";
|
|
|
5
5
|
import { detectDocumentLocale, resolveProjectContextLayout, toPosixPath, } from "./project-context-layout.js";
|
|
6
6
|
import { generateWorkflowSkillContent, LEGACY_WORKFLOW_SKILL_REL_PATH, MCP_PROBE_SKILL_REL_PATH, } from "./workflow-skill-template.js";
|
|
7
7
|
import { agentsContextNeedsUpgrade, getMcpProbeSkillVersion, parseSkillVersionMarker, skillContentNeedsUpgrade, } from "./workflow-skill-version.js";
|
|
8
|
-
import { isLikelyProjectNamedRelativePath, resolveWorkspaceRoot, } from "./workspace-root.js";
|
|
8
|
+
import { isLikelyProjectNamedRelativePath, getMcpPackageInstallRoot, resolveWorkspaceRoot, } from "./workspace-root.js";
|
|
9
|
+
function buildWorkspaceWarning(projectRoot) {
|
|
10
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
11
|
+
const packageRoot = path.resolve(getMcpPackageInstallRoot());
|
|
12
|
+
if (normalizedRoot === packageRoot) {
|
|
13
|
+
return [
|
|
14
|
+
"未能解析用户项目根目录,Skill/AGENTS.md 可能写入了 mcp-probe-kit 安装目录。",
|
|
15
|
+
"请在 MCP 配置 env 中设置 MCP_PROJECT_ROOT 为 ${workspaceFolder},",
|
|
16
|
+
"或调用工具时传 project_root 绝对路径。",
|
|
17
|
+
].join("");
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
9
21
|
function flattenToolArgs(args) {
|
|
10
22
|
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
11
23
|
return {};
|
|
@@ -131,10 +143,12 @@ export function ensureAgentsMdSkillReference(projectRoot) {
|
|
|
131
143
|
*/
|
|
132
144
|
export function ensureMcpProbeKitBootstrap(projectRoot) {
|
|
133
145
|
const root = path.resolve(projectRoot);
|
|
146
|
+
const workspaceWarning = buildWorkspaceWarning(root);
|
|
134
147
|
return {
|
|
135
148
|
projectRoot: root,
|
|
136
149
|
skill: ensureMcpProbeSkill(root),
|
|
137
150
|
agentsMd: ensureAgentsMdSkillReference(root),
|
|
151
|
+
workspaceWarning,
|
|
138
152
|
};
|
|
139
153
|
}
|
|
140
154
|
export function ensureMcpProbeKitBootstrapForToolCall(_toolName, args) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** MCP 包自身安装目录(bootstrap 误写此处说明工作区未解析成功) */
|
|
2
|
+
export declare function getMcpPackageInstallRoot(): string;
|
|
3
|
+
/** 解析 Cursor 等客户端注入的 WORKSPACE_FOLDER_PATHS */
|
|
4
|
+
export declare function resolveFromWorkspaceFolderPathsEnv(): string | null;
|
|
1
5
|
export declare function isLikelyProjectNamedRelativePath(inputPath?: string): boolean;
|
|
2
6
|
export declare function buildProjectRootRetryHint(inputPath?: string): {
|
|
3
7
|
preferred: {
|
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { discoverProjectRootFromLayout } from "./project-context-layout.js";
|
|
5
5
|
const WORKSPACE_ENV_KEYS = [
|
|
6
|
+
"WORKSPACE_FOLDER_PATHS",
|
|
6
7
|
"MCP_PROJECT_ROOT",
|
|
7
8
|
"MCP_WORKSPACE_ROOT",
|
|
8
9
|
"CURSOR_WORKSPACE_ROOT",
|
|
@@ -76,6 +77,34 @@ function getRuntimePackageRoot() {
|
|
|
76
77
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
77
78
|
return path.resolve(moduleDir, "..", "..");
|
|
78
79
|
}
|
|
80
|
+
/** MCP 包自身安装目录(bootstrap 误写此处说明工作区未解析成功) */
|
|
81
|
+
export function getMcpPackageInstallRoot() {
|
|
82
|
+
return getRuntimePackageRoot();
|
|
83
|
+
}
|
|
84
|
+
/** 解析 Cursor 等客户端注入的 WORKSPACE_FOLDER_PATHS */
|
|
85
|
+
export function resolveFromWorkspaceFolderPathsEnv() {
|
|
86
|
+
const raw = process.env.WORKSPACE_FOLDER_PATHS?.trim();
|
|
87
|
+
if (!raw) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (raw.startsWith("[")) {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
94
|
+
const first = safeResolve(String(parsed[0]));
|
|
95
|
+
if (isExistingDirectory(first)) {
|
|
96
|
+
return first;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// fall through
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const firstSegment = raw.split(/[|;]/)[0]?.trim() || raw;
|
|
105
|
+
const candidate = safeResolve(firstSegment);
|
|
106
|
+
return isExistingDirectory(candidate) ? candidate : null;
|
|
107
|
+
}
|
|
79
108
|
function looksLikeWorkspaceRoot(target) {
|
|
80
109
|
return WORKSPACE_MARKERS.some((marker) => fs.existsSync(path.join(target, marker)));
|
|
81
110
|
}
|
|
@@ -134,6 +163,10 @@ export function resolveWorkspaceRoot(explicitProjectRoot) {
|
|
|
134
163
|
if (isExistingDirectory(explicit)) {
|
|
135
164
|
return discoverProjectRootFromLayout(explicit) ?? explicit;
|
|
136
165
|
}
|
|
166
|
+
const fromCursorWorkspace = resolveFromWorkspaceFolderPathsEnv();
|
|
167
|
+
if (fromCursorWorkspace) {
|
|
168
|
+
return discoverProjectRootFromLayout(fromCursorWorkspace) ?? fromCursorWorkspace;
|
|
169
|
+
}
|
|
137
170
|
const packageRoot = getRuntimePackageRoot();
|
|
138
171
|
const cwd = safeResolve(process.cwd()) || packageRoot;
|
|
139
172
|
const fromLayout = discoverProjectRootFromLayout(cwd);
|
|
@@ -15,6 +15,10 @@ export declare const basicToolSchemas: readonly [{
|
|
|
15
15
|
readonly type: "string";
|
|
16
16
|
readonly description: "项目名称。可选,默认为'新项目'";
|
|
17
17
|
};
|
|
18
|
+
readonly project_root: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly description: "项目根目录绝对路径。用于 Skill/AGENTS.md 自动安装;未传则解析 Cursor 工作区";
|
|
21
|
+
};
|
|
18
22
|
};
|
|
19
23
|
readonly required: readonly [];
|
|
20
24
|
readonly additionalProperties: true;
|
|
@@ -16,6 +16,10 @@ export const basicToolSchemas = [
|
|
|
16
16
|
type: "string",
|
|
17
17
|
description: "项目名称。可选,默认为'新项目'",
|
|
18
18
|
},
|
|
19
|
+
project_root: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "项目根目录绝对路径。用于 Skill/AGENTS.md 自动安装;未传则解析 Cursor 工作区",
|
|
22
|
+
},
|
|
19
23
|
},
|
|
20
24
|
required: [],
|
|
21
25
|
additionalProperties: true,
|
package/build/schemas/index.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export declare const allToolSchemas: ({
|
|
|
15
15
|
readonly type: "string";
|
|
16
16
|
readonly description: "项目名称。可选,默认为'新项目'";
|
|
17
17
|
};
|
|
18
|
+
readonly project_root: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly description: "项目根目录绝对路径。用于 Skill/AGENTS.md 自动安装;未传则解析 Cursor 工作区";
|
|
21
|
+
};
|
|
18
22
|
};
|
|
19
23
|
readonly required: readonly [];
|
|
20
24
|
readonly additionalProperties: true;
|