@foundation0/api 1.1.7 → 1.1.10
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 +0 -10
- package/mcp/manual.md +161 -161
- package/mcp/server.test.ts +170 -53
- package/mcp/server.ts +340 -243
- package/package.json +1 -1
- package/projects.ts +27 -3
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 = {
|
|
@@ -173,109 +174,176 @@ const isDir = (candidate: string): boolean => {
|
|
|
173
174
|
}
|
|
174
175
|
};
|
|
175
176
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
const fileExists = (candidate: string): boolean => {
|
|
178
|
+
try {
|
|
179
|
+
fs.statSync(candidate);
|
|
180
|
+
return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
let
|
|
186
|
-
|
|
186
|
+
const findGitRepoRoot = (startDir: string): string | null => {
|
|
187
|
+
let current = path.resolve(startDir);
|
|
188
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
189
|
+
const dotGit = path.join(current, ".git");
|
|
190
|
+
if (isDir(dotGit) || fileExists(dotGit)) return current;
|
|
187
191
|
const parent = path.dirname(current);
|
|
188
|
-
if (parent === current)
|
|
189
|
-
if (looksLikeRepoRoot(parent)) return parent;
|
|
192
|
+
if (parent === current) return null;
|
|
190
193
|
current = parent;
|
|
191
194
|
}
|
|
195
|
+
return null;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const resolveGitDir = (workspaceRoot: string): string | null => {
|
|
199
|
+
const dotGit = path.join(workspaceRoot, ".git");
|
|
200
|
+
if (isDir(dotGit)) return dotGit;
|
|
201
|
+
if (!fileExists(dotGit)) return null;
|
|
192
202
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
try {
|
|
204
|
+
const content = fs.readFileSync(dotGit, "utf8");
|
|
205
|
+
const match = content.match(/^\s*gitdir:\s*(.+)\s*$/im);
|
|
206
|
+
if (!match) return null;
|
|
207
|
+
const raw = match[1].trim();
|
|
208
|
+
if (!raw) return null;
|
|
209
|
+
const resolved = path.resolve(workspaceRoot, raw);
|
|
210
|
+
return isDir(resolved) ? resolved : null;
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
198
213
|
}
|
|
214
|
+
};
|
|
199
215
|
|
|
200
|
-
|
|
216
|
+
type GitRemoteIdentity = {
|
|
217
|
+
owner: string | null;
|
|
218
|
+
repo: string;
|
|
219
|
+
fullName: string | null;
|
|
201
220
|
};
|
|
202
221
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
):
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
const parseGitRemoteIdentityFromUrl = (
|
|
223
|
+
remoteUrl: string,
|
|
224
|
+
): GitRemoteIdentity | null => {
|
|
225
|
+
const trimmed = remoteUrl.trim();
|
|
226
|
+
if (!trimmed) return null;
|
|
227
|
+
|
|
228
|
+
const withoutHash = trimmed.split("#")[0] ?? trimmed;
|
|
229
|
+
const withoutQuery = withoutHash.split("?")[0] ?? withoutHash;
|
|
230
|
+
const withoutGit = withoutQuery.endsWith(".git")
|
|
231
|
+
? withoutQuery.slice(0, -4)
|
|
232
|
+
: withoutQuery;
|
|
233
|
+
const normalized = withoutGit.replace(/\\/g, "/");
|
|
234
|
+
const scpPathMatch = normalized.match(/^[^@]+@[^:]+:(.+)$/);
|
|
235
|
+
let pathPart = scpPathMatch?.[1] ?? normalized;
|
|
236
|
+
|
|
237
|
+
if (!scpPathMatch) {
|
|
238
|
+
try {
|
|
239
|
+
const url = new URL(normalized);
|
|
240
|
+
pathPart = url.pathname;
|
|
241
|
+
} catch {
|
|
242
|
+
// Keep normalized value as-is.
|
|
243
|
+
}
|
|
216
244
|
}
|
|
217
245
|
|
|
218
|
-
const
|
|
219
|
-
|
|
246
|
+
const cleanPath = pathPart.replace(/^\/+|\/+$/g, "");
|
|
247
|
+
if (!cleanPath) return null;
|
|
248
|
+
const segments = cleanPath
|
|
249
|
+
.split("/")
|
|
250
|
+
.map((segment) => segment.trim())
|
|
251
|
+
.filter((segment) => segment.length > 0);
|
|
252
|
+
if (segments.length === 0) return null;
|
|
253
|
+
const repo = segments[segments.length - 1];
|
|
254
|
+
if (!repo) return null;
|
|
255
|
+
const owner = segments.length > 1 ? segments[segments.length - 2] : null;
|
|
256
|
+
return {
|
|
257
|
+
owner,
|
|
258
|
+
repo,
|
|
259
|
+
fullName: owner ? `${owner}/${repo}` : null,
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const readGitRemoteUrl = (gitDir: string): string | null => {
|
|
264
|
+
const configPath = path.join(gitDir, "config");
|
|
265
|
+
if (!fileExists(configPath)) return null;
|
|
266
|
+
try {
|
|
267
|
+
const config = fs.readFileSync(configPath, "utf8");
|
|
268
|
+
let currentRemote: string | null = null;
|
|
269
|
+
const remoteUrls = new Map<string, string>();
|
|
270
|
+
|
|
271
|
+
for (const rawLine of config.split(/\r?\n/)) {
|
|
272
|
+
const line = rawLine.trim();
|
|
273
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
274
|
+
|
|
275
|
+
const sectionMatch = line.match(/^\[\s*([^\s\]]+)(?:\s+"([^"]+)")?\s*\]\s*$/);
|
|
276
|
+
if (sectionMatch) {
|
|
277
|
+
const section = sectionMatch[1].toLowerCase();
|
|
278
|
+
const name = sectionMatch[2] ?? null;
|
|
279
|
+
currentRemote = section === "remote" ? name : null;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
220
282
|
|
|
221
|
-
|
|
222
|
-
|
|
283
|
+
if (!currentRemote) continue;
|
|
284
|
+
const kvMatch = line.match(/^([A-Za-z0-9][-A-Za-z0-9]*)\s*=\s*(.*)$/);
|
|
285
|
+
if (!kvMatch) continue;
|
|
286
|
+
const key = kvMatch[1].toLowerCase();
|
|
287
|
+
if (key !== "url") continue;
|
|
288
|
+
const value = kvMatch[2].trim().replace(/^"(.*)"$/, "$1").trim();
|
|
289
|
+
if (value) remoteUrls.set(currentRemote, value);
|
|
290
|
+
}
|
|
223
291
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
292
|
+
return remoteUrls.get("origin") ?? remoteUrls.values().next().value ?? null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
227
296
|
};
|
|
228
297
|
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
298
|
+
const detectRepoIdentityFromGitConfig = (
|
|
299
|
+
workspaceRoot: string,
|
|
300
|
+
): GitRemoteIdentity | null => {
|
|
301
|
+
const gitDir = resolveGitDir(workspaceRoot);
|
|
302
|
+
if (!gitDir) return null;
|
|
303
|
+
const remoteUrl = readGitRemoteUrl(gitDir);
|
|
304
|
+
if (!remoteUrl) return null;
|
|
305
|
+
return parseGitRemoteIdentityFromUrl(remoteUrl);
|
|
234
306
|
};
|
|
235
307
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
308
|
+
type RepoResolutionContext = {
|
|
309
|
+
defaultProcessRoot: string;
|
|
310
|
+
defaultRepoName: string;
|
|
311
|
+
defaultRepoFullName: string | null;
|
|
312
|
+
repoNames: string[];
|
|
313
|
+
repoNameByKey: Record<string, string>;
|
|
314
|
+
};
|
|
239
315
|
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const parsed = JSON.parse(input);
|
|
243
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
244
|
-
return {};
|
|
245
|
-
}
|
|
246
|
-
const out: Record<string, string> = {};
|
|
247
|
-
for (const [name, root] of Object.entries(parsed)) {
|
|
248
|
-
if (!name || typeof root !== "string" || !root.trim()) continue;
|
|
249
|
-
out[name.trim()] = root.trim();
|
|
250
|
-
}
|
|
251
|
-
return out;
|
|
252
|
-
} catch {
|
|
253
|
-
return {};
|
|
254
|
-
}
|
|
255
|
-
}
|
|
316
|
+
const repoNameKey = (value: string): string => value.trim().toLowerCase();
|
|
256
317
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (!key || !value) continue;
|
|
269
|
-
out[key] = value;
|
|
318
|
+
const uniqueStrings = (values: Array<string | null | undefined>): string[] => {
|
|
319
|
+
const seen = new Set<string>();
|
|
320
|
+
const out: string[] = [];
|
|
321
|
+
for (const value of values) {
|
|
322
|
+
if (!value) continue;
|
|
323
|
+
const trimmed = value.trim();
|
|
324
|
+
if (!trimmed) continue;
|
|
325
|
+
const key = repoNameKey(trimmed);
|
|
326
|
+
if (seen.has(key)) continue;
|
|
327
|
+
seen.add(key);
|
|
328
|
+
out.push(trimmed);
|
|
270
329
|
}
|
|
271
330
|
return out;
|
|
272
331
|
};
|
|
273
332
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
repoNames
|
|
333
|
+
const buildRepoNameContext = (
|
|
334
|
+
defaultRepoName: string,
|
|
335
|
+
defaultRepoFullName: string | null,
|
|
336
|
+
): { repoNames: string[]; repoNameByKey: Record<string, string> } => {
|
|
337
|
+
const repoNames = uniqueStrings([
|
|
338
|
+
defaultRepoName,
|
|
339
|
+
defaultRepoFullName,
|
|
340
|
+
defaultRepoFullName?.split("/").at(-1) ?? null,
|
|
341
|
+
]);
|
|
342
|
+
const repoNameByKey: Record<string, string> = {};
|
|
343
|
+
for (const name of repoNames) {
|
|
344
|
+
repoNameByKey[repoNameKey(name)] = name;
|
|
345
|
+
}
|
|
346
|
+
return { repoNames, repoNameByKey };
|
|
279
347
|
};
|
|
280
348
|
|
|
281
349
|
const resolveRepoSelectorOptions = (
|
|
@@ -283,68 +351,100 @@ const resolveRepoSelectorOptions = (
|
|
|
283
351
|
ctx: RepoResolutionContext,
|
|
284
352
|
): Record<string, unknown> => {
|
|
285
353
|
const next: Record<string, unknown> = { ...options };
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
(typeof next.repoRoot === "string" && next.repoRoot.trim().length > 0
|
|
289
|
-
? next.repoRoot.trim()
|
|
290
|
-
: null) ??
|
|
291
|
-
(typeof next.processRoot === "string" && next.processRoot.trim().length > 0
|
|
292
|
-
? next.processRoot.trim()
|
|
293
|
-
: null);
|
|
294
|
-
|
|
295
|
-
if (explicitRoot) {
|
|
296
|
-
next.repoRoot = normalizeRepoRoot(explicitRoot);
|
|
297
|
-
delete next.processRoot;
|
|
298
|
-
delete next.repoName;
|
|
299
|
-
return next;
|
|
300
|
-
}
|
|
354
|
+
delete next.repoRoot;
|
|
355
|
+
delete next.processRoot;
|
|
301
356
|
|
|
302
357
|
const repoName =
|
|
303
358
|
typeof next.repoName === "string" && next.repoName.trim().length > 0
|
|
304
359
|
? next.repoName.trim()
|
|
305
360
|
: null;
|
|
306
361
|
if (!repoName) {
|
|
307
|
-
|
|
362
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
308
363
|
return next;
|
|
309
364
|
}
|
|
310
365
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
366
|
+
const needle = repoNameKey(repoName);
|
|
367
|
+
const canonical = ctx.repoNameByKey[needle];
|
|
368
|
+
if (canonical) {
|
|
369
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
370
|
+
next.repoName = canonical;
|
|
315
371
|
return next;
|
|
316
372
|
}
|
|
317
373
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
next.
|
|
321
|
-
delete next.processRoot;
|
|
322
|
-
delete next.repoName;
|
|
374
|
+
if (ctx.defaultRepoFullName && needle === repoNameKey(ctx.defaultRepoFullName)) {
|
|
375
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
376
|
+
next.repoName = ctx.defaultRepoFullName;
|
|
323
377
|
return next;
|
|
324
378
|
}
|
|
325
379
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
next.
|
|
329
|
-
delete next.processRoot;
|
|
330
|
-
delete next.repoName;
|
|
380
|
+
if (needle === repoNameKey(ctx.defaultRepoName)) {
|
|
381
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
382
|
+
next.repoName = ctx.defaultRepoName;
|
|
331
383
|
return next;
|
|
332
384
|
}
|
|
333
385
|
|
|
386
|
+
const slashParts = repoName.split("/").filter((part) => part.trim().length > 0);
|
|
387
|
+
if (slashParts.length >= 2) {
|
|
388
|
+
const repoSegment = slashParts[slashParts.length - 1]?.trim();
|
|
389
|
+
if (repoSegment) {
|
|
390
|
+
const matched = ctx.repoNameByKey[repoNameKey(repoSegment)];
|
|
391
|
+
if (matched) {
|
|
392
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
393
|
+
next.repoName = matched;
|
|
394
|
+
return next;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
334
399
|
const suggestions = ctx.repoNames
|
|
335
|
-
.filter((name) => name
|
|
400
|
+
.filter((name) => repoNameKey(name).includes(needle))
|
|
336
401
|
.slice(0, 8);
|
|
337
402
|
const hint =
|
|
338
403
|
suggestions.length > 0
|
|
339
404
|
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
340
|
-
: ctx.repoNames.
|
|
341
|
-
? ` Available repos: ${ctx.repoNames.join(", ")}.`
|
|
342
|
-
: "";
|
|
405
|
+
: ` Available repoName values: ${ctx.repoNames.join(", ")}.`;
|
|
343
406
|
throw new Error(
|
|
344
|
-
`Unknown repoName: ${repoName}.${hint} Tip:
|
|
407
|
+
`Unknown repoName: ${repoName}.${hint} Tip: use "adl" or "F0/adl" style selectors.`,
|
|
345
408
|
);
|
|
346
409
|
};
|
|
347
410
|
|
|
411
|
+
const normalizeRepoNameForDisplay = (
|
|
412
|
+
requestedRepoName: string | null,
|
|
413
|
+
ctx: RepoResolutionContext,
|
|
414
|
+
): string => {
|
|
415
|
+
if (!requestedRepoName) return ctx.defaultRepoName;
|
|
416
|
+
const canonical = ctx.repoNameByKey[repoNameKey(requestedRepoName)];
|
|
417
|
+
return canonical ?? requestedRepoName;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const resolveProcessRootFromGit = (fallbackDir: string): string => {
|
|
421
|
+
const cwdRoot = findGitRepoRoot(process.cwd());
|
|
422
|
+
if (cwdRoot) return cwdRoot;
|
|
423
|
+
const fallbackRoot = findGitRepoRoot(fallbackDir);
|
|
424
|
+
if (fallbackRoot) return fallbackRoot;
|
|
425
|
+
return path.resolve(process.cwd());
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const resolveRepoIdentityFromGit = (
|
|
429
|
+
processRoot: string,
|
|
430
|
+
): {
|
|
431
|
+
defaultRepoName: string;
|
|
432
|
+
defaultRepoFullName: string | null;
|
|
433
|
+
} => {
|
|
434
|
+
const gitIdentity = detectRepoIdentityFromGitConfig(processRoot);
|
|
435
|
+
if (gitIdentity) {
|
|
436
|
+
return {
|
|
437
|
+
defaultRepoName: gitIdentity.repo,
|
|
438
|
+
defaultRepoFullName: gitIdentity.fullName,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
defaultRepoName: path.basename(processRoot),
|
|
444
|
+
defaultRepoFullName: null,
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
|
|
348
448
|
type NormalizedToolPayload = {
|
|
349
449
|
args: unknown[];
|
|
350
450
|
options: Record<string, unknown>;
|
|
@@ -463,7 +563,7 @@ const normalizePayload = (payload: unknown): NormalizedToolPayload => {
|
|
|
463
563
|
|
|
464
564
|
return {
|
|
465
565
|
args,
|
|
466
|
-
options
|
|
566
|
+
options,
|
|
467
567
|
};
|
|
468
568
|
};
|
|
469
569
|
|
|
@@ -570,7 +670,7 @@ const coercePayloadForTool = (
|
|
|
570
670
|
|
|
571
671
|
switch (toolName) {
|
|
572
672
|
case "projects.listProjects": {
|
|
573
|
-
// No positional args.
|
|
673
|
+
// No positional args. processRoot is injected from the selected repoName.
|
|
574
674
|
break;
|
|
575
675
|
}
|
|
576
676
|
case "projects.resolveProjectRoot":
|
|
@@ -660,7 +760,7 @@ const coercePayloadForTool = (
|
|
|
660
760
|
break;
|
|
661
761
|
}
|
|
662
762
|
|
|
663
|
-
return { args, options
|
|
763
|
+
return { args, options };
|
|
664
764
|
};
|
|
665
765
|
|
|
666
766
|
const normalizeBatchToolCall = (
|
|
@@ -1000,7 +1100,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1000
1100
|
repoName: {
|
|
1001
1101
|
type: "string",
|
|
1002
1102
|
description:
|
|
1003
|
-
|
|
1103
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1004
1104
|
},
|
|
1005
1105
|
args: {
|
|
1006
1106
|
type: "array",
|
|
@@ -1026,7 +1126,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1026
1126
|
repoName: {
|
|
1027
1127
|
type: "string",
|
|
1028
1128
|
description:
|
|
1029
|
-
|
|
1129
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1030
1130
|
},
|
|
1031
1131
|
args: {
|
|
1032
1132
|
type: "array",
|
|
@@ -1057,7 +1157,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1057
1157
|
repoName: {
|
|
1058
1158
|
type: "string",
|
|
1059
1159
|
description:
|
|
1060
|
-
|
|
1160
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1061
1161
|
},
|
|
1062
1162
|
args: {
|
|
1063
1163
|
type: "array",
|
|
@@ -1135,7 +1235,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1135
1235
|
repoName: {
|
|
1136
1236
|
type: "string",
|
|
1137
1237
|
description:
|
|
1138
|
-
|
|
1238
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1139
1239
|
},
|
|
1140
1240
|
args: {
|
|
1141
1241
|
type: "array",
|
|
@@ -1213,7 +1313,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1213
1313
|
repoName: {
|
|
1214
1314
|
type: "string",
|
|
1215
1315
|
description:
|
|
1216
|
-
|
|
1316
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1217
1317
|
},
|
|
1218
1318
|
args: {
|
|
1219
1319
|
type: "array",
|
|
@@ -1330,7 +1430,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1330
1430
|
repoName: {
|
|
1331
1431
|
type: "string",
|
|
1332
1432
|
description:
|
|
1333
|
-
|
|
1433
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1334
1434
|
},
|
|
1335
1435
|
args: {
|
|
1336
1436
|
type: "array",
|
|
@@ -1372,7 +1472,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1372
1472
|
repoName: {
|
|
1373
1473
|
type: "string",
|
|
1374
1474
|
description:
|
|
1375
|
-
|
|
1475
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1376
1476
|
},
|
|
1377
1477
|
args: {
|
|
1378
1478
|
type: "array",
|
|
@@ -1430,7 +1530,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1430
1530
|
repoName: {
|
|
1431
1531
|
type: "string",
|
|
1432
1532
|
description:
|
|
1433
|
-
|
|
1533
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1434
1534
|
},
|
|
1435
1535
|
args: {
|
|
1436
1536
|
type: "array",
|
|
@@ -1465,7 +1565,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1465
1565
|
repoName: {
|
|
1466
1566
|
type: "string",
|
|
1467
1567
|
description:
|
|
1468
|
-
|
|
1568
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1469
1569
|
},
|
|
1470
1570
|
args: {
|
|
1471
1571
|
type: "array",
|
|
@@ -1487,7 +1587,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1487
1587
|
repoName: {
|
|
1488
1588
|
type: "string",
|
|
1489
1589
|
description:
|
|
1490
|
-
|
|
1590
|
+
'Optional repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1491
1591
|
},
|
|
1492
1592
|
},
|
|
1493
1593
|
required: [],
|
|
@@ -1555,7 +1655,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1555
1655
|
repoName: {
|
|
1556
1656
|
type: "string",
|
|
1557
1657
|
description:
|
|
1558
|
-
|
|
1658
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1559
1659
|
},
|
|
1560
1660
|
},
|
|
1561
1661
|
$comment: safeJsonStringify({
|
|
@@ -1702,9 +1802,9 @@ const getInvocationPlanName = (toolName: string): string => {
|
|
|
1702
1802
|
const plan = toolInvocationPlans[toolName];
|
|
1703
1803
|
if (!plan) return "default";
|
|
1704
1804
|
if (plan === buildOptionsOnly) return "optionsOnly";
|
|
1705
|
-
if (plan ===
|
|
1706
|
-
if (plan ===
|
|
1707
|
-
if (plan ===
|
|
1805
|
+
if (plan === buildOptionsThenProcessRoot) return "optionsThenProcessRoot";
|
|
1806
|
+
if (plan === buildProcessRootThenOptions) return "processRootThenOptions";
|
|
1807
|
+
if (plan === buildProcessRootOnly) return "processRootOnly";
|
|
1708
1808
|
return "custom";
|
|
1709
1809
|
};
|
|
1710
1810
|
|
|
@@ -1716,16 +1816,16 @@ const buildInvocationExample = (toolName: string): Record<string, unknown> => {
|
|
|
1716
1816
|
const example: Record<string, unknown> = {};
|
|
1717
1817
|
if (requiredArgs && requiredArgs.length > 0) {
|
|
1718
1818
|
example.args = [...requiredArgs];
|
|
1719
|
-
} else if (plan !== "
|
|
1819
|
+
} else if (plan !== "processRootOnly") {
|
|
1720
1820
|
example.args = ["<arg0>"];
|
|
1721
1821
|
}
|
|
1722
1822
|
|
|
1723
|
-
if (plan === "
|
|
1823
|
+
if (plan === "processRootOnly") {
|
|
1724
1824
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1725
1825
|
return example;
|
|
1726
1826
|
}
|
|
1727
1827
|
|
|
1728
|
-
if (plan === "
|
|
1828
|
+
if (plan === "optionsThenProcessRoot" || plan === "processRootThenOptions") {
|
|
1729
1829
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1730
1830
|
return example;
|
|
1731
1831
|
}
|
|
@@ -1760,7 +1860,7 @@ const defaultToolInputSchema = (toolName: string) => ({
|
|
|
1760
1860
|
repoName: {
|
|
1761
1861
|
type: "string",
|
|
1762
1862
|
description:
|
|
1763
|
-
|
|
1863
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1764
1864
|
},
|
|
1765
1865
|
},
|
|
1766
1866
|
$comment: safeJsonStringify({
|
|
@@ -1876,13 +1976,13 @@ const buildToolList = (
|
|
|
1876
1976
|
type ToolInvoker = (
|
|
1877
1977
|
args: unknown[],
|
|
1878
1978
|
options: Record<string, unknown>,
|
|
1879
|
-
|
|
1979
|
+
defaultProcessRoot: string,
|
|
1880
1980
|
) => unknown[];
|
|
1881
1981
|
|
|
1882
1982
|
const buildOptionsOnly = (
|
|
1883
1983
|
args: unknown[],
|
|
1884
1984
|
options: Record<string, unknown>,
|
|
1885
|
-
|
|
1985
|
+
_defaultProcessRoot: string,
|
|
1886
1986
|
): unknown[] => {
|
|
1887
1987
|
const invocationArgs: unknown[] = [...args];
|
|
1888
1988
|
if (Object.keys(options).length > 0) {
|
|
@@ -1891,44 +1991,48 @@ const buildOptionsOnly = (
|
|
|
1891
1991
|
return invocationArgs;
|
|
1892
1992
|
};
|
|
1893
1993
|
|
|
1894
|
-
const
|
|
1994
|
+
const buildOptionsThenProcessRoot = (
|
|
1895
1995
|
args: unknown[],
|
|
1896
1996
|
options: Record<string, unknown>,
|
|
1897
|
-
|
|
1997
|
+
defaultProcessRoot: string,
|
|
1898
1998
|
): unknown[] => {
|
|
1899
1999
|
const invocationArgs: unknown[] = [...args];
|
|
1900
2000
|
const remaining = { ...options };
|
|
1901
|
-
const
|
|
1902
|
-
if (typeof
|
|
1903
|
-
delete remaining.
|
|
2001
|
+
const processRoot = remaining.processRoot;
|
|
2002
|
+
if (typeof processRoot === "string") {
|
|
2003
|
+
delete remaining.processRoot;
|
|
1904
2004
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
2005
|
+
delete remaining.repoRoot;
|
|
2006
|
+
const resolvedProcessRoot =
|
|
2007
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot;
|
|
1907
2008
|
|
|
1908
2009
|
if (Object.keys(remaining).length > 0) {
|
|
1909
2010
|
invocationArgs.push(remaining);
|
|
1910
|
-
} else if (
|
|
1911
|
-
// Preserve positional slot for signatures like fn(projectName, options?,
|
|
2011
|
+
} else if (resolvedProcessRoot) {
|
|
2012
|
+
// Preserve positional slot for signatures like fn(projectName, options?, processRoot?).
|
|
1912
2013
|
invocationArgs.push({});
|
|
1913
2014
|
}
|
|
1914
|
-
invocationArgs.push(
|
|
2015
|
+
invocationArgs.push(resolvedProcessRoot);
|
|
1915
2016
|
|
|
1916
2017
|
return invocationArgs;
|
|
1917
2018
|
};
|
|
1918
2019
|
|
|
1919
|
-
const
|
|
2020
|
+
const buildProcessRootThenOptions = (
|
|
1920
2021
|
args: unknown[],
|
|
1921
2022
|
options: Record<string, unknown>,
|
|
1922
|
-
|
|
2023
|
+
defaultProcessRoot: string,
|
|
1923
2024
|
): unknown[] => {
|
|
1924
2025
|
const invocationArgs: unknown[] = [...args];
|
|
1925
2026
|
const remaining = { ...options };
|
|
1926
|
-
const
|
|
1927
|
-
if (typeof
|
|
1928
|
-
delete remaining.
|
|
2027
|
+
const processRoot = remaining.processRoot;
|
|
2028
|
+
if (typeof processRoot === "string") {
|
|
2029
|
+
delete remaining.processRoot;
|
|
1929
2030
|
}
|
|
2031
|
+
delete remaining.repoRoot;
|
|
1930
2032
|
|
|
1931
|
-
invocationArgs.push(
|
|
2033
|
+
invocationArgs.push(
|
|
2034
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2035
|
+
);
|
|
1932
2036
|
if (Object.keys(remaining).length > 0) {
|
|
1933
2037
|
invocationArgs.push(remaining);
|
|
1934
2038
|
}
|
|
@@ -1936,39 +2040,42 @@ const buildRepoRootThenOptions = (
|
|
|
1936
2040
|
return invocationArgs;
|
|
1937
2041
|
};
|
|
1938
2042
|
|
|
1939
|
-
const
|
|
2043
|
+
const buildProcessRootOnly = (
|
|
1940
2044
|
args: unknown[],
|
|
1941
2045
|
options: Record<string, unknown>,
|
|
1942
|
-
|
|
2046
|
+
defaultProcessRoot: string,
|
|
1943
2047
|
): unknown[] => {
|
|
1944
2048
|
const invocationArgs: unknown[] = [...args];
|
|
1945
|
-
const
|
|
1946
|
-
invocationArgs.push(
|
|
2049
|
+
const processRoot = options.processRoot;
|
|
2050
|
+
invocationArgs.push(
|
|
2051
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2052
|
+
);
|
|
1947
2053
|
return invocationArgs;
|
|
1948
2054
|
};
|
|
1949
2055
|
|
|
1950
2056
|
const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
1951
|
-
"agents.setActive":
|
|
1952
|
-
"agents.resolveAgentsRootFrom":
|
|
1953
|
-
"projects.setActive":
|
|
1954
|
-
"projects.generateSpec":
|
|
1955
|
-
"projects.syncTasks":
|
|
1956
|
-
"projects.clearIssues":
|
|
1957
|
-
"projects.fetchGitTasks":
|
|
1958
|
-
"projects.createGitIssue":
|
|
1959
|
-
"projects.readGitTask":
|
|
1960
|
-
"projects.writeGitTask":
|
|
2057
|
+
"agents.setActive": buildProcessRootThenOptions,
|
|
2058
|
+
"agents.resolveAgentsRootFrom": buildProcessRootOnly,
|
|
2059
|
+
"projects.setActive": buildProcessRootThenOptions,
|
|
2060
|
+
"projects.generateSpec": buildOptionsThenProcessRoot,
|
|
2061
|
+
"projects.syncTasks": buildOptionsThenProcessRoot,
|
|
2062
|
+
"projects.clearIssues": buildOptionsThenProcessRoot,
|
|
2063
|
+
"projects.fetchGitTasks": buildOptionsThenProcessRoot,
|
|
2064
|
+
"projects.createGitIssue": buildOptionsThenProcessRoot,
|
|
2065
|
+
"projects.readGitTask": buildOptionsThenProcessRoot,
|
|
2066
|
+
"projects.writeGitTask": buildOptionsThenProcessRoot,
|
|
1961
2067
|
"agents.resolveTargetFile": buildOptionsOnly,
|
|
1962
2068
|
"projects.resolveProjectTargetFile": buildOptionsOnly,
|
|
1963
|
-
"agents.loadAgent":
|
|
1964
|
-
"agents.loadAgentPrompt":
|
|
1965
|
-
"projects.resolveImplementationPlan": (args, options,
|
|
2069
|
+
"agents.loadAgent": buildProcessRootOnly,
|
|
2070
|
+
"agents.loadAgentPrompt": buildProcessRootOnly,
|
|
2071
|
+
"projects.resolveImplementationPlan": (args, options, _defaultProcessRoot) => {
|
|
1966
2072
|
const invocationArgs: unknown[] = [...args];
|
|
1967
2073
|
const remaining = { ...options };
|
|
1968
|
-
const
|
|
1969
|
-
if (typeof
|
|
1970
|
-
delete remaining.
|
|
2074
|
+
const processRoot = remaining.processRoot;
|
|
2075
|
+
if (typeof processRoot === "string") {
|
|
2076
|
+
delete remaining.processRoot;
|
|
1971
2077
|
}
|
|
2078
|
+
delete remaining.repoRoot;
|
|
1972
2079
|
|
|
1973
2080
|
// This tool is a low-level helper: projects.resolveImplementationPlan(projectRoot, inputFile?, options?)
|
|
1974
2081
|
// If the caller provides options but no inputFile, preserve the positional slot.
|
|
@@ -1979,17 +2086,17 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
|
1979
2086
|
invocationArgs.push(remaining);
|
|
1980
2087
|
}
|
|
1981
2088
|
|
|
1982
|
-
// Intentionally do NOT append
|
|
2089
|
+
// Intentionally do NOT append processRoot: projectRoot is the first positional argument.
|
|
1983
2090
|
return invocationArgs;
|
|
1984
2091
|
},
|
|
1985
|
-
"agents.main":
|
|
1986
|
-
"agents.resolveAgentsRoot":
|
|
1987
|
-
"agents.listAgents":
|
|
1988
|
-
"projects.resolveProjectRoot":
|
|
1989
|
-
"projects.listProjects":
|
|
1990
|
-
"projects.listProjectDocs":
|
|
1991
|
-
"projects.readProjectDoc":
|
|
1992
|
-
"projects.main":
|
|
2092
|
+
"agents.main": buildProcessRootOnly,
|
|
2093
|
+
"agents.resolveAgentsRoot": buildProcessRootOnly,
|
|
2094
|
+
"agents.listAgents": buildProcessRootOnly,
|
|
2095
|
+
"projects.resolveProjectRoot": buildProcessRootOnly,
|
|
2096
|
+
"projects.listProjects": buildProcessRootOnly,
|
|
2097
|
+
"projects.listProjectDocs": buildProcessRootOnly,
|
|
2098
|
+
"projects.readProjectDoc": buildProcessRootOnly,
|
|
2099
|
+
"projects.main": buildProcessRootOnly,
|
|
1993
2100
|
};
|
|
1994
2101
|
|
|
1995
2102
|
const invokeTool = async (
|
|
@@ -2005,14 +2112,14 @@ const invokeTool = async (
|
|
|
2005
2112
|
};
|
|
2006
2113
|
const invoke =
|
|
2007
2114
|
toolInvocationPlans[tool.name] ??
|
|
2008
|
-
((rawArgs, rawOptions,
|
|
2115
|
+
((rawArgs, rawOptions, _processRoot) => {
|
|
2009
2116
|
const invocationArgs = [...rawArgs];
|
|
2010
2117
|
if (Object.keys(rawOptions).length > 0) {
|
|
2011
2118
|
invocationArgs.push(rawOptions);
|
|
2012
2119
|
}
|
|
2013
2120
|
return invocationArgs;
|
|
2014
2121
|
});
|
|
2015
|
-
const invocationArgs = invoke(args, options, repoCtx.
|
|
2122
|
+
const invocationArgs = invoke(args, options, repoCtx.defaultProcessRoot);
|
|
2016
2123
|
|
|
2017
2124
|
return Promise.resolve(tool.method(...invocationArgs));
|
|
2018
2125
|
};
|
|
@@ -2022,13 +2129,8 @@ export interface ExampleMcpServerOptions {
|
|
|
2022
2129
|
serverVersion?: string;
|
|
2023
2130
|
toolsPrefix?: string;
|
|
2024
2131
|
/**
|
|
2025
|
-
* Optional default repo
|
|
2026
|
-
* If omitted,
|
|
2027
|
-
*/
|
|
2028
|
-
repoRoot?: string;
|
|
2029
|
-
/**
|
|
2030
|
-
* Optional default repo name (LLM-friendly identifier).
|
|
2031
|
-
* If omitted, defaults to the basename of the resolved repo root.
|
|
2132
|
+
* Optional default repo selector (for MCP repoName matching).
|
|
2133
|
+
* If omitted, auto-detected from git remote (fallback: workspace folder name).
|
|
2032
2134
|
*/
|
|
2033
2135
|
repoName?: string;
|
|
2034
2136
|
allowedRootEndpoints?: string[];
|
|
@@ -2162,30 +2264,24 @@ export const createExampleMcpServer = (
|
|
|
2162
2264
|
options: ExampleMcpServerOptions = {},
|
|
2163
2265
|
): ExampleMcpServerInstance => {
|
|
2164
2266
|
let toolCatalog: unknown[] = [];
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2267
|
+
|
|
2268
|
+
const serverFileDir = path.dirname(fileURLToPath(import.meta.url));
|
|
2269
|
+
const defaultProcessRoot = resolveProcessRootFromGit(serverFileDir);
|
|
2270
|
+
const configuredRepoName = options.repoName?.trim() || null;
|
|
2271
|
+
const resolvedRepoIdentity = resolveRepoIdentityFromGit(defaultProcessRoot);
|
|
2272
|
+
const defaultRepoName = configuredRepoName || resolvedRepoIdentity.defaultRepoName;
|
|
2273
|
+
const defaultRepoFullName = resolvedRepoIdentity.defaultRepoFullName;
|
|
2274
|
+
const { repoNames, repoNameByKey } = buildRepoNameContext(
|
|
2275
|
+
defaultRepoName,
|
|
2276
|
+
defaultRepoFullName,
|
|
2170
2277
|
);
|
|
2171
|
-
|
|
2172
|
-
(options.repoName ?? process.env.MCP_REPO_NAME ?? process.env.F0_REPO_NAME)?.trim() ||
|
|
2173
|
-
path.basename(defaultRepoRoot);
|
|
2174
|
-
const repoMapRaw = process.env.MCP_REPOS ?? process.env.F0_REPOS;
|
|
2175
|
-
const repoMap = {
|
|
2176
|
-
...parseRepoMap(repoMapRaw),
|
|
2177
|
-
[defaultRepoName]: defaultRepoRoot,
|
|
2178
|
-
};
|
|
2179
|
-
const repoNames = Object.keys(repoMap).sort((a, b) => a.localeCompare(b));
|
|
2180
|
-
const repoMapByKey: Record<string, string> = {};
|
|
2181
|
-
for (const [name, root] of Object.entries(repoMap)) {
|
|
2182
|
-
repoMapByKey[name.toLowerCase()] = root;
|
|
2183
|
-
}
|
|
2278
|
+
|
|
2184
2279
|
const repoCtx: RepoResolutionContext = {
|
|
2185
|
-
|
|
2280
|
+
defaultProcessRoot,
|
|
2186
2281
|
defaultRepoName,
|
|
2187
|
-
|
|
2282
|
+
defaultRepoFullName,
|
|
2188
2283
|
repoNames,
|
|
2284
|
+
repoNameByKey,
|
|
2189
2285
|
};
|
|
2190
2286
|
|
|
2191
2287
|
const parseString = (value: unknown): string | null => {
|
|
@@ -2226,7 +2322,7 @@ export const createExampleMcpServer = (
|
|
|
2226
2322
|
"F0 MCP helper tools:",
|
|
2227
2323
|
"- mcp.listTools: returns tool catalog with access + invocation hints",
|
|
2228
2324
|
"- mcp.describeTool: describe one tool by name (prefixed or unprefixed)",
|
|
2229
|
-
"- mcp.workspace: explain
|
|
2325
|
+
"- mcp.workspace: explain Git workspace context (cwd, repoName selectors, projects)",
|
|
2230
2326
|
"- mcp.search: LLM-friendly search over project docs/spec (local-first)",
|
|
2231
2327
|
"",
|
|
2232
2328
|
'Tip: Prefer mcp.search for "search spec/docs" requests.',
|
|
@@ -2238,35 +2334,35 @@ export const createExampleMcpServer = (
|
|
|
2238
2334
|
? {
|
|
2239
2335
|
keys: Object.keys(input),
|
|
2240
2336
|
repoName: (input as any).repoName ?? null,
|
|
2241
|
-
repoRoot: (input as any).repoRoot ?? null,
|
|
2242
|
-
processRoot: (input as any).processRoot ?? null,
|
|
2243
2337
|
}
|
|
2244
|
-
: { keys: [], repoName: null
|
|
2338
|
+
: { keys: [], repoName: null };
|
|
2245
2339
|
const requestedRepoName = parseString(payload.repoName);
|
|
2246
2340
|
const resolved = resolveRepoSelectorOptions(payload, repoCtx);
|
|
2247
|
-
const
|
|
2248
|
-
typeof resolved.
|
|
2249
|
-
? resolved.
|
|
2250
|
-
: repoCtx.
|
|
2251
|
-
const effectiveRepoName =
|
|
2252
|
-
requestedRepoName
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
const projectsDir = path.join(
|
|
2257
|
-
const apiDir = path.join(
|
|
2258
|
-
const agentsDir = path.join(
|
|
2341
|
+
const processRoot =
|
|
2342
|
+
typeof resolved.processRoot === "string"
|
|
2343
|
+
? resolved.processRoot
|
|
2344
|
+
: repoCtx.defaultProcessRoot;
|
|
2345
|
+
const effectiveRepoName = normalizeRepoNameForDisplay(
|
|
2346
|
+
requestedRepoName,
|
|
2347
|
+
repoCtx,
|
|
2348
|
+
);
|
|
2349
|
+
|
|
2350
|
+
const projectsDir = path.join(processRoot, "projects");
|
|
2351
|
+
const apiDir = path.join(processRoot, "api");
|
|
2352
|
+
const agentsDir = path.join(processRoot, "agents");
|
|
2259
2353
|
const hasProjectsDir = isDir(projectsDir);
|
|
2260
2354
|
const hasApiDir = isDir(apiDir);
|
|
2261
2355
|
const hasAgentsDir = isDir(agentsDir);
|
|
2262
2356
|
|
|
2263
|
-
const projects = hasProjectsDir
|
|
2357
|
+
const projects = hasProjectsDir
|
|
2358
|
+
? projectsApi.listProjects(processRoot)
|
|
2359
|
+
: [];
|
|
2264
2360
|
const hint = (() => {
|
|
2265
2361
|
if (!hasProjectsDir) {
|
|
2266
2362
|
return [
|
|
2267
|
-
"
|
|
2268
|
-
"
|
|
2269
|
-
"
|
|
2363
|
+
"Git workspace does not contain /projects.",
|
|
2364
|
+
"Pass repoName as \"adl\" or \"F0/adl\".",
|
|
2365
|
+
"For single-repo layouts, use source:\"auto\" or source:\"gitea\" on search tools.",
|
|
2270
2366
|
].join(" ");
|
|
2271
2367
|
}
|
|
2272
2368
|
if (hasProjectsDir && projects.length === 0) {
|
|
@@ -2282,9 +2378,10 @@ export const createExampleMcpServer = (
|
|
|
2282
2378
|
received,
|
|
2283
2379
|
cwd: process.cwd(),
|
|
2284
2380
|
defaultRepoName: repoCtx.defaultRepoName,
|
|
2285
|
-
|
|
2381
|
+
defaultRepoFullName: repoCtx.defaultRepoFullName,
|
|
2382
|
+
workspaceRoot: repoCtx.defaultProcessRoot,
|
|
2286
2383
|
repoName: effectiveRepoName,
|
|
2287
|
-
|
|
2384
|
+
processRoot,
|
|
2288
2385
|
availableRepoNames: repoCtx.repoNames,
|
|
2289
2386
|
hasProjectsDir,
|
|
2290
2387
|
hasApiDir,
|
|
@@ -2334,10 +2431,10 @@ export const createExampleMcpServer = (
|
|
|
2334
2431
|
search: async (input: unknown) => {
|
|
2335
2432
|
const payload = isRecord(input) ? input : {};
|
|
2336
2433
|
const resolved = resolveRepoSelectorOptions(payload, repoCtx);
|
|
2337
|
-
const
|
|
2338
|
-
typeof resolved.
|
|
2339
|
-
? resolved.
|
|
2340
|
-
: repoCtx.
|
|
2434
|
+
const processRoot =
|
|
2435
|
+
typeof resolved.processRoot === "string"
|
|
2436
|
+
? resolved.processRoot
|
|
2437
|
+
: repoCtx.defaultProcessRoot;
|
|
2341
2438
|
|
|
2342
2439
|
const sectionRaw = parseString(payload.section)?.toLowerCase();
|
|
2343
2440
|
const section = sectionRaw === "docs" ? "docs" : "spec";
|
|
@@ -2384,11 +2481,11 @@ export const createExampleMcpServer = (
|
|
|
2384
2481
|
|
|
2385
2482
|
const projectName = ensureProjectName(
|
|
2386
2483
|
parseString(payload.projectName),
|
|
2387
|
-
|
|
2484
|
+
processRoot,
|
|
2388
2485
|
);
|
|
2389
2486
|
|
|
2390
2487
|
const searchOptions: Record<string, unknown> = {
|
|
2391
|
-
processRoot
|
|
2488
|
+
processRoot,
|
|
2392
2489
|
};
|
|
2393
2490
|
|
|
2394
2491
|
const source = parseString(payload.source)?.toLowerCase();
|
|
@@ -2711,9 +2808,9 @@ export const createExampleMcpServer = (
|
|
|
2711
2808
|
/projects[\\/].+projects[\\/]/i.test(message)
|
|
2712
2809
|
) {
|
|
2713
2810
|
details.hint =
|
|
2714
|
-
|
|
2811
|
+
'Repo selection is invalid. repoName must be "<repo>" or "<owner>/<repo>", for example "adl" or "F0/adl".';
|
|
2715
2812
|
details.suggestion =
|
|
2716
|
-
"Call mcp.workspace to see
|
|
2813
|
+
"Call mcp.workspace to see accepted repoName selectors.";
|
|
2717
2814
|
}
|
|
2718
2815
|
|
|
2719
2816
|
if (
|
|
@@ -2724,7 +2821,7 @@ export const createExampleMcpServer = (
|
|
|
2724
2821
|
details.hint =
|
|
2725
2822
|
"Repo selection might be wrong, or the server filesystem does not contain /projects for this workspace.";
|
|
2726
2823
|
details.suggestion =
|
|
2727
|
-
"Call mcp.workspace to see
|
|
2824
|
+
"Call mcp.workspace to see workspaceRoot and available repoName values.";
|
|
2728
2825
|
details.example = {
|
|
2729
2826
|
tool: prefix ? `${prefix}.mcp.workspace` : "mcp.workspace",
|
|
2730
2827
|
arguments: {},
|