@foundation0/api 1.1.4 → 1.1.5
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/manual.md +150 -0
- package/mcp/server.test.ts +177 -3
- package/mcp/server.ts +173 -85
- package/package.json +1 -1
package/mcp/manual.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Foundation0 MCP Tool-Calling Manual (for LLMs)
|
|
2
|
+
|
|
3
|
+
This is a **tool-calling guide** for the Foundation0 MCP server in `api/mcp/*`.
|
|
4
|
+
Assume the server is already configured in your MCP host and you can call its tools.
|
|
5
|
+
|
|
6
|
+
## 0) Golden rules
|
|
7
|
+
|
|
8
|
+
1. If you are unsure about a tool name, call `mcp.listTools` first and use the exact name it returns (prefixes vary).
|
|
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).
|
|
12
|
+
5. Tool results are returned as a **JSON text envelope**: parse the text as JSON and check `ok`.
|
|
13
|
+
|
|
14
|
+
## 1) Tool naming (prefixes + aliases)
|
|
15
|
+
|
|
16
|
+
Depending on how the server was started, tool names may be:
|
|
17
|
+
|
|
18
|
+
- unprefixed: `projects.listProjects`
|
|
19
|
+
- prefixed: `api.projects.listProjects`
|
|
20
|
+
|
|
21
|
+
The server usually exposes both when a prefix is set, but do not assume: **discover at runtime** via `mcp.listTools`.
|
|
22
|
+
|
|
23
|
+
Some tools also have OpenAI-safe underscore aliases (no dots). Example:
|
|
24
|
+
|
|
25
|
+
- `net.curl` may also be available as `net_curl`
|
|
26
|
+
|
|
27
|
+
## 2) The 3 discovery calls
|
|
28
|
+
|
|
29
|
+
### A) List all tools
|
|
30
|
+
|
|
31
|
+
Tool call:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "name": "mcp.listTools", "arguments": {} }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### B) Describe one tool (schema + example)
|
|
38
|
+
|
|
39
|
+
Tool call (prefixed or unprefixed names both work here):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{ "name": "mcp.describeTool", "arguments": { "args": ["projects.listProjects"] } }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### C) `mcp.search` for "search docs/spec" requests
|
|
46
|
+
|
|
47
|
+
Tool call:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"name": "mcp.search",
|
|
52
|
+
"arguments": {
|
|
53
|
+
"projectName": "<project-name>",
|
|
54
|
+
"section": "spec",
|
|
55
|
+
"pattern": "authentication",
|
|
56
|
+
"repoRoot": "<repo-root>",
|
|
57
|
+
"ignoreCase": true,
|
|
58
|
+
"maxCount": 50
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 3) Payload shapes (important)
|
|
64
|
+
|
|
65
|
+
Most tools accept either:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{ "args": ["<project-name>"], "options": { "repoRoot": "<repo-root>" } }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
or named keys (recommended). The server merges top-level keys into `options`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{ "projectName": "<project-name>", "repoRoot": "<repo-root>" }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Guideline: if a tool has a natural named parameter (`projectName`, `agentName`, `target`, `taskRef`), pass it explicitly.
|
|
78
|
+
|
|
79
|
+
## 4) Common calls (examples)
|
|
80
|
+
|
|
81
|
+
### A) List projects
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"name": "projects.listProjects",
|
|
86
|
+
"arguments": { "repoRoot": "<repo-root>" }
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### B) List agents
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"name": "agents.listAgents",
|
|
95
|
+
"arguments": { "repoRoot": "<repo-root>" }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### C) Set an active file (projects)
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "projects.setActive",
|
|
104
|
+
"arguments": {
|
|
105
|
+
"args": ["<project-name>", "/implementation-plan.v0.0.1"],
|
|
106
|
+
"options": { "repoRoot": "<repo-root>", "latest": true }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### D) Batch multiple calls
|
|
112
|
+
|
|
113
|
+
Call `batch` (or `<prefix>.batch`) to run multiple tool calls:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"name": "batch",
|
|
118
|
+
"arguments": {
|
|
119
|
+
"calls": [
|
|
120
|
+
{ "tool": "projects.usage" },
|
|
121
|
+
{ "tool": "projects.listProjects", "options": { "repoRoot": "<repo-root>" } }
|
|
122
|
+
],
|
|
123
|
+
"continueOnError": true,
|
|
124
|
+
"maxConcurrency": 4
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 5) Reading responses (envelopes + errors)
|
|
130
|
+
|
|
131
|
+
Tool results are returned as text containing JSON like:
|
|
132
|
+
|
|
133
|
+
- success: `{ "ok": true, "result": ... }`
|
|
134
|
+
- error: `{ "ok": false, "error": { "message": "...", "details": { ... } } }`
|
|
135
|
+
|
|
136
|
+
If you get:
|
|
137
|
+
|
|
138
|
+
- **Unknown tool**: use the `suggestions` from the error (when present), or call `mcp.listTools` again and retry.
|
|
139
|
+
- **Missing project name**: pass `projectName` (or set `args[0]`).
|
|
140
|
+
- **Project folder not found: ...projects/.../projects/...**: you likely passed the wrong `repoRoot` (it must be the repo root; legacy alias `processRoot`).
|
|
141
|
+
|
|
142
|
+
## 6) Tool availability (read/write/admin)
|
|
143
|
+
|
|
144
|
+
The server can be started in modes that hide tools:
|
|
145
|
+
|
|
146
|
+
- read-only mode removes write-capable tools
|
|
147
|
+
- admin-only tools are hidden unless the server is started in admin mode
|
|
148
|
+
- root namespaces can be whitelisted (so entire namespaces may be missing)
|
|
149
|
+
|
|
150
|
+
If a tool is not listed by `mcp.listTools`, you cannot call it in the current server configuration.
|
package/mcp/server.test.ts
CHANGED
|
@@ -32,9 +32,17 @@ describe("createExampleMcpServer endpoint whitelist", () => {
|
|
|
32
32
|
const names = instance.tools.map((tool) => tool.name);
|
|
33
33
|
|
|
34
34
|
expect(names.length).toBeGreaterThan(0);
|
|
35
|
-
expect(
|
|
35
|
+
expect(
|
|
36
|
+
names.every(
|
|
37
|
+
(name) => name.startsWith("projects.") || name.startsWith("mcp."),
|
|
38
|
+
),
|
|
39
|
+
).toBe(true);
|
|
36
40
|
expect(names.some((name) => name.startsWith("agents."))).toBe(false);
|
|
37
41
|
expect(names.some((name) => name.startsWith("net."))).toBe(false);
|
|
42
|
+
expect(names).toContain("mcp.usage");
|
|
43
|
+
expect(names).toContain("mcp.listTools");
|
|
44
|
+
expect(names).toContain("mcp.describeTool");
|
|
45
|
+
expect(names).not.toContain("mcp.search");
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
it("throws on unknown root endpoints", () => {
|
|
@@ -77,12 +85,20 @@ describe("createExampleMcpServer endpoint whitelist", () => {
|
|
|
77
85
|
const names = instance.tools.map((tool) => tool.name);
|
|
78
86
|
|
|
79
87
|
expect(names.length).toBeGreaterThan(0);
|
|
80
|
-
expect(
|
|
88
|
+
expect(
|
|
89
|
+
names.every(
|
|
90
|
+
(name) => name.startsWith("projects.") || name.startsWith("mcp."),
|
|
91
|
+
),
|
|
92
|
+
).toBe(true);
|
|
81
93
|
expect(names).toContain("projects.readGitTask");
|
|
82
94
|
expect(names).toContain("projects.fetchGitTasks");
|
|
83
95
|
expect(names).not.toContain("projects.createGitIssue");
|
|
84
96
|
expect(names).not.toContain("projects.writeGitTask");
|
|
85
97
|
expect(names).not.toContain("projects.syncTasks");
|
|
98
|
+
expect(names).toContain("mcp.usage");
|
|
99
|
+
expect(names).toContain("mcp.listTools");
|
|
100
|
+
expect(names).toContain("mcp.describeTool");
|
|
101
|
+
expect(names).not.toContain("mcp.search");
|
|
86
102
|
});
|
|
87
103
|
|
|
88
104
|
it("re-enables issue write endpoints when enableIssues=true with disableWrite", () => {
|
|
@@ -186,6 +202,161 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
186
202
|
}
|
|
187
203
|
});
|
|
188
204
|
|
|
205
|
+
it("accepts repoRoot for projects tools (preferred over legacy processRoot)", async () => {
|
|
206
|
+
const tempDir = await fs.mkdtemp(
|
|
207
|
+
path.join(os.tmpdir(), "f0-mcp-server-reporoot-"),
|
|
208
|
+
);
|
|
209
|
+
try {
|
|
210
|
+
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
211
|
+
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
212
|
+
recursive: true,
|
|
213
|
+
});
|
|
214
|
+
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
215
|
+
recursive: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const instance = createExampleMcpServer();
|
|
219
|
+
const handler = getToolHandler(instance);
|
|
220
|
+
|
|
221
|
+
const result = await handler(
|
|
222
|
+
{
|
|
223
|
+
method: "tools/call",
|
|
224
|
+
params: {
|
|
225
|
+
name: "projects.listProjects",
|
|
226
|
+
arguments: {
|
|
227
|
+
repoRoot: tempDir,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.isError).toBe(false);
|
|
235
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
236
|
+
expect(payload.ok).toBe(true);
|
|
237
|
+
expect(payload.result).toContain("adl");
|
|
238
|
+
expect(payload.result).toContain("beta");
|
|
239
|
+
} finally {
|
|
240
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("auto-detects repoRoot from process.cwd() when repoRoot is omitted", async () => {
|
|
245
|
+
const tempDir = await fs.mkdtemp(
|
|
246
|
+
path.join(os.tmpdir(), "f0-mcp-server-autoreporoot-"),
|
|
247
|
+
);
|
|
248
|
+
const originalCwd = process.cwd();
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
252
|
+
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
253
|
+
recursive: true,
|
|
254
|
+
});
|
|
255
|
+
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
256
|
+
recursive: true,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
process.chdir(tempDir);
|
|
260
|
+
|
|
261
|
+
const instance = createExampleMcpServer();
|
|
262
|
+
const handler = getToolHandler(instance);
|
|
263
|
+
|
|
264
|
+
const result = await handler(
|
|
265
|
+
{
|
|
266
|
+
method: "tools/call",
|
|
267
|
+
params: {
|
|
268
|
+
name: "projects.listProjects",
|
|
269
|
+
arguments: {},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(result.isError).toBe(false);
|
|
276
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
277
|
+
expect(payload.ok).toBe(true);
|
|
278
|
+
expect(payload.result).toContain("adl");
|
|
279
|
+
expect(payload.result).toContain("beta");
|
|
280
|
+
} finally {
|
|
281
|
+
process.chdir(originalCwd);
|
|
282
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("still accepts legacy processRoot for backwards compatibility", async () => {
|
|
287
|
+
const tempDir = await fs.mkdtemp(
|
|
288
|
+
path.join(os.tmpdir(), "f0-mcp-server-processroot-"),
|
|
289
|
+
);
|
|
290
|
+
try {
|
|
291
|
+
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
292
|
+
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
293
|
+
recursive: true,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const instance = createExampleMcpServer();
|
|
297
|
+
const handler = getToolHandler(instance);
|
|
298
|
+
|
|
299
|
+
const result = await handler(
|
|
300
|
+
{
|
|
301
|
+
method: "tools/call",
|
|
302
|
+
params: {
|
|
303
|
+
name: "projects.listProjects",
|
|
304
|
+
arguments: {
|
|
305
|
+
processRoot: tempDir,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(result.isError).toBe(false);
|
|
313
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
314
|
+
expect(payload.ok).toBe(true);
|
|
315
|
+
expect(payload.result).toContain("adl");
|
|
316
|
+
} finally {
|
|
317
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("normalizes repoRoot when caller accidentally passes a project root", async () => {
|
|
322
|
+
const tempDir = await fs.mkdtemp(
|
|
323
|
+
path.join(os.tmpdir(), "f0-mcp-server-reporoot-normalize-"),
|
|
324
|
+
);
|
|
325
|
+
try {
|
|
326
|
+
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
327
|
+
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
328
|
+
recursive: true,
|
|
329
|
+
});
|
|
330
|
+
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
331
|
+
recursive: true,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const instance = createExampleMcpServer();
|
|
335
|
+
const handler = getToolHandler(instance);
|
|
336
|
+
|
|
337
|
+
const result = await handler(
|
|
338
|
+
{
|
|
339
|
+
method: "tools/call",
|
|
340
|
+
params: {
|
|
341
|
+
name: "projects.listProjects",
|
|
342
|
+
arguments: {
|
|
343
|
+
repoRoot: path.join(tempDir, "projects", "adl"),
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{},
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(result.isError).toBe(false);
|
|
351
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
352
|
+
expect(payload.ok).toBe(true);
|
|
353
|
+
expect(payload.result).toContain("adl");
|
|
354
|
+
expect(payload.result).toContain("beta");
|
|
355
|
+
} finally {
|
|
356
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
189
360
|
it('parses continueOnError from string "false" (fails fast)', async () => {
|
|
190
361
|
const instance = createExampleMcpServer();
|
|
191
362
|
const handler = getToolHandler(instance);
|
|
@@ -256,7 +427,10 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
256
427
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
257
428
|
expect(payload.ok).toBe(true);
|
|
258
429
|
expect(typeof payload.result).toBe("string");
|
|
259
|
-
expect(payload.result).toContain("
|
|
430
|
+
expect(payload.result).toContain("MCP");
|
|
431
|
+
expect(payload.result).toContain("projects.listProjects");
|
|
432
|
+
expect(payload.result).not.toContain("f0 projects");
|
|
433
|
+
expect(payload.result).not.toContain("--generate-spec");
|
|
260
434
|
});
|
|
261
435
|
|
|
262
436
|
it("exposes mcp.describeTool for tool discovery", async () => {
|
package/mcp/server.ts
CHANGED
|
@@ -48,6 +48,51 @@ type ApiEndpoint = {
|
|
|
48
48
|
mcp: ToolNamespace;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
const MCP_HELPER_TOOL_KEYS = ["usage", "listTools", "describeTool"] as const;
|
|
52
|
+
type McpHelperToolKey = (typeof MCP_HELPER_TOOL_KEYS)[number];
|
|
53
|
+
const MCP_HELPER_TOOL_NAMES = new Set<string>(
|
|
54
|
+
MCP_HELPER_TOOL_KEYS.map((key) => `mcp.${key}`),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const formatToolNameForUsage = (
|
|
58
|
+
toolName: string,
|
|
59
|
+
toolsPrefix: string | undefined,
|
|
60
|
+
): string => (toolsPrefix ? `${toolsPrefix}.${toolName}` : toolName);
|
|
61
|
+
|
|
62
|
+
const buildProjectsMcpUsage = (toolsPrefix: string | undefined): string => {
|
|
63
|
+
const tool = (name: string) => formatToolNameForUsage(name, toolsPrefix);
|
|
64
|
+
return [
|
|
65
|
+
"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 } }`,
|
|
69
|
+
"",
|
|
70
|
+
"Discovery:",
|
|
71
|
+
`- ${tool("mcp.listTools")}: list available tools (and their schemas)`,
|
|
72
|
+
`- ${tool("mcp.describeTool")}: { args: [\"projects.generateSpec\"] } for schema + example`,
|
|
73
|
+
"",
|
|
74
|
+
"Payload shape:",
|
|
75
|
+
"- Prefer { args: [...], options: {...} }. Top-level keys are also treated as options.",
|
|
76
|
+
].join("\n");
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildAgentsMcpUsage = (toolsPrefix: string | undefined): string => {
|
|
80
|
+
const tool = (name: string) => formatToolNameForUsage(name, toolsPrefix);
|
|
81
|
+
return [
|
|
82
|
+
"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 } }`,
|
|
86
|
+
"",
|
|
87
|
+
"Discovery:",
|
|
88
|
+
`- ${tool("mcp.listTools")}: list available tools (and their schemas)`,
|
|
89
|
+
`- ${tool("mcp.describeTool")}: { args: [\"agents.setActive\"] } for schema + example`,
|
|
90
|
+
"",
|
|
91
|
+
"Payload shape:",
|
|
92
|
+
"- Prefer { args: [...], options: {...} }. Top-level keys are also treated as options.",
|
|
93
|
+
].join("\n");
|
|
94
|
+
};
|
|
95
|
+
|
|
51
96
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
52
97
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
98
|
|
|
@@ -131,11 +176,22 @@ const isDir = (candidate: string): boolean => {
|
|
|
131
176
|
const looksLikeRepoRoot = (candidate: string): boolean =>
|
|
132
177
|
isDir(path.join(candidate, "projects")) && isDir(path.join(candidate, "api"));
|
|
133
178
|
|
|
134
|
-
const
|
|
179
|
+
const detectRepoRootFromCwd = (): string | null => {
|
|
180
|
+
let current = process.cwd();
|
|
181
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
182
|
+
if (looksLikeRepoRoot(current)) return current;
|
|
183
|
+
const parent = path.dirname(current);
|
|
184
|
+
if (parent === current) break;
|
|
185
|
+
current = parent;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const normalizeRepoRoot = (raw: string): string => {
|
|
135
191
|
const resolved = path.resolve(raw);
|
|
136
192
|
if (looksLikeRepoRoot(resolved)) return resolved;
|
|
137
193
|
|
|
138
|
-
// Common mistake: passing a project root like ".../projects/adl" as
|
|
194
|
+
// Common mistake: passing a project root like ".../projects/adl" as repoRoot.
|
|
139
195
|
// Try to find the containing repo root by walking up a few levels.
|
|
140
196
|
let current = resolved;
|
|
141
197
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
@@ -155,14 +211,28 @@ const normalizeProcessRoot = (raw: string): string => {
|
|
|
155
211
|
return resolved;
|
|
156
212
|
};
|
|
157
213
|
|
|
158
|
-
const
|
|
214
|
+
const normalizeRepoRootOption = (
|
|
159
215
|
options: Record<string, unknown>,
|
|
160
216
|
): Record<string, unknown> => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
217
|
+
const rawRepoRoot = typeof options.repoRoot === "string" ? options.repoRoot : null;
|
|
218
|
+
const rawProcessRoot =
|
|
219
|
+
typeof options.processRoot === "string" ? options.processRoot : null;
|
|
220
|
+
const raw = rawRepoRoot ?? rawProcessRoot;
|
|
221
|
+
|
|
222
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
223
|
+
const detected = detectRepoRootFromCwd();
|
|
224
|
+
return detected ? { ...options, repoRoot: detected } : options;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const trimmed = raw.trim();
|
|
228
|
+
const normalized = normalizeRepoRoot(trimmed);
|
|
229
|
+
|
|
230
|
+
const next: Record<string, unknown> = { ...options, repoRoot: normalized };
|
|
231
|
+
delete next.processRoot;
|
|
232
|
+
|
|
233
|
+
const alreadyCanonical =
|
|
234
|
+
rawRepoRoot !== null && rawRepoRoot === normalized && !("processRoot" in options);
|
|
235
|
+
return alreadyCanonical ? options : next;
|
|
166
236
|
};
|
|
167
237
|
|
|
168
238
|
type NormalizedToolPayload = {
|
|
@@ -283,7 +353,7 @@ const normalizePayload = (payload: unknown): NormalizedToolPayload => {
|
|
|
283
353
|
|
|
284
354
|
return {
|
|
285
355
|
args,
|
|
286
|
-
options:
|
|
356
|
+
options: normalizeRepoRootOption(options),
|
|
287
357
|
};
|
|
288
358
|
};
|
|
289
359
|
|
|
@@ -390,7 +460,7 @@ const coercePayloadForTool = (
|
|
|
390
460
|
|
|
391
461
|
switch (toolName) {
|
|
392
462
|
case "projects.listProjects": {
|
|
393
|
-
// No positional args.
|
|
463
|
+
// No positional args. repoRoot is handled via options.repoRoot + buildRepoRootOnly.
|
|
394
464
|
break;
|
|
395
465
|
}
|
|
396
466
|
case "projects.resolveProjectRoot":
|
|
@@ -480,7 +550,7 @@ const coercePayloadForTool = (
|
|
|
480
550
|
break;
|
|
481
551
|
}
|
|
482
552
|
|
|
483
|
-
return { args, options:
|
|
553
|
+
return { args, options: normalizeRepoRootOption(options) };
|
|
484
554
|
};
|
|
485
555
|
|
|
486
556
|
const normalizeBatchToolCall = (
|
|
@@ -791,7 +861,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
791
861
|
description: "Unused.",
|
|
792
862
|
additionalProperties: true,
|
|
793
863
|
},
|
|
794
|
-
|
|
864
|
+
repoRoot: {
|
|
795
865
|
type: "string",
|
|
796
866
|
description: "Unused.",
|
|
797
867
|
},
|
|
@@ -801,7 +871,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
801
871
|
type: "object",
|
|
802
872
|
additionalProperties: true,
|
|
803
873
|
properties: {
|
|
804
|
-
|
|
874
|
+
repoRoot: {
|
|
805
875
|
type: "string",
|
|
806
876
|
description:
|
|
807
877
|
'Repo root containing /projects and /agents. If you have a project root like ".../projects/adl", omit this or pass its parent.',
|
|
@@ -817,7 +887,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
817
887
|
type: "string",
|
|
818
888
|
description: 'Project name under /projects (e.g. "adl").',
|
|
819
889
|
},
|
|
820
|
-
|
|
890
|
+
repoRoot: {
|
|
821
891
|
type: "string",
|
|
822
892
|
description:
|
|
823
893
|
"Repo root containing /projects and /agents. If omitted, uses server cwd.",
|
|
@@ -843,7 +913,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
843
913
|
type: "string",
|
|
844
914
|
description: 'Project name under /projects (e.g. "adl").',
|
|
845
915
|
},
|
|
846
|
-
|
|
916
|
+
repoRoot: {
|
|
847
917
|
type: "string",
|
|
848
918
|
description: "Repo root containing /projects and /agents.",
|
|
849
919
|
},
|
|
@@ -873,7 +943,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
873
943
|
description:
|
|
874
944
|
'Doc path under docs/, starting with "docs/..." (or a bare filename in catalog).',
|
|
875
945
|
},
|
|
876
|
-
|
|
946
|
+
repoRoot: {
|
|
877
947
|
type: "string",
|
|
878
948
|
description: "Repo root containing /projects and /agents.",
|
|
879
949
|
},
|
|
@@ -950,7 +1020,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
950
1020
|
type: "string",
|
|
951
1021
|
description: "Remote repo override for gitea mode.",
|
|
952
1022
|
},
|
|
953
|
-
|
|
1023
|
+
repoRoot: {
|
|
954
1024
|
type: "string",
|
|
955
1025
|
description: "Repo root containing /projects and /agents.",
|
|
956
1026
|
},
|
|
@@ -1027,7 +1097,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1027
1097
|
type: "string",
|
|
1028
1098
|
description: "Remote repo override for gitea mode.",
|
|
1029
1099
|
},
|
|
1030
|
-
|
|
1100
|
+
repoRoot: {
|
|
1031
1101
|
type: "string",
|
|
1032
1102
|
description: "Repo root containing /projects and /agents.",
|
|
1033
1103
|
},
|
|
@@ -1143,7 +1213,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1143
1213
|
type: "boolean",
|
|
1144
1214
|
description: "If true, only return issues with TASK-* IDs.",
|
|
1145
1215
|
},
|
|
1146
|
-
|
|
1216
|
+
repoRoot: {
|
|
1147
1217
|
type: "string",
|
|
1148
1218
|
description: "Repo root containing /projects.",
|
|
1149
1219
|
},
|
|
@@ -1184,7 +1254,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1184
1254
|
type: "boolean",
|
|
1185
1255
|
description: "If true, restrict to issues with TASK-* payloads.",
|
|
1186
1256
|
},
|
|
1187
|
-
|
|
1257
|
+
repoRoot: {
|
|
1188
1258
|
type: "string",
|
|
1189
1259
|
description: "Repo root containing /projects.",
|
|
1190
1260
|
},
|
|
@@ -1241,7 +1311,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1241
1311
|
type: "string",
|
|
1242
1312
|
description: "Optional task signature.",
|
|
1243
1313
|
},
|
|
1244
|
-
|
|
1314
|
+
repoRoot: {
|
|
1245
1315
|
type: "string",
|
|
1246
1316
|
description: "Repo root containing /projects.",
|
|
1247
1317
|
},
|
|
@@ -1275,7 +1345,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1275
1345
|
items: { type: "string" },
|
|
1276
1346
|
description: "Labels to set.",
|
|
1277
1347
|
},
|
|
1278
|
-
|
|
1348
|
+
repoRoot: {
|
|
1279
1349
|
type: "string",
|
|
1280
1350
|
description: "Repo root containing /projects.",
|
|
1281
1351
|
},
|
|
@@ -1349,7 +1419,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1349
1419
|
type: "string",
|
|
1350
1420
|
description: "Optional override cache directory.",
|
|
1351
1421
|
},
|
|
1352
|
-
|
|
1422
|
+
repoRoot: {
|
|
1353
1423
|
type: "string",
|
|
1354
1424
|
description:
|
|
1355
1425
|
"Repo root containing /projects. If you pass a project root, it will be normalized.",
|
|
@@ -1384,7 +1454,7 @@ const buildArgsSchemaFromPlaceholders = (
|
|
|
1384
1454
|
const TOOL_ARGS_SCHEMA_OVERRIDES: Record<string, Record<string, unknown>> = {
|
|
1385
1455
|
"projects.listProjects": {
|
|
1386
1456
|
type: "array",
|
|
1387
|
-
description: "No positional arguments. Use options.
|
|
1457
|
+
description: "No positional arguments. Use options.repoRoot if needed.",
|
|
1388
1458
|
minItems: 0,
|
|
1389
1459
|
maxItems: 0,
|
|
1390
1460
|
items: {},
|
|
@@ -1499,9 +1569,9 @@ const getInvocationPlanName = (toolName: string): string => {
|
|
|
1499
1569
|
const plan = toolInvocationPlans[toolName];
|
|
1500
1570
|
if (!plan) return "default";
|
|
1501
1571
|
if (plan === buildOptionsOnly) return "optionsOnly";
|
|
1502
|
-
if (plan ===
|
|
1503
|
-
if (plan ===
|
|
1504
|
-
if (plan ===
|
|
1572
|
+
if (plan === buildOptionsThenRepoRoot) return "optionsThenRepoRoot";
|
|
1573
|
+
if (plan === buildRepoRootThenOptions) return "repoRootThenOptions";
|
|
1574
|
+
if (plan === buildRepoRootOnly) return "repoRootOnly";
|
|
1505
1575
|
return "custom";
|
|
1506
1576
|
};
|
|
1507
1577
|
|
|
@@ -1513,17 +1583,17 @@ const buildInvocationExample = (toolName: string): Record<string, unknown> => {
|
|
|
1513
1583
|
const example: Record<string, unknown> = {};
|
|
1514
1584
|
if (requiredArgs && requiredArgs.length > 0) {
|
|
1515
1585
|
example.args = [...requiredArgs];
|
|
1516
|
-
} else if (plan !== "
|
|
1586
|
+
} else if (plan !== "repoRootOnly") {
|
|
1517
1587
|
example.args = ["<arg0>"];
|
|
1518
1588
|
}
|
|
1519
1589
|
|
|
1520
|
-
if (plan === "
|
|
1521
|
-
example.options = {
|
|
1590
|
+
if (plan === "repoRootOnly") {
|
|
1591
|
+
example.options = { repoRoot: "<repo-root>", ...defaultOptions };
|
|
1522
1592
|
return example;
|
|
1523
1593
|
}
|
|
1524
1594
|
|
|
1525
|
-
if (plan === "
|
|
1526
|
-
example.options = {
|
|
1595
|
+
if (plan === "optionsThenRepoRoot" || plan === "repoRootThenOptions") {
|
|
1596
|
+
example.options = { repoRoot: "<repo-root>", ...defaultOptions };
|
|
1527
1597
|
return example;
|
|
1528
1598
|
}
|
|
1529
1599
|
|
|
@@ -1554,7 +1624,7 @@ const defaultToolInputSchema = (toolName: string) => ({
|
|
|
1554
1624
|
additionalProperties: true,
|
|
1555
1625
|
description: "Named options",
|
|
1556
1626
|
},
|
|
1557
|
-
|
|
1627
|
+
repoRoot: {
|
|
1558
1628
|
type: "string",
|
|
1559
1629
|
description:
|
|
1560
1630
|
'Repo root containing /projects and /agents. If you have a project root like ".../projects/adl", omit this or pass its parent.',
|
|
@@ -1686,43 +1756,43 @@ const buildOptionsOnly = (
|
|
|
1686
1756
|
return invocationArgs;
|
|
1687
1757
|
};
|
|
1688
1758
|
|
|
1689
|
-
const
|
|
1759
|
+
const buildOptionsThenRepoRoot = (
|
|
1690
1760
|
args: unknown[],
|
|
1691
1761
|
options: Record<string, unknown>,
|
|
1692
1762
|
): unknown[] => {
|
|
1693
1763
|
const invocationArgs: unknown[] = [...args];
|
|
1694
1764
|
const remaining = { ...options };
|
|
1695
|
-
const
|
|
1696
|
-
if (typeof
|
|
1697
|
-
delete remaining.
|
|
1765
|
+
const repoRoot = remaining.repoRoot;
|
|
1766
|
+
if (typeof repoRoot === "string") {
|
|
1767
|
+
delete remaining.repoRoot;
|
|
1698
1768
|
}
|
|
1699
1769
|
|
|
1700
1770
|
if (Object.keys(remaining).length > 0) {
|
|
1701
1771
|
invocationArgs.push(remaining);
|
|
1702
|
-
} else if (typeof
|
|
1703
|
-
// Preserve positional slot for signatures like fn(projectName, options?,
|
|
1772
|
+
} else if (typeof repoRoot === "string") {
|
|
1773
|
+
// Preserve positional slot for signatures like fn(projectName, options?, repoRoot?).
|
|
1704
1774
|
invocationArgs.push({});
|
|
1705
1775
|
}
|
|
1706
|
-
if (typeof
|
|
1707
|
-
invocationArgs.push(
|
|
1776
|
+
if (typeof repoRoot === "string") {
|
|
1777
|
+
invocationArgs.push(repoRoot);
|
|
1708
1778
|
}
|
|
1709
1779
|
|
|
1710
1780
|
return invocationArgs;
|
|
1711
1781
|
};
|
|
1712
1782
|
|
|
1713
|
-
const
|
|
1783
|
+
const buildRepoRootThenOptions = (
|
|
1714
1784
|
args: unknown[],
|
|
1715
1785
|
options: Record<string, unknown>,
|
|
1716
1786
|
): unknown[] => {
|
|
1717
1787
|
const invocationArgs: unknown[] = [...args];
|
|
1718
1788
|
const remaining = { ...options };
|
|
1719
|
-
const
|
|
1720
|
-
if (typeof
|
|
1721
|
-
delete remaining.
|
|
1789
|
+
const repoRoot = remaining.repoRoot;
|
|
1790
|
+
if (typeof repoRoot === "string") {
|
|
1791
|
+
delete remaining.repoRoot;
|
|
1722
1792
|
}
|
|
1723
1793
|
|
|
1724
|
-
if (typeof
|
|
1725
|
-
invocationArgs.push(
|
|
1794
|
+
if (typeof repoRoot === "string") {
|
|
1795
|
+
invocationArgs.push(repoRoot);
|
|
1726
1796
|
}
|
|
1727
1797
|
if (Object.keys(remaining).length > 0) {
|
|
1728
1798
|
invocationArgs.push(remaining);
|
|
@@ -1731,39 +1801,39 @@ const buildProcessRootThenOptions = (
|
|
|
1731
1801
|
return invocationArgs;
|
|
1732
1802
|
};
|
|
1733
1803
|
|
|
1734
|
-
const
|
|
1804
|
+
const buildRepoRootOnly = (
|
|
1735
1805
|
args: unknown[],
|
|
1736
1806
|
options: Record<string, unknown>,
|
|
1737
1807
|
): unknown[] => {
|
|
1738
1808
|
const invocationArgs: unknown[] = [...args];
|
|
1739
|
-
const
|
|
1740
|
-
if (typeof
|
|
1741
|
-
invocationArgs.push(
|
|
1809
|
+
const repoRoot = options.repoRoot;
|
|
1810
|
+
if (typeof repoRoot === "string") {
|
|
1811
|
+
invocationArgs.push(repoRoot);
|
|
1742
1812
|
}
|
|
1743
1813
|
return invocationArgs;
|
|
1744
1814
|
};
|
|
1745
1815
|
|
|
1746
1816
|
const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
1747
|
-
"agents.setActive":
|
|
1748
|
-
"agents.resolveAgentsRootFrom":
|
|
1749
|
-
"projects.setActive":
|
|
1750
|
-
"projects.generateSpec":
|
|
1751
|
-
"projects.syncTasks":
|
|
1752
|
-
"projects.clearIssues":
|
|
1753
|
-
"projects.fetchGitTasks":
|
|
1754
|
-
"projects.createGitIssue":
|
|
1755
|
-
"projects.readGitTask":
|
|
1756
|
-
"projects.writeGitTask":
|
|
1817
|
+
"agents.setActive": buildRepoRootThenOptions,
|
|
1818
|
+
"agents.resolveAgentsRootFrom": buildRepoRootOnly,
|
|
1819
|
+
"projects.setActive": buildRepoRootThenOptions,
|
|
1820
|
+
"projects.generateSpec": buildOptionsThenRepoRoot,
|
|
1821
|
+
"projects.syncTasks": buildOptionsThenRepoRoot,
|
|
1822
|
+
"projects.clearIssues": buildOptionsThenRepoRoot,
|
|
1823
|
+
"projects.fetchGitTasks": buildOptionsThenRepoRoot,
|
|
1824
|
+
"projects.createGitIssue": buildOptionsThenRepoRoot,
|
|
1825
|
+
"projects.readGitTask": buildOptionsThenRepoRoot,
|
|
1826
|
+
"projects.writeGitTask": buildOptionsThenRepoRoot,
|
|
1757
1827
|
"agents.resolveTargetFile": buildOptionsOnly,
|
|
1758
1828
|
"projects.resolveProjectTargetFile": buildOptionsOnly,
|
|
1759
|
-
"agents.loadAgent":
|
|
1760
|
-
"agents.loadAgentPrompt":
|
|
1829
|
+
"agents.loadAgent": buildRepoRootOnly,
|
|
1830
|
+
"agents.loadAgentPrompt": buildRepoRootOnly,
|
|
1761
1831
|
"projects.resolveImplementationPlan": (args, options) => {
|
|
1762
1832
|
const invocationArgs: unknown[] = [...args];
|
|
1763
1833
|
const remaining = { ...options };
|
|
1764
|
-
const
|
|
1765
|
-
if (typeof
|
|
1766
|
-
delete remaining.
|
|
1834
|
+
const repoRoot = remaining.repoRoot;
|
|
1835
|
+
if (typeof repoRoot === "string") {
|
|
1836
|
+
delete remaining.repoRoot;
|
|
1767
1837
|
}
|
|
1768
1838
|
|
|
1769
1839
|
// This tool is a low-level helper: projects.resolveImplementationPlan(projectRoot, inputFile?, options?)
|
|
@@ -1775,17 +1845,17 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
|
1775
1845
|
invocationArgs.push(remaining);
|
|
1776
1846
|
}
|
|
1777
1847
|
|
|
1778
|
-
// Intentionally do NOT append
|
|
1848
|
+
// Intentionally do NOT append repoRoot: projectRoot is the first positional argument.
|
|
1779
1849
|
return invocationArgs;
|
|
1780
1850
|
},
|
|
1781
|
-
"agents.main":
|
|
1782
|
-
"agents.resolveAgentsRoot":
|
|
1783
|
-
"agents.listAgents":
|
|
1784
|
-
"projects.resolveProjectRoot":
|
|
1785
|
-
"projects.listProjects":
|
|
1786
|
-
"projects.listProjectDocs":
|
|
1787
|
-
"projects.readProjectDoc":
|
|
1788
|
-
"projects.main":
|
|
1851
|
+
"agents.main": buildRepoRootOnly,
|
|
1852
|
+
"agents.resolveAgentsRoot": buildRepoRootOnly,
|
|
1853
|
+
"agents.listAgents": buildRepoRootOnly,
|
|
1854
|
+
"projects.resolveProjectRoot": buildRepoRootOnly,
|
|
1855
|
+
"projects.listProjects": buildRepoRootOnly,
|
|
1856
|
+
"projects.listProjectDocs": buildRepoRootOnly,
|
|
1857
|
+
"projects.readProjectDoc": buildRepoRootOnly,
|
|
1858
|
+
"projects.main": buildRepoRootOnly,
|
|
1789
1859
|
};
|
|
1790
1860
|
|
|
1791
1861
|
const invokeTool = async (
|
|
@@ -2026,9 +2096,10 @@ export const createExampleMcpServer = (
|
|
|
2026
2096
|
},
|
|
2027
2097
|
search: async (input: unknown) => {
|
|
2028
2098
|
const payload = isRecord(input) ? input : {};
|
|
2029
|
-
const
|
|
2030
|
-
const raw =
|
|
2031
|
-
|
|
2099
|
+
const repoRoot = (() => {
|
|
2100
|
+
const raw =
|
|
2101
|
+
parseString(payload.repoRoot) ?? parseString(payload.processRoot);
|
|
2102
|
+
return raw ? normalizeRepoRoot(raw) : process.cwd();
|
|
2032
2103
|
})();
|
|
2033
2104
|
|
|
2034
2105
|
const sectionRaw = parseString(payload.section)?.toLowerCase();
|
|
@@ -2076,11 +2147,11 @@ export const createExampleMcpServer = (
|
|
|
2076
2147
|
|
|
2077
2148
|
const projectName = ensureProjectName(
|
|
2078
2149
|
parseString(payload.projectName),
|
|
2079
|
-
|
|
2150
|
+
repoRoot,
|
|
2080
2151
|
);
|
|
2081
2152
|
|
|
2082
2153
|
const searchOptions: Record<string, unknown> = {
|
|
2083
|
-
processRoot,
|
|
2154
|
+
processRoot: repoRoot,
|
|
2084
2155
|
};
|
|
2085
2156
|
|
|
2086
2157
|
const source = parseString(payload.source)?.toLowerCase();
|
|
@@ -2106,9 +2177,15 @@ export const createExampleMcpServer = (
|
|
|
2106
2177
|
} satisfies ToolNamespace;
|
|
2107
2178
|
|
|
2108
2179
|
const api: ApiEndpoint = {
|
|
2109
|
-
agents:
|
|
2180
|
+
agents: {
|
|
2181
|
+
...agentsApi,
|
|
2182
|
+
usage: () => buildAgentsMcpUsage(options.toolsPrefix),
|
|
2183
|
+
},
|
|
2110
2184
|
net: netApi,
|
|
2111
|
-
projects:
|
|
2185
|
+
projects: {
|
|
2186
|
+
...projectsApi,
|
|
2187
|
+
usage: () => buildProjectsMcpUsage(options.toolsPrefix),
|
|
2188
|
+
},
|
|
2112
2189
|
mcp: mcpApi,
|
|
2113
2190
|
};
|
|
2114
2191
|
|
|
@@ -2140,7 +2217,18 @@ export const createExampleMcpServer = (
|
|
|
2140
2217
|
const selectedTools = selectedRoots.flatMap((root) =>
|
|
2141
2218
|
collectTools(api[root], [root]),
|
|
2142
2219
|
);
|
|
2143
|
-
const
|
|
2220
|
+
const selectedToolsWithMcpHelpers = (() => {
|
|
2221
|
+
if (selectedRoots.includes("mcp")) return selectedTools;
|
|
2222
|
+
const helperApi: Partial<Record<McpHelperToolKey, unknown>> = {};
|
|
2223
|
+
for (const key of MCP_HELPER_TOOL_KEYS) {
|
|
2224
|
+
helperApi[key] = (mcpApi as any)[key];
|
|
2225
|
+
}
|
|
2226
|
+
const helperTools = collectTools(helperApi as ToolNamespace, ["mcp"]).filter(
|
|
2227
|
+
(tool) => MCP_HELPER_TOOL_NAMES.has(tool.name),
|
|
2228
|
+
);
|
|
2229
|
+
return [...helperTools, ...selectedTools];
|
|
2230
|
+
})();
|
|
2231
|
+
const selectedToolsWithAliases = applyToolAliases(selectedToolsWithMcpHelpers);
|
|
2144
2232
|
buildToolMeta(selectedToolsWithAliases);
|
|
2145
2233
|
const adminFilteredTools = Boolean(options.admin)
|
|
2146
2234
|
? selectedToolsWithAliases
|
|
@@ -2207,7 +2295,7 @@ export const createExampleMcpServer = (
|
|
|
2207
2295
|
tool: prefix
|
|
2208
2296
|
? `${prefix}.projects.listProjects`
|
|
2209
2297
|
: "projects.listProjects",
|
|
2210
|
-
options: {
|
|
2298
|
+
options: { repoRoot: "<repo-root>" },
|
|
2211
2299
|
},
|
|
2212
2300
|
],
|
|
2213
2301
|
continueOnError: true,
|
|
@@ -2386,7 +2474,7 @@ export const createExampleMcpServer = (
|
|
|
2386
2474
|
/projects[\\/].+projects[\\/]/i.test(message)
|
|
2387
2475
|
) {
|
|
2388
2476
|
details.hint =
|
|
2389
|
-
"You likely passed a project root as
|
|
2477
|
+
"You likely passed a project root as repoRoot. repoRoot should be the repo root containing /projects. (Legacy alias: processRoot.)";
|
|
2390
2478
|
}
|
|
2391
2479
|
|
|
2392
2480
|
if (/Missing search pattern\./i.test(message)) {
|