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/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 decorateResult(toolName, args, raw, traceMeta) {
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
- return await initProject(args);
452
+ result = (await initProject(args));
453
+ break;
378
454
  case "gencommit":
379
- return await gencommit(args);
455
+ result = (await gencommit(args));
456
+ break;
380
457
  case "code_review":
381
- return await codeReview(args);
458
+ result = (await codeReview(args));
459
+ break;
382
460
  case "code_insight":
383
- return await codeInsight(args, context);
461
+ result = (await codeInsight(args, context));
462
+ break;
384
463
  case "gentest":
385
- return await gentest(args);
464
+ result = (await gentest(args));
465
+ break;
386
466
  case "refactor":
387
- return await refactor(args);
467
+ result = (await refactor(args));
468
+ break;
388
469
  case "init_project_context":
389
- return await initProjectContext(args);
470
+ result = (await initProjectContext(args));
471
+ break;
390
472
  case "workflow":
391
- return await workflow(args);
473
+ result = (await workflow(args));
474
+ break;
392
475
  case "add_feature":
393
- return await addFeature(args);
476
+ result = (await addFeature(args));
477
+ break;
394
478
  case "check_spec":
395
- return await checkSpec(args);
479
+ result = (await checkSpec(args));
480
+ break;
396
481
  case "fix_bug":
397
- return await fixBug(args);
482
+ result = (await fixBug(args));
483
+ break;
398
484
  case "estimate":
399
- return await estimate(args);
485
+ result = (await estimate(args));
486
+ break;
400
487
  case "start_feature":
401
- return await startFeature(args, context);
488
+ result = (await startFeature(args, context));
489
+ break;
402
490
  case "start_bugfix":
403
- return await startBugfix(args, context);
491
+ result = (await startBugfix(args, context));
492
+ break;
404
493
  case "start_onboard":
405
- return await startOnboard(args, context);
494
+ result = (await startOnboard(args, context));
495
+ break;
406
496
  case "start_ralph":
407
- return await startRalph(args, context);
497
+ result = (await startRalph(args, context));
498
+ break;
408
499
  case "interview":
409
- return await interview(args);
500
+ result = (await interview(args));
501
+ break;
410
502
  case "ask_user":
411
- return await askUser(args);
503
+ result = (await askUser(args));
504
+ break;
412
505
  case "ui_design_system":
413
- return await uiDesignSystem(args);
506
+ result = (await uiDesignSystem(args));
507
+ break;
414
508
  case "ui_search":
415
- return await uiSearch(args);
509
+ result = (await uiSearch(args));
510
+ break;
416
511
  case "sync_ui_data":
417
- return await syncUiData(args, context);
512
+ result = (await syncUiData(args, context));
513
+ break;
418
514
  case "start_ui":
419
- return await startUi(args, context);
515
+ result = (await startUi(args, context));
516
+ break;
420
517
  case "start_product":
421
- return await startProduct((args ?? {}), context);
518
+ result = (await startProduct((args ?? {}), context));
519
+ break;
422
520
  case "git_work_report":
423
- return await gitWorkReport(args);
521
+ result = (await gitWorkReport(args));
522
+ break;
424
523
  case "search_memory":
425
- return await searchMemory(args);
524
+ result = (await searchMemory(args));
525
+ break;
426
526
  case "read_memory_asset":
427
- return await readMemoryAsset(args);
527
+ result = (await readMemoryAsset(args));
528
+ break;
428
529
  case "memorize_asset":
429
- return await memorizeAsset(args);
530
+ result = (await memorizeAsset(args));
531
+ break;
430
532
  case "delete_memory_asset":
431
- return await deleteMemoryAsset(args);
533
+ result = (await deleteMemoryAsset(args));
534
+ break;
432
535
  case "update_memory_asset":
433
- return await updateMemoryAsset(args);
536
+ result = (await updateMemoryAsset(args));
537
+ break;
434
538
  case "scan_and_extract_patterns":
435
- return await scanAndExtractPatterns(args);
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: "最近一次 code_insight 或 start_* 生成的图谱快照",
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 = graphSnapshotOrder
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
- console.error("MCP Probe Kit 服务器已启动");
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,
@@ -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;