@foundation0/api 1.1.6 → 1.1.8

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/mcp/cli.ts CHANGED
@@ -67,6 +67,7 @@ const parseListArg = (value: string | undefined): string[] => {
67
67
  const serverName = getArgValue('--server-name', 'f0-mcp')
68
68
  const serverVersion = getArgValue('--server-version', '1.0.0')
69
69
  const toolsPrefix = getArgValue('--tools-prefix') ?? process.env.MCP_TOOLS_PREFIX
70
+ const repoName = getArgValue('--repo-name') ?? process.env.MCP_REPO_NAME ?? process.env.F0_REPO_NAME
70
71
  const repoRoot =
71
72
  getArgValue('--repo-root') ?? process.env.MCP_REPO_ROOT ?? process.env.F0_REPO_ROOT
72
73
  const allowedRootEndpoints = parseListArg(
@@ -82,6 +83,8 @@ if (hasFlag('--help') || hasFlag('-h')) {
82
83
  console.log(' --server-name <name>')
83
84
  console.log(' --server-version <version>')
84
85
  console.log(' --tools-prefix <prefix>')
86
+ console.log(' --repo-name <name>')
87
+ console.log(' Default repoName used by tool calls when omitted. Env: MCP_REPO_NAME or F0_REPO_NAME.')
85
88
  console.log(' --repo-root <path>')
86
89
  console.log(' Default repo root on the server filesystem (contains /api and /projects).')
87
90
  console.log(' Env: MCP_REPO_ROOT or F0_REPO_ROOT.')
@@ -102,6 +105,7 @@ void runExampleMcpServer({
102
105
  serverVersion: serverVersion ?? undefined,
103
106
  toolsPrefix,
104
107
  repoRoot: repoRoot ?? undefined,
108
+ repoName: repoName ?? undefined,
105
109
  allowedRootEndpoints,
106
110
  disableWrite,
107
111
  enableIssues,
package/mcp/manual.md CHANGED
@@ -7,11 +7,11 @@ Assume the server is already configured in your MCP host and you can call its to
7
7
 
8
8
  1. If you are unsure about a tool name, call `mcp.listTools` first and use the exact name it returns (prefixes vary).
9
9
  2. Use `mcp.describeTool` before your first call to a tool to get the input schema + an example payload.
10
- 3. Prefer named keys when possible (e.g. `projectName`, `repoRoot`) rather than relying on positional `args`.
11
- 4. When a tool needs `repoRoot`, it must be the **repo root** containing both `api/` and `projects/` (not a single project folder). (Legacy alias: `processRoot` is still accepted.) If omitted, the server will try to auto-detect a repo root from its current working directory (best effort).
10
+ 3. Prefer named keys when possible (e.g. `projectName`, `repoName`) rather than relying on positional `args`.
11
+ 4. `repoName` selects the repo workspace on the **server filesystem**. Usually omit it and let the server use its default. Legacy path aliases: `repoRoot` / `processRoot`.
12
12
  5. Tool results are returned as a **JSON text envelope**: parse the text as JSON and check `ok`.
13
13
 
14
- Note: `repoRoot` is a path on the **server's filesystem** (not the client/LLM). If you're unsure what the server can see, call `mcp.workspace`.
14
+ Note: If you’re unsure what the server can see (cwd/default repoRoot/available repoName values), call `mcp.workspace`. Multi-repo `repoName` mappings can be configured server-side via `MCP_REPOS` (JSON object or `name=path;name=path`).
15
15
 
16
16
  ## 1) Tool naming (prefixes + aliases)
17
17
 
@@ -55,14 +55,14 @@ Tool call:
55
55
  "projectName": "<project-name>",
56
56
  "section": "spec",
57
57
  "pattern": "authentication",
58
- "repoRoot": "<repo-root>",
58
+ "repoName": "<repo-name>",
59
59
  "ignoreCase": true,
60
60
  "maxCount": 50
61
61
  }
62
62
  }
63
63
  ```
64
64
 
65
- ### D) `mcp.workspace` to debug repoRoot/cwd issues
65
+ ### D) `mcp.workspace` to debug repoName/repoRoot/cwd issues
66
66
 
67
67
  Tool call:
68
68
 
@@ -75,13 +75,13 @@ Tool call:
75
75
  Most tools accept either:
76
76
 
77
77
  ```json
78
- { "args": ["<project-name>"], "options": { "repoRoot": "<repo-root>" } }
78
+ { "args": ["<project-name>"], "options": { "repoName": "<repo-name>" } }
79
79
  ```
80
80
 
81
81
  or named keys (recommended). The server merges top-level keys into `options`:
82
82
 
83
83
  ```json
84
- { "projectName": "<project-name>", "repoRoot": "<repo-root>" }
84
+ { "projectName": "<project-name>", "repoName": "<repo-name>" }
85
85
  ```
86
86
 
87
87
  Guideline: if a tool has a natural named parameter (`projectName`, `agentName`, `target`, `taskRef`), pass it explicitly.
@@ -93,7 +93,7 @@ Guideline: if a tool has a natural named parameter (`projectName`, `agentName`,
93
93
  ```json
94
94
  {
95
95
  "name": "projects.listProjects",
96
- "arguments": { "repoRoot": "<repo-root>" }
96
+ "arguments": { "repoName": "<repo-name>" }
97
97
  }
98
98
  ```
99
99
 
@@ -102,7 +102,7 @@ Guideline: if a tool has a natural named parameter (`projectName`, `agentName`,
102
102
  ```json
103
103
  {
104
104
  "name": "agents.listAgents",
105
- "arguments": { "repoRoot": "<repo-root>" }
105
+ "arguments": { "repoName": "<repo-name>" }
106
106
  }
107
107
  ```
108
108
 
@@ -113,7 +113,7 @@ Guideline: if a tool has a natural named parameter (`projectName`, `agentName`,
113
113
  "name": "projects.setActive",
114
114
  "arguments": {
115
115
  "args": ["<project-name>", "/implementation-plan.v0.0.1"],
116
- "options": { "repoRoot": "<repo-root>", "latest": true }
116
+ "options": { "repoName": "<repo-name>", "latest": true }
117
117
  }
118
118
  }
119
119
  ```
@@ -128,7 +128,7 @@ Call `batch` (or `<prefix>.batch`) to run multiple tool calls:
128
128
  "arguments": {
129
129
  "calls": [
130
130
  { "tool": "projects.usage" },
131
- { "tool": "projects.listProjects", "options": { "repoRoot": "<repo-root>" } }
131
+ { "tool": "projects.listProjects", "options": { "repoName": "<repo-name>" } }
132
132
  ],
133
133
  "continueOnError": true,
134
134
  "maxConcurrency": 4
@@ -147,8 +147,8 @@ If you get:
147
147
 
148
148
  - **Unknown tool**: use the `suggestions` from the error (when present), or call `mcp.listTools` again and retry.
149
149
  - **Missing project name**: pass `projectName` (or set `args[0]`).
150
- - **Project folder not found: ...projects/.../projects/...**: you likely passed the wrong `repoRoot` (it must be the repo root; legacy alias `processRoot`).
151
- - **`projects.listProjects()` returns `[]` unexpectedly**: call `mcp.workspace` to confirm the server’s `cwd`/`repoRoot` and whether `/projects` exists.
150
+ - **Project folder not found: ...projects/.../projects/...**: call `mcp.workspace` and verify the server repo workspace (default `repoRoot`, and optional `repoName` mapping).
151
+ - **`projects.listProjects()` returns `[]` unexpectedly**: call `mcp.workspace` to confirm the server’s `cwd`/default `repoRoot` and whether `/projects` exists.
152
152
 
153
153
  ## 6) Tool availability (read/write/admin)
154
154
 
@@ -204,9 +204,9 @@ describe("createExampleMcpServer request handling", () => {
204
204
  }
205
205
  });
206
206
 
207
- it("accepts repoRoot for projects tools (preferred over legacy processRoot)", async () => {
207
+ it("accepts repoName for projects tools (preferred over legacy repoRoot/processRoot)", async () => {
208
208
  const tempDir = await fs.mkdtemp(
209
- path.join(os.tmpdir(), "f0-mcp-server-reporoot-"),
209
+ path.join(os.tmpdir(), "f0-mcp-server-reponame-"),
210
210
  );
211
211
  try {
212
212
  await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
@@ -217,7 +217,7 @@ describe("createExampleMcpServer request handling", () => {
217
217
  recursive: true,
218
218
  });
219
219
 
220
- const instance = createExampleMcpServer();
220
+ const instance = createExampleMcpServer({ repoRoot: tempDir, repoName: "test" });
221
221
  const handler = getToolHandler(instance);
222
222
 
223
223
  const result = await handler(
@@ -226,7 +226,7 @@ describe("createExampleMcpServer request handling", () => {
226
226
  params: {
227
227
  name: "projects.listProjects",
228
228
  arguments: {
229
- repoRoot: tempDir,
229
+ repoName: "test",
230
230
  },
231
231
  },
232
232
  },
@@ -243,7 +243,7 @@ describe("createExampleMcpServer request handling", () => {
243
243
  }
244
244
  });
245
245
 
246
- it("auto-detects repoRoot from process.cwd() when repoRoot is omitted", async () => {
246
+ it("auto-detects repoRoot from process.cwd() when repoName is omitted", async () => {
247
247
  const tempDir = await fs.mkdtemp(
248
248
  path.join(os.tmpdir(), "f0-mcp-server-autoreporoot-"),
249
249
  );
@@ -320,7 +320,7 @@ describe("createExampleMcpServer request handling", () => {
320
320
  }
321
321
  });
322
322
 
323
- it("normalizes repoRoot when caller accidentally passes a project root", async () => {
323
+ it("normalizes legacy repoRoot when caller accidentally passes a project root", async () => {
324
324
  const tempDir = await fs.mkdtemp(
325
325
  path.join(os.tmpdir(), "f0-mcp-server-reporoot-normalize-"),
326
326
  );
@@ -359,7 +359,7 @@ describe("createExampleMcpServer request handling", () => {
359
359
  }
360
360
  });
361
361
 
362
- it("uses server default repoRoot when repoRoot is omitted", async () => {
362
+ it("uses server default repoRoot when repoName is omitted", async () => {
363
363
  const tempDir = await fs.mkdtemp(
364
364
  path.join(os.tmpdir(), "f0-mcp-server-default-reporoot-"),
365
365
  );
@@ -424,6 +424,7 @@ describe("createExampleMcpServer request handling", () => {
424
424
  const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
425
425
  expect(payload.ok).toBe(true);
426
426
  expect(payload.result.repoRoot).toBe(path.resolve(tempDir));
427
+ expect(payload.result.defaultRepoRoot).toBe(path.resolve(tempDir));
427
428
  expect(payload.result.hasProjectsDir).toBe(true);
428
429
  expect(Array.isArray(payload.result.projects)).toBe(true);
429
430
  } finally {
@@ -431,6 +432,118 @@ describe("createExampleMcpServer request handling", () => {
431
432
  }
432
433
  });
433
434
 
435
+ it("auto-detects repo root/name from .git/config when no repoRoot/repoName are provided", async () => {
436
+ const originalCwd = process.cwd();
437
+ const tempDir = await fs.mkdtemp(
438
+ path.join(os.tmpdir(), "f0-mcp-server-git-detect-"),
439
+ );
440
+ try {
441
+ await fs.mkdir(path.join(tempDir, "api", "mcp"), { recursive: true });
442
+ await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
443
+ recursive: true,
444
+ });
445
+ await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
446
+ await fs.writeFile(
447
+ path.join(tempDir, ".git", "config"),
448
+ [
449
+ '[remote "origin"]',
450
+ "\turl = https://example.com/F0/adl.git",
451
+ "",
452
+ ].join("\n"),
453
+ );
454
+
455
+ process.chdir(path.join(tempDir, "api", "mcp"));
456
+ const instance = createExampleMcpServer();
457
+ const handler = getToolHandler(instance);
458
+
459
+ const workspace = await handler(
460
+ {
461
+ method: "tools/call",
462
+ params: {
463
+ name: "mcp.workspace",
464
+ arguments: {},
465
+ },
466
+ },
467
+ {},
468
+ );
469
+
470
+ expect(workspace.isError).toBe(false);
471
+ const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
472
+ expect(payload.ok).toBe(true);
473
+ expect(payload.result.defaultRepoRoot).toBe(path.resolve(tempDir));
474
+ expect(payload.result.repoRoot).toBe(path.resolve(tempDir));
475
+ expect(payload.result.defaultRepoName).toBe("adl");
476
+ expect(payload.result.repoName).toBe("adl");
477
+ expect(payload.result.availableRepoNames).toContain("adl");
478
+
479
+ const list = await handler(
480
+ {
481
+ method: "tools/call",
482
+ params: {
483
+ name: "projects.listProjects",
484
+ arguments: {},
485
+ },
486
+ },
487
+ {},
488
+ );
489
+ expect(list.isError).toBe(false);
490
+ const listPayload = JSON.parse(list.content?.[0]?.text ?? "{}");
491
+ expect(listPayload.ok).toBe(true);
492
+ expect(listPayload.result).toContain("adl");
493
+ } finally {
494
+ process.chdir(originalCwd);
495
+ await fs.rm(tempDir, { recursive: true, force: true });
496
+ }
497
+ });
498
+
499
+ it("supports worktree-style .git file (gitdir: ...) when deriving repoName from config", async () => {
500
+ const originalCwd = process.cwd();
501
+ const tempDir = await fs.mkdtemp(
502
+ path.join(os.tmpdir(), "f0-mcp-server-gitfile-detect-"),
503
+ );
504
+ try {
505
+ await fs.mkdir(path.join(tempDir, "api", "mcp"), { recursive: true });
506
+ await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
507
+ recursive: true,
508
+ });
509
+
510
+ const gitDir = path.join(tempDir, ".gitdir");
511
+ await fs.mkdir(gitDir, { recursive: true });
512
+ await fs.writeFile(
513
+ path.join(gitDir, "config"),
514
+ [
515
+ '[remote "origin"]',
516
+ "\turl = git@github.com:F0/adl.git",
517
+ "",
518
+ ].join("\n"),
519
+ );
520
+ await fs.writeFile(path.join(tempDir, ".git"), "gitdir: .gitdir\n");
521
+
522
+ process.chdir(path.join(tempDir, "api", "mcp"));
523
+ const instance = createExampleMcpServer();
524
+ const handler = getToolHandler(instance);
525
+
526
+ const workspace = await handler(
527
+ {
528
+ method: "tools/call",
529
+ params: {
530
+ name: "mcp.workspace",
531
+ arguments: {},
532
+ },
533
+ },
534
+ {},
535
+ );
536
+
537
+ expect(workspace.isError).toBe(false);
538
+ const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
539
+ expect(payload.ok).toBe(true);
540
+ expect(payload.result.defaultRepoName).toBe("adl");
541
+ } finally {
542
+ process.chdir(originalCwd);
543
+ await fs.rm(tempDir, { recursive: true, force: true });
544
+ }
545
+ });
546
+
434
547
  it('parses continueOnError from string "false" (fails fast)', async () => {
435
548
  const instance = createExampleMcpServer();
436
549
  const handler = getToolHandler(instance);
package/mcp/server.ts CHANGED
@@ -9,6 +9,7 @@ import * as netApi from "../net.ts";
9
9
  import * as projectsApi from "../projects.ts";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
12
13
 
13
14
  type ApiMethod = (...args: unknown[]) => unknown;
14
15
  type ToolInvocationPayload = {
@@ -63,9 +64,9 @@ const buildProjectsMcpUsage = (toolsPrefix: string | undefined): string => {
63
64
  const tool = (name: string) => formatToolNameForUsage(name, toolsPrefix);
64
65
  return [
65
66
  "MCP Projects usage (tools/call):",
66
- `- ${tool("projects.listProjects")}: { options: { repoRoot: \"<repo-root>\" } }`,
67
- `- ${tool("projects.generateSpec")}: { args: [\"<projectName>\"], options: { repoRoot: \"<repo-root>\" } }`,
68
- `- ${tool("projects.setActive")}: { args: [\"<projectName>\", \"/implementation-plan.v0.0.1\"], options: { repoRoot: \"<repo-root>\", latest: true } }`,
67
+ `- ${tool("projects.listProjects")}: { options: { repoName: \"<repo-name>\" } }`,
68
+ `- ${tool("projects.generateSpec")}: { args: [\"<projectName>\"], options: { repoName: \"<repo-name>\" } }`,
69
+ `- ${tool("projects.setActive")}: { args: [\"<projectName>\", \"/implementation-plan.v0.0.1\"], options: { repoName: \"<repo-name>\", latest: true } }`,
69
70
  "",
70
71
  "Discovery:",
71
72
  `- ${tool("mcp.listTools")}: list available tools (and their schemas)`,
@@ -80,9 +81,9 @@ const buildAgentsMcpUsage = (toolsPrefix: string | undefined): string => {
80
81
  const tool = (name: string) => formatToolNameForUsage(name, toolsPrefix);
81
82
  return [
82
83
  "MCP Agents usage (tools/call):",
83
- `- ${tool("agents.listAgents")}: { options: { repoRoot: \"<repo-root>\" } }`,
84
- `- ${tool("agents.loadAgent")}: { args: [\"<agentName>\"], options: { repoRoot: \"<repo-root>\" } }`,
85
- `- ${tool("agents.setActive")}: { args: [\"<agentName>\", \"/system/boot.v0.0.1\"], options: { repoRoot: \"<repo-root>\", latest: true } }`,
84
+ `- ${tool("agents.listAgents")}: { options: { repoName: \"<repo-name>\" } }`,
85
+ `- ${tool("agents.loadAgent")}: { args: [\"<agentName>\"], options: { repoName: \"<repo-name>\" } }`,
86
+ `- ${tool("agents.setActive")}: { args: [\"<agentName>\", \"/system/boot.v0.0.1\"], options: { repoName: \"<repo-name>\", latest: true } }`,
86
87
  "",
87
88
  "Discovery:",
88
89
  `- ${tool("mcp.listTools")}: list available tools (and their schemas)`,
@@ -176,6 +177,104 @@ const isDir = (candidate: string): boolean => {
176
177
  const looksLikeRepoRoot = (candidate: string): boolean =>
177
178
  isDir(path.join(candidate, "projects")) && isDir(path.join(candidate, "api"));
178
179
 
180
+ const fileExists = (candidate: string): boolean => {
181
+ try {
182
+ fs.statSync(candidate);
183
+ return true;
184
+ } catch {
185
+ return false;
186
+ }
187
+ };
188
+
189
+ const findGitRepoRoot = (startDir: string): string | null => {
190
+ let current = path.resolve(startDir);
191
+ for (let depth = 0; depth < 32; depth += 1) {
192
+ const dotGit = path.join(current, ".git");
193
+ if (isDir(dotGit) || fileExists(dotGit)) return current;
194
+ const parent = path.dirname(current);
195
+ if (parent === current) return null;
196
+ current = parent;
197
+ }
198
+ return null;
199
+ };
200
+
201
+ const resolveGitDir = (repoRoot: string): string | null => {
202
+ const dotGit = path.join(repoRoot, ".git");
203
+ if (isDir(dotGit)) return dotGit;
204
+ if (!fileExists(dotGit)) return null;
205
+
206
+ try {
207
+ const content = fs.readFileSync(dotGit, "utf8");
208
+ const match = content.match(/^\s*gitdir:\s*(.+)\s*$/im);
209
+ if (!match) return null;
210
+ const raw = match[1].trim();
211
+ if (!raw) return null;
212
+ const resolved = path.resolve(repoRoot, raw);
213
+ return isDir(resolved) ? resolved : null;
214
+ } catch {
215
+ return null;
216
+ }
217
+ };
218
+
219
+ const parseRepoNameFromRemoteUrl = (remoteUrl: string): string | null => {
220
+ const trimmed = remoteUrl.trim();
221
+ if (!trimmed) return null;
222
+
223
+ const withoutHash = trimmed.split("#")[0] ?? trimmed;
224
+ const withoutQuery = withoutHash.split("?")[0] ?? withoutHash;
225
+ const withoutGit = withoutQuery.endsWith(".git")
226
+ ? withoutQuery.slice(0, -4)
227
+ : withoutQuery;
228
+
229
+ const lastSep = Math.max(withoutGit.lastIndexOf("/"), withoutGit.lastIndexOf(":"));
230
+ const candidate = (lastSep >= 0 ? withoutGit.slice(lastSep + 1) : withoutGit).trim();
231
+ if (!candidate) return null;
232
+ return candidate;
233
+ };
234
+
235
+ const readGitRemoteUrl = (gitDir: string): string | null => {
236
+ const configPath = path.join(gitDir, "config");
237
+ if (!fileExists(configPath)) return null;
238
+ try {
239
+ const config = fs.readFileSync(configPath, "utf8");
240
+ let currentRemote: string | null = null;
241
+ const remoteUrls = new Map<string, string>();
242
+
243
+ for (const rawLine of config.split(/\r?\n/)) {
244
+ const line = rawLine.trim();
245
+ if (!line || line.startsWith("#") || line.startsWith(";")) continue;
246
+
247
+ const sectionMatch = line.match(/^\[\s*([^\s\]]+)(?:\s+"([^"]+)")?\s*\]\s*$/);
248
+ if (sectionMatch) {
249
+ const section = sectionMatch[1].toLowerCase();
250
+ const name = sectionMatch[2] ?? null;
251
+ currentRemote = section === "remote" ? name : null;
252
+ continue;
253
+ }
254
+
255
+ if (!currentRemote) continue;
256
+ const kvMatch = line.match(/^([A-Za-z0-9][-A-Za-z0-9]*)\s*=\s*(.*)$/);
257
+ if (!kvMatch) continue;
258
+ const key = kvMatch[1].toLowerCase();
259
+ if (key !== "url") continue;
260
+ const value = kvMatch[2].trim().replace(/^"(.*)"$/, "$1").trim();
261
+ if (value) remoteUrls.set(currentRemote, value);
262
+ }
263
+
264
+ return remoteUrls.get("origin") ?? remoteUrls.values().next().value ?? null;
265
+ } catch {
266
+ return null;
267
+ }
268
+ };
269
+
270
+ const detectRepoNameFromGitConfig = (repoRoot: string): string | null => {
271
+ const gitDir = resolveGitDir(repoRoot);
272
+ if (!gitDir) return null;
273
+ const remoteUrl = readGitRemoteUrl(gitDir);
274
+ if (!remoteUrl) return null;
275
+ return parseRepoNameFromRemoteUrl(remoteUrl);
276
+ };
277
+
179
278
  const normalizeRepoRoot = (raw: string): string => {
180
279
  const resolved = path.resolve(raw);
181
280
  if (looksLikeRepoRoot(resolved)) return resolved;
@@ -226,6 +325,125 @@ const normalizeRepoRootOption = (
226
325
  return alreadyCanonical ? options : next;
227
326
  };
228
327
 
328
+ const looksLikePathish = (value: string): boolean => {
329
+ const trimmed = value.trim();
330
+ if (!trimmed) return false;
331
+ if (/^[a-zA-Z]:[\\/]/.test(trimmed)) return true;
332
+ return trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith(".");
333
+ };
334
+
335
+ const parseRepoMap = (raw: string | undefined): Record<string, string> => {
336
+ const input = typeof raw === "string" ? raw.trim() : "";
337
+ if (!input) return {};
338
+
339
+ if (input.startsWith("{")) {
340
+ try {
341
+ const parsed = JSON.parse(input);
342
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
343
+ return {};
344
+ }
345
+ const out: Record<string, string> = {};
346
+ for (const [name, root] of Object.entries(parsed)) {
347
+ if (!name || typeof root !== "string" || !root.trim()) continue;
348
+ out[name.trim()] = root.trim();
349
+ }
350
+ return out;
351
+ } catch {
352
+ return {};
353
+ }
354
+ }
355
+
356
+ const entries = input
357
+ .split(/[;,]/g)
358
+ .map((entry) => entry.trim())
359
+ .filter((entry) => entry.length > 0);
360
+
361
+ const out: Record<string, string> = {};
362
+ for (const entry of entries) {
363
+ const [name, root] = entry.split("=", 2);
364
+ if (!name || !root) continue;
365
+ const key = name.trim();
366
+ const value = root.trim();
367
+ if (!key || !value) continue;
368
+ out[key] = value;
369
+ }
370
+ return out;
371
+ };
372
+
373
+ type RepoResolutionContext = {
374
+ defaultRepoRoot: string;
375
+ defaultRepoName: string;
376
+ repoMapByKey: Record<string, string>;
377
+ repoNames: string[];
378
+ };
379
+
380
+ const resolveRepoSelectorOptions = (
381
+ options: Record<string, unknown>,
382
+ ctx: RepoResolutionContext,
383
+ ): Record<string, unknown> => {
384
+ const next: Record<string, unknown> = { ...options };
385
+
386
+ const explicitRoot =
387
+ (typeof next.repoRoot === "string" && next.repoRoot.trim().length > 0
388
+ ? next.repoRoot.trim()
389
+ : null) ??
390
+ (typeof next.processRoot === "string" && next.processRoot.trim().length > 0
391
+ ? next.processRoot.trim()
392
+ : null);
393
+
394
+ if (explicitRoot) {
395
+ next.repoRoot = normalizeRepoRoot(explicitRoot);
396
+ delete next.processRoot;
397
+ delete next.repoName;
398
+ return next;
399
+ }
400
+
401
+ const repoName =
402
+ typeof next.repoName === "string" && next.repoName.trim().length > 0
403
+ ? next.repoName.trim()
404
+ : null;
405
+ if (!repoName) {
406
+ delete next.processRoot;
407
+ return next;
408
+ }
409
+
410
+ if (looksLikePathish(repoName)) {
411
+ next.repoRoot = normalizeRepoRoot(repoName);
412
+ delete next.processRoot;
413
+ delete next.repoName;
414
+ return next;
415
+ }
416
+
417
+ const needle = repoName.toLowerCase();
418
+ if (needle === ctx.defaultRepoName.toLowerCase()) {
419
+ next.repoRoot = ctx.defaultRepoRoot;
420
+ delete next.processRoot;
421
+ delete next.repoName;
422
+ return next;
423
+ }
424
+
425
+ const mapped = ctx.repoMapByKey[needle];
426
+ if (mapped) {
427
+ next.repoRoot = normalizeRepoRoot(mapped);
428
+ delete next.processRoot;
429
+ delete next.repoName;
430
+ return next;
431
+ }
432
+
433
+ const suggestions = ctx.repoNames
434
+ .filter((name) => name.toLowerCase().includes(needle))
435
+ .slice(0, 8);
436
+ const hint =
437
+ suggestions.length > 0
438
+ ? ` Did you mean: ${suggestions.join(", ")}?`
439
+ : ctx.repoNames.length > 0
440
+ ? ` Available repos: ${ctx.repoNames.join(", ")}.`
441
+ : "";
442
+ throw new Error(
443
+ `Unknown repoName: ${repoName}.${hint} Tip: call mcp.workspace to see the server repo context.`,
444
+ );
445
+ };
446
+
229
447
  type NormalizedToolPayload = {
230
448
  args: unknown[];
231
449
  options: Record<string, unknown>;
@@ -451,7 +669,7 @@ const coercePayloadForTool = (
451
669
 
452
670
  switch (toolName) {
453
671
  case "projects.listProjects": {
454
- // No positional args. repoRoot is handled via options.repoRoot + buildRepoRootOnly.
672
+ // No positional args. repoRoot is resolved from repoName/repoRoot/processRoot and passed via buildRepoRootOnly.
455
673
  break;
456
674
  }
457
675
  case "projects.resolveProjectRoot":
@@ -852,7 +1070,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
852
1070
  description: "Unused.",
853
1071
  additionalProperties: true,
854
1072
  },
855
- repoRoot: {
1073
+ repoName: {
856
1074
  type: "string",
857
1075
  description: "Unused.",
858
1076
  },
@@ -862,10 +1080,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
862
1080
  type: "object",
863
1081
  additionalProperties: true,
864
1082
  properties: {
865
- repoRoot: {
1083
+ repoName: {
866
1084
  type: "string",
867
1085
  description:
868
- 'Repo root containing /projects and /agents. If you have a project root like ".../projects/adl", omit this or pass its parent.',
1086
+ "Repo selector (LLM-friendly). Omit to use server default. Tip: call mcp.workspace to see available repoName values.",
869
1087
  },
870
1088
  },
871
1089
  required: [],
@@ -878,10 +1096,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
878
1096
  type: "string",
879
1097
  description: 'Project name under /projects (e.g. "adl").',
880
1098
  },
881
- repoRoot: {
1099
+ repoName: {
882
1100
  type: "string",
883
1101
  description:
884
- "Repo root on the server filesystem (contains /projects and /agents). If omitted, uses the server default (auto-detected from cwd).",
1102
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
885
1103
  },
886
1104
  args: {
887
1105
  type: "array",
@@ -904,9 +1122,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
904
1122
  type: "string",
905
1123
  description: 'Project name under /projects (e.g. "adl").',
906
1124
  },
907
- repoRoot: {
1125
+ repoName: {
908
1126
  type: "string",
909
- description: "Repo root containing /projects and /agents.",
1127
+ description:
1128
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
910
1129
  },
911
1130
  args: {
912
1131
  type: "array",
@@ -934,9 +1153,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
934
1153
  description:
935
1154
  'Doc path under docs/, starting with "docs/..." (or a bare filename in catalog).',
936
1155
  },
937
- repoRoot: {
1156
+ repoName: {
938
1157
  type: "string",
939
- description: "Repo root containing /projects and /agents.",
1158
+ description:
1159
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
940
1160
  },
941
1161
  args: {
942
1162
  type: "array",
@@ -1011,9 +1231,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1011
1231
  type: "string",
1012
1232
  description: "Remote repo override for gitea mode.",
1013
1233
  },
1014
- repoRoot: {
1234
+ repoName: {
1015
1235
  type: "string",
1016
- description: "Repo root containing /projects and /agents.",
1236
+ description:
1237
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1017
1238
  },
1018
1239
  args: {
1019
1240
  type: "array",
@@ -1088,9 +1309,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1088
1309
  type: "string",
1089
1310
  description: "Remote repo override for gitea mode.",
1090
1311
  },
1091
- repoRoot: {
1312
+ repoName: {
1092
1313
  type: "string",
1093
- description: "Repo root containing /projects and /agents.",
1314
+ description:
1315
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1094
1316
  },
1095
1317
  args: {
1096
1318
  type: "array",
@@ -1204,9 +1426,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1204
1426
  type: "boolean",
1205
1427
  description: "If true, only return issues with TASK-* IDs.",
1206
1428
  },
1207
- repoRoot: {
1429
+ repoName: {
1208
1430
  type: "string",
1209
- description: "Repo root containing /projects.",
1431
+ description:
1432
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1210
1433
  },
1211
1434
  args: {
1212
1435
  type: "array",
@@ -1245,9 +1468,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1245
1468
  type: "boolean",
1246
1469
  description: "If true, restrict to issues with TASK-* payloads.",
1247
1470
  },
1248
- repoRoot: {
1471
+ repoName: {
1249
1472
  type: "string",
1250
- description: "Repo root containing /projects.",
1473
+ description:
1474
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1251
1475
  },
1252
1476
  args: {
1253
1477
  type: "array",
@@ -1302,9 +1526,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1302
1526
  type: "string",
1303
1527
  description: "Optional task signature.",
1304
1528
  },
1305
- repoRoot: {
1529
+ repoName: {
1306
1530
  type: "string",
1307
- description: "Repo root containing /projects.",
1531
+ description:
1532
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1308
1533
  },
1309
1534
  args: {
1310
1535
  type: "array",
@@ -1336,9 +1561,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1336
1561
  items: { type: "string" },
1337
1562
  description: "Labels to set.",
1338
1563
  },
1339
- repoRoot: {
1564
+ repoName: {
1340
1565
  type: "string",
1341
- description: "Repo root containing /projects.",
1566
+ description:
1567
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1342
1568
  },
1343
1569
  args: {
1344
1570
  type: "array",
@@ -1357,10 +1583,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1357
1583
  type: "object",
1358
1584
  additionalProperties: true,
1359
1585
  properties: {
1360
- repoRoot: {
1586
+ repoName: {
1361
1587
  type: "string",
1362
1588
  description:
1363
- "Optional repo root on the server filesystem. If omitted, uses the server default (auto-detected by walking up from cwd). Legacy alias: processRoot.",
1589
+ "Optional repo selector (LLM-friendly). Omit to use server default. You can also pass a server filesystem path via legacy repoRoot/processRoot.",
1364
1590
  },
1365
1591
  },
1366
1592
  required: [],
@@ -1425,10 +1651,10 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1425
1651
  type: "string",
1426
1652
  description: "Optional override cache directory.",
1427
1653
  },
1428
- repoRoot: {
1654
+ repoName: {
1429
1655
  type: "string",
1430
1656
  description:
1431
- "Repo root containing /projects. If you pass a project root, it will be normalized.",
1657
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1432
1658
  },
1433
1659
  },
1434
1660
  $comment: safeJsonStringify({
@@ -1460,7 +1686,7 @@ const buildArgsSchemaFromPlaceholders = (
1460
1686
  const TOOL_ARGS_SCHEMA_OVERRIDES: Record<string, Record<string, unknown>> = {
1461
1687
  "projects.listProjects": {
1462
1688
  type: "array",
1463
- description: "No positional arguments. Use options.repoRoot if needed.",
1689
+ description: "No positional arguments. Use options.repoName if needed.",
1464
1690
  minItems: 0,
1465
1691
  maxItems: 0,
1466
1692
  items: {},
@@ -1594,12 +1820,12 @@ const buildInvocationExample = (toolName: string): Record<string, unknown> => {
1594
1820
  }
1595
1821
 
1596
1822
  if (plan === "repoRootOnly") {
1597
- example.options = { repoRoot: "<repo-root>", ...defaultOptions };
1823
+ example.options = { repoName: "<repo-name>", ...defaultOptions };
1598
1824
  return example;
1599
1825
  }
1600
1826
 
1601
1827
  if (plan === "optionsThenRepoRoot" || plan === "repoRootThenOptions") {
1602
- example.options = { repoRoot: "<repo-root>", ...defaultOptions };
1828
+ example.options = { repoName: "<repo-name>", ...defaultOptions };
1603
1829
  return example;
1604
1830
  }
1605
1831
 
@@ -1630,10 +1856,10 @@ const defaultToolInputSchema = (toolName: string) => ({
1630
1856
  additionalProperties: true,
1631
1857
  description: "Named options",
1632
1858
  },
1633
- repoRoot: {
1859
+ repoName: {
1634
1860
  type: "string",
1635
1861
  description:
1636
- 'Repo root on the server filesystem (contains /projects and /agents). If omitted, uses the server default (auto-detected from cwd). If you have a project root like ".../projects/adl", omit this or pass its parent.',
1862
+ "Repo selector (LLM-friendly). Omit to use server default. Legacy path aliases: repoRoot/processRoot.",
1637
1863
  },
1638
1864
  },
1639
1865
  $comment: safeJsonStringify({
@@ -1868,10 +2094,14 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
1868
2094
  const invokeTool = async (
1869
2095
  tool: ToolDefinition,
1870
2096
  payload: unknown,
1871
- defaultRepoRoot: string,
2097
+ repoCtx: RepoResolutionContext,
1872
2098
  ): Promise<unknown> => {
1873
2099
  const normalized = normalizePayload(payload);
1874
- const { args, options } = coercePayloadForTool(tool.name, normalized);
2100
+ const coerced = coercePayloadForTool(tool.name, normalized);
2101
+ const { args, options } = {
2102
+ args: coerced.args,
2103
+ options: resolveRepoSelectorOptions(coerced.options, repoCtx),
2104
+ };
1875
2105
  const invoke =
1876
2106
  toolInvocationPlans[tool.name] ??
1877
2107
  ((rawArgs, rawOptions, _repoRoot) => {
@@ -1881,7 +2111,7 @@ const invokeTool = async (
1881
2111
  }
1882
2112
  return invocationArgs;
1883
2113
  });
1884
- const invocationArgs = invoke(args, options, defaultRepoRoot);
2114
+ const invocationArgs = invoke(args, options, repoCtx.defaultRepoRoot);
1885
2115
 
1886
2116
  return Promise.resolve(tool.method(...invocationArgs));
1887
2117
  };
@@ -1895,6 +2125,11 @@ export interface ExampleMcpServerOptions {
1895
2125
  * If omitted, the server attempts to auto-detect by walking up from cwd.
1896
2126
  */
1897
2127
  repoRoot?: string;
2128
+ /**
2129
+ * Optional default repo name (LLM-friendly identifier).
2130
+ * If omitted, defaults to the basename of the resolved repo root.
2131
+ */
2132
+ repoName?: string;
1898
2133
  allowedRootEndpoints?: string[];
1899
2134
  disableWrite?: boolean;
1900
2135
  enableIssues?: boolean;
@@ -2026,12 +2261,50 @@ export const createExampleMcpServer = (
2026
2261
  options: ExampleMcpServerOptions = {},
2027
2262
  ): ExampleMcpServerInstance => {
2028
2263
  let toolCatalog: unknown[] = [];
2029
- const defaultRepoRoot = normalizeRepoRoot(
2030
- options.repoRoot ??
2031
- process.env.MCP_REPO_ROOT ??
2032
- process.env.F0_REPO_ROOT ??
2033
- process.cwd(),
2034
- );
2264
+
2265
+ const configuredRepoRoot =
2266
+ options.repoRoot ?? process.env.MCP_REPO_ROOT ?? process.env.F0_REPO_ROOT;
2267
+ const cwd = process.cwd();
2268
+ const cwdNormalized = normalizeRepoRoot(cwd);
2269
+ const cwdLooksLikeRepoRoot = looksLikeRepoRoot(cwdNormalized);
2270
+ const cwdGitRoot = findGitRepoRoot(cwd);
2271
+ const serverFileDir = path.dirname(fileURLToPath(import.meta.url));
2272
+ const serverFileGitRoot = findGitRepoRoot(serverFileDir);
2273
+
2274
+ const rawDefaultRepoRoot =
2275
+ configuredRepoRoot ??
2276
+ (cwdLooksLikeRepoRoot ? cwdNormalized : null) ??
2277
+ cwdGitRoot ??
2278
+ serverFileGitRoot ??
2279
+ cwd ??
2280
+ serverFileDir;
2281
+ const defaultRepoRoot = normalizeRepoRoot(rawDefaultRepoRoot);
2282
+
2283
+ const configuredRepoName =
2284
+ (options.repoName ?? process.env.MCP_REPO_NAME ?? process.env.F0_REPO_NAME)?.trim() ||
2285
+ null;
2286
+ const gitDerivedRepoName =
2287
+ detectRepoNameFromGitConfig(defaultRepoRoot) ??
2288
+ (cwdGitRoot ? detectRepoNameFromGitConfig(cwdGitRoot) : null) ??
2289
+ (serverFileGitRoot ? detectRepoNameFromGitConfig(serverFileGitRoot) : null);
2290
+ const defaultRepoName =
2291
+ configuredRepoName || gitDerivedRepoName || path.basename(defaultRepoRoot);
2292
+ const repoMapRaw = process.env.MCP_REPOS ?? process.env.F0_REPOS;
2293
+ const repoMap = {
2294
+ ...parseRepoMap(repoMapRaw),
2295
+ [defaultRepoName]: defaultRepoRoot,
2296
+ };
2297
+ const repoNames = Object.keys(repoMap).sort((a, b) => a.localeCompare(b));
2298
+ const repoMapByKey: Record<string, string> = {};
2299
+ for (const [name, root] of Object.entries(repoMap)) {
2300
+ repoMapByKey[name.toLowerCase()] = root;
2301
+ }
2302
+ const repoCtx: RepoResolutionContext = {
2303
+ defaultRepoRoot,
2304
+ defaultRepoName,
2305
+ repoMapByKey,
2306
+ repoNames,
2307
+ };
2035
2308
 
2036
2309
  const parseString = (value: unknown): string | null => {
2037
2310
  if (typeof value !== "string") return null;
@@ -2071,7 +2344,7 @@ export const createExampleMcpServer = (
2071
2344
  "F0 MCP helper tools:",
2072
2345
  "- mcp.listTools: returns tool catalog with access + invocation hints",
2073
2346
  "- mcp.describeTool: describe one tool by name (prefixed or unprefixed)",
2074
- "- mcp.workspace: explain server filesystem context (cwd, repoRoot, projects)",
2347
+ "- mcp.workspace: explain server filesystem context (cwd, repoName/repoRoot, projects)",
2075
2348
  "- mcp.search: LLM-friendly search over project docs/spec (local-first)",
2076
2349
  "",
2077
2350
  'Tip: Prefer mcp.search for "search spec/docs" requests.',
@@ -2082,13 +2355,21 @@ export const createExampleMcpServer = (
2082
2355
  const received = isRecord(input)
2083
2356
  ? {
2084
2357
  keys: Object.keys(input),
2358
+ repoName: (input as any).repoName ?? null,
2085
2359
  repoRoot: (input as any).repoRoot ?? null,
2086
2360
  processRoot: (input as any).processRoot ?? null,
2087
2361
  }
2088
- : { keys: [], repoRoot: null, processRoot: null };
2089
- const raw =
2090
- parseString(payload.repoRoot) ?? parseString(payload.processRoot);
2091
- const repoRoot = raw ? normalizeRepoRoot(raw) : defaultRepoRoot;
2362
+ : { keys: [], repoName: null, repoRoot: null, processRoot: null };
2363
+ const requestedRepoName = parseString(payload.repoName);
2364
+ const resolved = resolveRepoSelectorOptions(payload, repoCtx);
2365
+ const repoRoot =
2366
+ typeof resolved.repoRoot === "string"
2367
+ ? resolved.repoRoot
2368
+ : repoCtx.defaultRepoRoot;
2369
+ const effectiveRepoName =
2370
+ requestedRepoName && !looksLikePathish(requestedRepoName)
2371
+ ? requestedRepoName
2372
+ : repoCtx.defaultRepoName;
2092
2373
 
2093
2374
  const projectsDir = path.join(repoRoot, "projects");
2094
2375
  const apiDir = path.join(repoRoot, "api");
@@ -2102,8 +2383,8 @@ export const createExampleMcpServer = (
2102
2383
  if (!hasProjectsDir) {
2103
2384
  return [
2104
2385
  "Repo does not contain /projects on the server filesystem.",
2105
- "Start the MCP server from the monorepo root (the folder that contains both /api and /projects), or pass repoRoot explicitly.",
2106
- "repoRoot is a server-side path, not a client/LLM path.",
2386
+ "Start the MCP server from the monorepo root (the folder that contains both /api and /projects), or configure the server with --repo-root / MCP_REPO_ROOT.",
2387
+ "Tool callers should usually omit repoName and let the server use its default workspace.",
2107
2388
  ].join(" ");
2108
2389
  }
2109
2390
  if (hasProjectsDir && projects.length === 0) {
@@ -2118,9 +2399,11 @@ export const createExampleMcpServer = (
2118
2399
  return {
2119
2400
  received,
2120
2401
  cwd: process.cwd(),
2121
- configuredRepoRoot: options.repoRoot ?? null,
2122
- defaultRepoRoot,
2402
+ defaultRepoName: repoCtx.defaultRepoName,
2403
+ defaultRepoRoot: repoCtx.defaultRepoRoot,
2404
+ repoName: effectiveRepoName,
2123
2405
  repoRoot,
2406
+ availableRepoNames: repoCtx.repoNames,
2124
2407
  hasProjectsDir,
2125
2408
  hasApiDir,
2126
2409
  hasAgentsDir,
@@ -2168,11 +2451,11 @@ export const createExampleMcpServer = (
2168
2451
  },
2169
2452
  search: async (input: unknown) => {
2170
2453
  const payload = isRecord(input) ? input : {};
2171
- const repoRoot = (() => {
2172
- const raw =
2173
- parseString(payload.repoRoot) ?? parseString(payload.processRoot);
2174
- return raw ? normalizeRepoRoot(raw) : defaultRepoRoot;
2175
- })();
2454
+ const resolved = resolveRepoSelectorOptions(payload, repoCtx);
2455
+ const repoRoot =
2456
+ typeof resolved.repoRoot === "string"
2457
+ ? resolved.repoRoot
2458
+ : repoCtx.defaultRepoRoot;
2176
2459
 
2177
2460
  const sectionRaw = parseString(payload.section)?.toLowerCase();
2178
2461
  const section = sectionRaw === "docs" ? "docs" : "spec";
@@ -2367,7 +2650,7 @@ export const createExampleMcpServer = (
2367
2650
  tool: prefix
2368
2651
  ? `${prefix}.projects.listProjects`
2369
2652
  : "projects.listProjects",
2370
- options: { repoRoot: "<repo-root>" },
2653
+ options: { repoName: "<repo-name>" },
2371
2654
  },
2372
2655
  ],
2373
2656
  continueOnError: true,
@@ -2546,8 +2829,9 @@ export const createExampleMcpServer = (
2546
2829
  /projects[\\/].+projects[\\/]/i.test(message)
2547
2830
  ) {
2548
2831
  details.hint =
2549
- "You likely passed a project root as repoRoot. repoRoot should be the repo root containing /projects. (Legacy alias: processRoot.)";
2550
- details.suggestion = "Call mcp.workspace to see cwd/repoRoot on the server.";
2832
+ "You likely passed a project root as repoName (or legacy repoRoot/processRoot). Repo selection should point at the monorepo root that contains /projects.";
2833
+ details.suggestion =
2834
+ "Call mcp.workspace to see the server’s cwd/default repoRoot, then omit repoName or pass the correct repoName.";
2551
2835
  }
2552
2836
 
2553
2837
  if (
@@ -2556,8 +2840,9 @@ export const createExampleMcpServer = (
2556
2840
  /projects[\\/]/i.test(message)
2557
2841
  ) {
2558
2842
  details.hint =
2559
- "repoRoot might be wrong, or the server filesystem does not contain /projects for this repoRoot.";
2560
- details.suggestion = "Call mcp.workspace to see cwd/repoRoot on the server.";
2843
+ "Repo selection might be wrong, or the server filesystem does not contain /projects for this workspace.";
2844
+ details.suggestion =
2845
+ "Call mcp.workspace to see the server’s cwd/default repoRoot and available repoName values.";
2561
2846
  details.example = {
2562
2847
  tool: prefix ? `${prefix}.mcp.workspace` : "mcp.workspace",
2563
2848
  arguments: {},
@@ -2625,11 +2910,7 @@ export const createExampleMcpServer = (
2625
2910
  }
2626
2911
 
2627
2912
  try {
2628
- const data = await invokeTool(
2629
- toolDefinition,
2630
- { args, options },
2631
- defaultRepoRoot,
2632
- );
2913
+ const data = await invokeTool(toolDefinition, { args, options }, repoCtx);
2633
2914
  return { index, tool, isError: false, data };
2634
2915
  } catch (error) {
2635
2916
  const message =
@@ -2681,7 +2962,7 @@ export const createExampleMcpServer = (
2681
2962
  }
2682
2963
 
2683
2964
  try {
2684
- const data = await invokeTool(tool, request.params.arguments, defaultRepoRoot);
2965
+ const data = await invokeTool(tool, request.params.arguments, repoCtx);
2685
2966
  return toolOk(data);
2686
2967
  } catch (error) {
2687
2968
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundation0/api",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Foundation 0 API",
5
5
  "type": "module",
6
6
  "bin": {