@foundation0/api 1.1.8 → 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 +102 -97
- package/mcp/server.ts +267 -288
- package/package.json +1 -1
- package/projects.ts +27 -3
package/mcp/server.ts
CHANGED
|
@@ -174,9 +174,6 @@ const isDir = (candidate: string): boolean => {
|
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
-
const looksLikeRepoRoot = (candidate: string): boolean =>
|
|
178
|
-
isDir(path.join(candidate, "projects")) && isDir(path.join(candidate, "api"));
|
|
179
|
-
|
|
180
177
|
const fileExists = (candidate: string): boolean => {
|
|
181
178
|
try {
|
|
182
179
|
fs.statSync(candidate);
|
|
@@ -198,8 +195,8 @@ const findGitRepoRoot = (startDir: string): string | null => {
|
|
|
198
195
|
return null;
|
|
199
196
|
};
|
|
200
197
|
|
|
201
|
-
const resolveGitDir = (
|
|
202
|
-
const dotGit = path.join(
|
|
198
|
+
const resolveGitDir = (workspaceRoot: string): string | null => {
|
|
199
|
+
const dotGit = path.join(workspaceRoot, ".git");
|
|
203
200
|
if (isDir(dotGit)) return dotGit;
|
|
204
201
|
if (!fileExists(dotGit)) return null;
|
|
205
202
|
|
|
@@ -209,14 +206,22 @@ const resolveGitDir = (repoRoot: string): string | null => {
|
|
|
209
206
|
if (!match) return null;
|
|
210
207
|
const raw = match[1].trim();
|
|
211
208
|
if (!raw) return null;
|
|
212
|
-
const resolved = path.resolve(
|
|
209
|
+
const resolved = path.resolve(workspaceRoot, raw);
|
|
213
210
|
return isDir(resolved) ? resolved : null;
|
|
214
211
|
} catch {
|
|
215
212
|
return null;
|
|
216
213
|
}
|
|
217
214
|
};
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
type GitRemoteIdentity = {
|
|
217
|
+
owner: string | null;
|
|
218
|
+
repo: string;
|
|
219
|
+
fullName: string | null;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const parseGitRemoteIdentityFromUrl = (
|
|
223
|
+
remoteUrl: string,
|
|
224
|
+
): GitRemoteIdentity | null => {
|
|
220
225
|
const trimmed = remoteUrl.trim();
|
|
221
226
|
if (!trimmed) return null;
|
|
222
227
|
|
|
@@ -225,11 +230,34 @@ const parseRepoNameFromRemoteUrl = (remoteUrl: string): string | null => {
|
|
|
225
230
|
const withoutGit = withoutQuery.endsWith(".git")
|
|
226
231
|
? withoutQuery.slice(0, -4)
|
|
227
232
|
: withoutQuery;
|
|
233
|
+
const normalized = withoutGit.replace(/\\/g, "/");
|
|
234
|
+
const scpPathMatch = normalized.match(/^[^@]+@[^:]+:(.+)$/);
|
|
235
|
+
let pathPart = scpPathMatch?.[1] ?? normalized;
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
if (!scpPathMatch) {
|
|
238
|
+
try {
|
|
239
|
+
const url = new URL(normalized);
|
|
240
|
+
pathPart = url.pathname;
|
|
241
|
+
} catch {
|
|
242
|
+
// Keep normalized value as-is.
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
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
|
+
};
|
|
233
261
|
};
|
|
234
262
|
|
|
235
263
|
const readGitRemoteUrl = (gitDir: string): string | null => {
|
|
@@ -267,114 +295,55 @@ const readGitRemoteUrl = (gitDir: string): string | null => {
|
|
|
267
295
|
}
|
|
268
296
|
};
|
|
269
297
|
|
|
270
|
-
const
|
|
271
|
-
|
|
298
|
+
const detectRepoIdentityFromGitConfig = (
|
|
299
|
+
workspaceRoot: string,
|
|
300
|
+
): GitRemoteIdentity | null => {
|
|
301
|
+
const gitDir = resolveGitDir(workspaceRoot);
|
|
272
302
|
if (!gitDir) return null;
|
|
273
303
|
const remoteUrl = readGitRemoteUrl(gitDir);
|
|
274
304
|
if (!remoteUrl) return null;
|
|
275
|
-
return
|
|
305
|
+
return parseGitRemoteIdentityFromUrl(remoteUrl);
|
|
276
306
|
};
|
|
277
307
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
let current = resolved;
|
|
285
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
286
|
-
const parent = path.dirname(current);
|
|
287
|
-
if (parent === current) break;
|
|
288
|
-
if (looksLikeRepoRoot(parent)) return parent;
|
|
289
|
-
current = parent;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const parts = resolved.split(path.sep).filter((part) => part.length > 0);
|
|
293
|
-
const projectsIndex = parts.lastIndexOf("projects");
|
|
294
|
-
if (projectsIndex >= 0) {
|
|
295
|
-
const candidate = parts.slice(0, projectsIndex).join(path.sep);
|
|
296
|
-
if (candidate && looksLikeRepoRoot(candidate)) return candidate;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return resolved;
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const normalizeRepoRootOption = (
|
|
303
|
-
options: Record<string, unknown>,
|
|
304
|
-
): Record<string, unknown> => {
|
|
305
|
-
const rawRepoRoot = typeof options.repoRoot === "string" ? options.repoRoot : null;
|
|
306
|
-
const rawProcessRoot =
|
|
307
|
-
typeof options.processRoot === "string" ? options.processRoot : null;
|
|
308
|
-
const raw = rawRepoRoot ?? rawProcessRoot;
|
|
309
|
-
|
|
310
|
-
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
311
|
-
const next = { ...options };
|
|
312
|
-
delete next.repoRoot;
|
|
313
|
-
delete next.processRoot;
|
|
314
|
-
return next;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const trimmed = raw.trim();
|
|
318
|
-
const normalized = normalizeRepoRoot(trimmed);
|
|
319
|
-
|
|
320
|
-
const next: Record<string, unknown> = { ...options, repoRoot: normalized };
|
|
321
|
-
delete next.processRoot;
|
|
322
|
-
|
|
323
|
-
const alreadyCanonical =
|
|
324
|
-
rawRepoRoot !== null && rawRepoRoot === normalized && !("processRoot" in options);
|
|
325
|
-
return alreadyCanonical ? options : next;
|
|
326
|
-
};
|
|
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(".");
|
|
308
|
+
type RepoResolutionContext = {
|
|
309
|
+
defaultProcessRoot: string;
|
|
310
|
+
defaultRepoName: string;
|
|
311
|
+
defaultRepoFullName: string | null;
|
|
312
|
+
repoNames: string[];
|
|
313
|
+
repoNameByKey: Record<string, string>;
|
|
333
314
|
};
|
|
334
315
|
|
|
335
|
-
const
|
|
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
|
-
}
|
|
316
|
+
const repoNameKey = (value: string): string => value.trim().toLowerCase();
|
|
355
317
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!key || !value) continue;
|
|
368
|
-
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);
|
|
369
329
|
}
|
|
370
330
|
return out;
|
|
371
331
|
};
|
|
372
332
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
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 };
|
|
378
347
|
};
|
|
379
348
|
|
|
380
349
|
const resolveRepoSelectorOptions = (
|
|
@@ -382,68 +351,100 @@ const resolveRepoSelectorOptions = (
|
|
|
382
351
|
ctx: RepoResolutionContext,
|
|
383
352
|
): Record<string, unknown> => {
|
|
384
353
|
const next: Record<string, unknown> = { ...options };
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
}
|
|
354
|
+
delete next.repoRoot;
|
|
355
|
+
delete next.processRoot;
|
|
400
356
|
|
|
401
357
|
const repoName =
|
|
402
358
|
typeof next.repoName === "string" && next.repoName.trim().length > 0
|
|
403
359
|
? next.repoName.trim()
|
|
404
360
|
: null;
|
|
405
361
|
if (!repoName) {
|
|
406
|
-
|
|
362
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
407
363
|
return next;
|
|
408
364
|
}
|
|
409
365
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
366
|
+
const needle = repoNameKey(repoName);
|
|
367
|
+
const canonical = ctx.repoNameByKey[needle];
|
|
368
|
+
if (canonical) {
|
|
369
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
370
|
+
next.repoName = canonical;
|
|
414
371
|
return next;
|
|
415
372
|
}
|
|
416
373
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
next.
|
|
420
|
-
delete next.processRoot;
|
|
421
|
-
delete next.repoName;
|
|
374
|
+
if (ctx.defaultRepoFullName && needle === repoNameKey(ctx.defaultRepoFullName)) {
|
|
375
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
376
|
+
next.repoName = ctx.defaultRepoFullName;
|
|
422
377
|
return next;
|
|
423
378
|
}
|
|
424
379
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
next.
|
|
428
|
-
delete next.processRoot;
|
|
429
|
-
delete next.repoName;
|
|
380
|
+
if (needle === repoNameKey(ctx.defaultRepoName)) {
|
|
381
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
382
|
+
next.repoName = ctx.defaultRepoName;
|
|
430
383
|
return next;
|
|
431
384
|
}
|
|
432
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
|
+
|
|
433
399
|
const suggestions = ctx.repoNames
|
|
434
|
-
.filter((name) => name
|
|
400
|
+
.filter((name) => repoNameKey(name).includes(needle))
|
|
435
401
|
.slice(0, 8);
|
|
436
402
|
const hint =
|
|
437
403
|
suggestions.length > 0
|
|
438
404
|
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
439
|
-
: ctx.repoNames.
|
|
440
|
-
? ` Available repos: ${ctx.repoNames.join(", ")}.`
|
|
441
|
-
: "";
|
|
405
|
+
: ` Available repoName values: ${ctx.repoNames.join(", ")}.`;
|
|
442
406
|
throw new Error(
|
|
443
|
-
`Unknown repoName: ${repoName}.${hint} Tip:
|
|
407
|
+
`Unknown repoName: ${repoName}.${hint} Tip: use "adl" or "F0/adl" style selectors.`,
|
|
444
408
|
);
|
|
445
409
|
};
|
|
446
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
|
+
|
|
447
448
|
type NormalizedToolPayload = {
|
|
448
449
|
args: unknown[];
|
|
449
450
|
options: Record<string, unknown>;
|
|
@@ -562,7 +563,7 @@ const normalizePayload = (payload: unknown): NormalizedToolPayload => {
|
|
|
562
563
|
|
|
563
564
|
return {
|
|
564
565
|
args,
|
|
565
|
-
options
|
|
566
|
+
options,
|
|
566
567
|
};
|
|
567
568
|
};
|
|
568
569
|
|
|
@@ -669,7 +670,7 @@ const coercePayloadForTool = (
|
|
|
669
670
|
|
|
670
671
|
switch (toolName) {
|
|
671
672
|
case "projects.listProjects": {
|
|
672
|
-
// No positional args.
|
|
673
|
+
// No positional args. processRoot is injected from the selected repoName.
|
|
673
674
|
break;
|
|
674
675
|
}
|
|
675
676
|
case "projects.resolveProjectRoot":
|
|
@@ -759,7 +760,7 @@ const coercePayloadForTool = (
|
|
|
759
760
|
break;
|
|
760
761
|
}
|
|
761
762
|
|
|
762
|
-
return { args, options
|
|
763
|
+
return { args, options };
|
|
763
764
|
};
|
|
764
765
|
|
|
765
766
|
const normalizeBatchToolCall = (
|
|
@@ -1099,7 +1100,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1099
1100
|
repoName: {
|
|
1100
1101
|
type: "string",
|
|
1101
1102
|
description:
|
|
1102
|
-
|
|
1103
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1103
1104
|
},
|
|
1104
1105
|
args: {
|
|
1105
1106
|
type: "array",
|
|
@@ -1125,7 +1126,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1125
1126
|
repoName: {
|
|
1126
1127
|
type: "string",
|
|
1127
1128
|
description:
|
|
1128
|
-
|
|
1129
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1129
1130
|
},
|
|
1130
1131
|
args: {
|
|
1131
1132
|
type: "array",
|
|
@@ -1156,7 +1157,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1156
1157
|
repoName: {
|
|
1157
1158
|
type: "string",
|
|
1158
1159
|
description:
|
|
1159
|
-
|
|
1160
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1160
1161
|
},
|
|
1161
1162
|
args: {
|
|
1162
1163
|
type: "array",
|
|
@@ -1234,7 +1235,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1234
1235
|
repoName: {
|
|
1235
1236
|
type: "string",
|
|
1236
1237
|
description:
|
|
1237
|
-
|
|
1238
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1238
1239
|
},
|
|
1239
1240
|
args: {
|
|
1240
1241
|
type: "array",
|
|
@@ -1312,7 +1313,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1312
1313
|
repoName: {
|
|
1313
1314
|
type: "string",
|
|
1314
1315
|
description:
|
|
1315
|
-
|
|
1316
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1316
1317
|
},
|
|
1317
1318
|
args: {
|
|
1318
1319
|
type: "array",
|
|
@@ -1429,7 +1430,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1429
1430
|
repoName: {
|
|
1430
1431
|
type: "string",
|
|
1431
1432
|
description:
|
|
1432
|
-
|
|
1433
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1433
1434
|
},
|
|
1434
1435
|
args: {
|
|
1435
1436
|
type: "array",
|
|
@@ -1471,7 +1472,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1471
1472
|
repoName: {
|
|
1472
1473
|
type: "string",
|
|
1473
1474
|
description:
|
|
1474
|
-
|
|
1475
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1475
1476
|
},
|
|
1476
1477
|
args: {
|
|
1477
1478
|
type: "array",
|
|
@@ -1529,7 +1530,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1529
1530
|
repoName: {
|
|
1530
1531
|
type: "string",
|
|
1531
1532
|
description:
|
|
1532
|
-
|
|
1533
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1533
1534
|
},
|
|
1534
1535
|
args: {
|
|
1535
1536
|
type: "array",
|
|
@@ -1564,7 +1565,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1564
1565
|
repoName: {
|
|
1565
1566
|
type: "string",
|
|
1566
1567
|
description:
|
|
1567
|
-
|
|
1568
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1568
1569
|
},
|
|
1569
1570
|
args: {
|
|
1570
1571
|
type: "array",
|
|
@@ -1586,7 +1587,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1586
1587
|
repoName: {
|
|
1587
1588
|
type: "string",
|
|
1588
1589
|
description:
|
|
1589
|
-
|
|
1590
|
+
'Optional repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1590
1591
|
},
|
|
1591
1592
|
},
|
|
1592
1593
|
required: [],
|
|
@@ -1654,7 +1655,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1654
1655
|
repoName: {
|
|
1655
1656
|
type: "string",
|
|
1656
1657
|
description:
|
|
1657
|
-
|
|
1658
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1658
1659
|
},
|
|
1659
1660
|
},
|
|
1660
1661
|
$comment: safeJsonStringify({
|
|
@@ -1801,9 +1802,9 @@ const getInvocationPlanName = (toolName: string): string => {
|
|
|
1801
1802
|
const plan = toolInvocationPlans[toolName];
|
|
1802
1803
|
if (!plan) return "default";
|
|
1803
1804
|
if (plan === buildOptionsOnly) return "optionsOnly";
|
|
1804
|
-
if (plan ===
|
|
1805
|
-
if (plan ===
|
|
1806
|
-
if (plan ===
|
|
1805
|
+
if (plan === buildOptionsThenProcessRoot) return "optionsThenProcessRoot";
|
|
1806
|
+
if (plan === buildProcessRootThenOptions) return "processRootThenOptions";
|
|
1807
|
+
if (plan === buildProcessRootOnly) return "processRootOnly";
|
|
1807
1808
|
return "custom";
|
|
1808
1809
|
};
|
|
1809
1810
|
|
|
@@ -1815,16 +1816,16 @@ const buildInvocationExample = (toolName: string): Record<string, unknown> => {
|
|
|
1815
1816
|
const example: Record<string, unknown> = {};
|
|
1816
1817
|
if (requiredArgs && requiredArgs.length > 0) {
|
|
1817
1818
|
example.args = [...requiredArgs];
|
|
1818
|
-
} else if (plan !== "
|
|
1819
|
+
} else if (plan !== "processRootOnly") {
|
|
1819
1820
|
example.args = ["<arg0>"];
|
|
1820
1821
|
}
|
|
1821
1822
|
|
|
1822
|
-
if (plan === "
|
|
1823
|
+
if (plan === "processRootOnly") {
|
|
1823
1824
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1824
1825
|
return example;
|
|
1825
1826
|
}
|
|
1826
1827
|
|
|
1827
|
-
if (plan === "
|
|
1828
|
+
if (plan === "optionsThenProcessRoot" || plan === "processRootThenOptions") {
|
|
1828
1829
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1829
1830
|
return example;
|
|
1830
1831
|
}
|
|
@@ -1859,7 +1860,7 @@ const defaultToolInputSchema = (toolName: string) => ({
|
|
|
1859
1860
|
repoName: {
|
|
1860
1861
|
type: "string",
|
|
1861
1862
|
description:
|
|
1862
|
-
|
|
1863
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1863
1864
|
},
|
|
1864
1865
|
},
|
|
1865
1866
|
$comment: safeJsonStringify({
|
|
@@ -1975,13 +1976,13 @@ const buildToolList = (
|
|
|
1975
1976
|
type ToolInvoker = (
|
|
1976
1977
|
args: unknown[],
|
|
1977
1978
|
options: Record<string, unknown>,
|
|
1978
|
-
|
|
1979
|
+
defaultProcessRoot: string,
|
|
1979
1980
|
) => unknown[];
|
|
1980
1981
|
|
|
1981
1982
|
const buildOptionsOnly = (
|
|
1982
1983
|
args: unknown[],
|
|
1983
1984
|
options: Record<string, unknown>,
|
|
1984
|
-
|
|
1985
|
+
_defaultProcessRoot: string,
|
|
1985
1986
|
): unknown[] => {
|
|
1986
1987
|
const invocationArgs: unknown[] = [...args];
|
|
1987
1988
|
if (Object.keys(options).length > 0) {
|
|
@@ -1990,44 +1991,48 @@ const buildOptionsOnly = (
|
|
|
1990
1991
|
return invocationArgs;
|
|
1991
1992
|
};
|
|
1992
1993
|
|
|
1993
|
-
const
|
|
1994
|
+
const buildOptionsThenProcessRoot = (
|
|
1994
1995
|
args: unknown[],
|
|
1995
1996
|
options: Record<string, unknown>,
|
|
1996
|
-
|
|
1997
|
+
defaultProcessRoot: string,
|
|
1997
1998
|
): unknown[] => {
|
|
1998
1999
|
const invocationArgs: unknown[] = [...args];
|
|
1999
2000
|
const remaining = { ...options };
|
|
2000
|
-
const
|
|
2001
|
-
if (typeof
|
|
2002
|
-
delete remaining.
|
|
2001
|
+
const processRoot = remaining.processRoot;
|
|
2002
|
+
if (typeof processRoot === "string") {
|
|
2003
|
+
delete remaining.processRoot;
|
|
2003
2004
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2005
|
+
delete remaining.repoRoot;
|
|
2006
|
+
const resolvedProcessRoot =
|
|
2007
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot;
|
|
2006
2008
|
|
|
2007
2009
|
if (Object.keys(remaining).length > 0) {
|
|
2008
2010
|
invocationArgs.push(remaining);
|
|
2009
|
-
} else if (
|
|
2010
|
-
// Preserve positional slot for signatures like fn(projectName, options?,
|
|
2011
|
+
} else if (resolvedProcessRoot) {
|
|
2012
|
+
// Preserve positional slot for signatures like fn(projectName, options?, processRoot?).
|
|
2011
2013
|
invocationArgs.push({});
|
|
2012
2014
|
}
|
|
2013
|
-
invocationArgs.push(
|
|
2015
|
+
invocationArgs.push(resolvedProcessRoot);
|
|
2014
2016
|
|
|
2015
2017
|
return invocationArgs;
|
|
2016
2018
|
};
|
|
2017
2019
|
|
|
2018
|
-
const
|
|
2020
|
+
const buildProcessRootThenOptions = (
|
|
2019
2021
|
args: unknown[],
|
|
2020
2022
|
options: Record<string, unknown>,
|
|
2021
|
-
|
|
2023
|
+
defaultProcessRoot: string,
|
|
2022
2024
|
): unknown[] => {
|
|
2023
2025
|
const invocationArgs: unknown[] = [...args];
|
|
2024
2026
|
const remaining = { ...options };
|
|
2025
|
-
const
|
|
2026
|
-
if (typeof
|
|
2027
|
-
delete remaining.
|
|
2027
|
+
const processRoot = remaining.processRoot;
|
|
2028
|
+
if (typeof processRoot === "string") {
|
|
2029
|
+
delete remaining.processRoot;
|
|
2028
2030
|
}
|
|
2031
|
+
delete remaining.repoRoot;
|
|
2029
2032
|
|
|
2030
|
-
invocationArgs.push(
|
|
2033
|
+
invocationArgs.push(
|
|
2034
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2035
|
+
);
|
|
2031
2036
|
if (Object.keys(remaining).length > 0) {
|
|
2032
2037
|
invocationArgs.push(remaining);
|
|
2033
2038
|
}
|
|
@@ -2035,39 +2040,42 @@ const buildRepoRootThenOptions = (
|
|
|
2035
2040
|
return invocationArgs;
|
|
2036
2041
|
};
|
|
2037
2042
|
|
|
2038
|
-
const
|
|
2043
|
+
const buildProcessRootOnly = (
|
|
2039
2044
|
args: unknown[],
|
|
2040
2045
|
options: Record<string, unknown>,
|
|
2041
|
-
|
|
2046
|
+
defaultProcessRoot: string,
|
|
2042
2047
|
): unknown[] => {
|
|
2043
2048
|
const invocationArgs: unknown[] = [...args];
|
|
2044
|
-
const
|
|
2045
|
-
invocationArgs.push(
|
|
2049
|
+
const processRoot = options.processRoot;
|
|
2050
|
+
invocationArgs.push(
|
|
2051
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2052
|
+
);
|
|
2046
2053
|
return invocationArgs;
|
|
2047
2054
|
};
|
|
2048
2055
|
|
|
2049
2056
|
const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
2050
|
-
"agents.setActive":
|
|
2051
|
-
"agents.resolveAgentsRootFrom":
|
|
2052
|
-
"projects.setActive":
|
|
2053
|
-
"projects.generateSpec":
|
|
2054
|
-
"projects.syncTasks":
|
|
2055
|
-
"projects.clearIssues":
|
|
2056
|
-
"projects.fetchGitTasks":
|
|
2057
|
-
"projects.createGitIssue":
|
|
2058
|
-
"projects.readGitTask":
|
|
2059
|
-
"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,
|
|
2060
2067
|
"agents.resolveTargetFile": buildOptionsOnly,
|
|
2061
2068
|
"projects.resolveProjectTargetFile": buildOptionsOnly,
|
|
2062
|
-
"agents.loadAgent":
|
|
2063
|
-
"agents.loadAgentPrompt":
|
|
2064
|
-
"projects.resolveImplementationPlan": (args, options,
|
|
2069
|
+
"agents.loadAgent": buildProcessRootOnly,
|
|
2070
|
+
"agents.loadAgentPrompt": buildProcessRootOnly,
|
|
2071
|
+
"projects.resolveImplementationPlan": (args, options, _defaultProcessRoot) => {
|
|
2065
2072
|
const invocationArgs: unknown[] = [...args];
|
|
2066
2073
|
const remaining = { ...options };
|
|
2067
|
-
const
|
|
2068
|
-
if (typeof
|
|
2069
|
-
delete remaining.
|
|
2074
|
+
const processRoot = remaining.processRoot;
|
|
2075
|
+
if (typeof processRoot === "string") {
|
|
2076
|
+
delete remaining.processRoot;
|
|
2070
2077
|
}
|
|
2078
|
+
delete remaining.repoRoot;
|
|
2071
2079
|
|
|
2072
2080
|
// This tool is a low-level helper: projects.resolveImplementationPlan(projectRoot, inputFile?, options?)
|
|
2073
2081
|
// If the caller provides options but no inputFile, preserve the positional slot.
|
|
@@ -2078,17 +2086,17 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
|
2078
2086
|
invocationArgs.push(remaining);
|
|
2079
2087
|
}
|
|
2080
2088
|
|
|
2081
|
-
// Intentionally do NOT append
|
|
2089
|
+
// Intentionally do NOT append processRoot: projectRoot is the first positional argument.
|
|
2082
2090
|
return invocationArgs;
|
|
2083
2091
|
},
|
|
2084
|
-
"agents.main":
|
|
2085
|
-
"agents.resolveAgentsRoot":
|
|
2086
|
-
"agents.listAgents":
|
|
2087
|
-
"projects.resolveProjectRoot":
|
|
2088
|
-
"projects.listProjects":
|
|
2089
|
-
"projects.listProjectDocs":
|
|
2090
|
-
"projects.readProjectDoc":
|
|
2091
|
-
"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,
|
|
2092
2100
|
};
|
|
2093
2101
|
|
|
2094
2102
|
const invokeTool = async (
|
|
@@ -2104,14 +2112,14 @@ const invokeTool = async (
|
|
|
2104
2112
|
};
|
|
2105
2113
|
const invoke =
|
|
2106
2114
|
toolInvocationPlans[tool.name] ??
|
|
2107
|
-
((rawArgs, rawOptions,
|
|
2115
|
+
((rawArgs, rawOptions, _processRoot) => {
|
|
2108
2116
|
const invocationArgs = [...rawArgs];
|
|
2109
2117
|
if (Object.keys(rawOptions).length > 0) {
|
|
2110
2118
|
invocationArgs.push(rawOptions);
|
|
2111
2119
|
}
|
|
2112
2120
|
return invocationArgs;
|
|
2113
2121
|
});
|
|
2114
|
-
const invocationArgs = invoke(args, options, repoCtx.
|
|
2122
|
+
const invocationArgs = invoke(args, options, repoCtx.defaultProcessRoot);
|
|
2115
2123
|
|
|
2116
2124
|
return Promise.resolve(tool.method(...invocationArgs));
|
|
2117
2125
|
};
|
|
@@ -2121,13 +2129,8 @@ export interface ExampleMcpServerOptions {
|
|
|
2121
2129
|
serverVersion?: string;
|
|
2122
2130
|
toolsPrefix?: string;
|
|
2123
2131
|
/**
|
|
2124
|
-
* Optional default repo
|
|
2125
|
-
* If omitted,
|
|
2126
|
-
*/
|
|
2127
|
-
repoRoot?: string;
|
|
2128
|
-
/**
|
|
2129
|
-
* Optional default repo name (LLM-friendly identifier).
|
|
2130
|
-
* 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).
|
|
2131
2134
|
*/
|
|
2132
2135
|
repoName?: string;
|
|
2133
2136
|
allowedRootEndpoints?: string[];
|
|
@@ -2262,48 +2265,23 @@ export const createExampleMcpServer = (
|
|
|
2262
2265
|
): ExampleMcpServerInstance => {
|
|
2263
2266
|
let toolCatalog: unknown[] = [];
|
|
2264
2267
|
|
|
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
2268
|
const serverFileDir = path.dirname(fileURLToPath(import.meta.url));
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
-
}
|
|
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,
|
|
2277
|
+
);
|
|
2278
|
+
|
|
2302
2279
|
const repoCtx: RepoResolutionContext = {
|
|
2303
|
-
|
|
2280
|
+
defaultProcessRoot,
|
|
2304
2281
|
defaultRepoName,
|
|
2305
|
-
|
|
2282
|
+
defaultRepoFullName,
|
|
2306
2283
|
repoNames,
|
|
2284
|
+
repoNameByKey,
|
|
2307
2285
|
};
|
|
2308
2286
|
|
|
2309
2287
|
const parseString = (value: unknown): string | null => {
|
|
@@ -2344,7 +2322,7 @@ export const createExampleMcpServer = (
|
|
|
2344
2322
|
"F0 MCP helper tools:",
|
|
2345
2323
|
"- mcp.listTools: returns tool catalog with access + invocation hints",
|
|
2346
2324
|
"- mcp.describeTool: describe one tool by name (prefixed or unprefixed)",
|
|
2347
|
-
"- mcp.workspace: explain
|
|
2325
|
+
"- mcp.workspace: explain Git workspace context (cwd, repoName selectors, projects)",
|
|
2348
2326
|
"- mcp.search: LLM-friendly search over project docs/spec (local-first)",
|
|
2349
2327
|
"",
|
|
2350
2328
|
'Tip: Prefer mcp.search for "search spec/docs" requests.',
|
|
@@ -2356,35 +2334,35 @@ export const createExampleMcpServer = (
|
|
|
2356
2334
|
? {
|
|
2357
2335
|
keys: Object.keys(input),
|
|
2358
2336
|
repoName: (input as any).repoName ?? null,
|
|
2359
|
-
repoRoot: (input as any).repoRoot ?? null,
|
|
2360
|
-
processRoot: (input as any).processRoot ?? null,
|
|
2361
2337
|
}
|
|
2362
|
-
: { keys: [], repoName: null
|
|
2338
|
+
: { keys: [], repoName: null };
|
|
2363
2339
|
const requestedRepoName = parseString(payload.repoName);
|
|
2364
2340
|
const resolved = resolveRepoSelectorOptions(payload, repoCtx);
|
|
2365
|
-
const
|
|
2366
|
-
typeof resolved.
|
|
2367
|
-
? resolved.
|
|
2368
|
-
: repoCtx.
|
|
2369
|
-
const effectiveRepoName =
|
|
2370
|
-
requestedRepoName
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
const projectsDir = path.join(
|
|
2375
|
-
const apiDir = path.join(
|
|
2376
|
-
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");
|
|
2377
2353
|
const hasProjectsDir = isDir(projectsDir);
|
|
2378
2354
|
const hasApiDir = isDir(apiDir);
|
|
2379
2355
|
const hasAgentsDir = isDir(agentsDir);
|
|
2380
2356
|
|
|
2381
|
-
const projects = hasProjectsDir
|
|
2357
|
+
const projects = hasProjectsDir
|
|
2358
|
+
? projectsApi.listProjects(processRoot)
|
|
2359
|
+
: [];
|
|
2382
2360
|
const hint = (() => {
|
|
2383
2361
|
if (!hasProjectsDir) {
|
|
2384
2362
|
return [
|
|
2385
|
-
"
|
|
2386
|
-
"
|
|
2387
|
-
"
|
|
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.",
|
|
2388
2366
|
].join(" ");
|
|
2389
2367
|
}
|
|
2390
2368
|
if (hasProjectsDir && projects.length === 0) {
|
|
@@ -2400,9 +2378,10 @@ export const createExampleMcpServer = (
|
|
|
2400
2378
|
received,
|
|
2401
2379
|
cwd: process.cwd(),
|
|
2402
2380
|
defaultRepoName: repoCtx.defaultRepoName,
|
|
2403
|
-
|
|
2381
|
+
defaultRepoFullName: repoCtx.defaultRepoFullName,
|
|
2382
|
+
workspaceRoot: repoCtx.defaultProcessRoot,
|
|
2404
2383
|
repoName: effectiveRepoName,
|
|
2405
|
-
|
|
2384
|
+
processRoot,
|
|
2406
2385
|
availableRepoNames: repoCtx.repoNames,
|
|
2407
2386
|
hasProjectsDir,
|
|
2408
2387
|
hasApiDir,
|
|
@@ -2452,10 +2431,10 @@ export const createExampleMcpServer = (
|
|
|
2452
2431
|
search: async (input: unknown) => {
|
|
2453
2432
|
const payload = isRecord(input) ? input : {};
|
|
2454
2433
|
const resolved = resolveRepoSelectorOptions(payload, repoCtx);
|
|
2455
|
-
const
|
|
2456
|
-
typeof resolved.
|
|
2457
|
-
? resolved.
|
|
2458
|
-
: repoCtx.
|
|
2434
|
+
const processRoot =
|
|
2435
|
+
typeof resolved.processRoot === "string"
|
|
2436
|
+
? resolved.processRoot
|
|
2437
|
+
: repoCtx.defaultProcessRoot;
|
|
2459
2438
|
|
|
2460
2439
|
const sectionRaw = parseString(payload.section)?.toLowerCase();
|
|
2461
2440
|
const section = sectionRaw === "docs" ? "docs" : "spec";
|
|
@@ -2502,11 +2481,11 @@ export const createExampleMcpServer = (
|
|
|
2502
2481
|
|
|
2503
2482
|
const projectName = ensureProjectName(
|
|
2504
2483
|
parseString(payload.projectName),
|
|
2505
|
-
|
|
2484
|
+
processRoot,
|
|
2506
2485
|
);
|
|
2507
2486
|
|
|
2508
2487
|
const searchOptions: Record<string, unknown> = {
|
|
2509
|
-
processRoot
|
|
2488
|
+
processRoot,
|
|
2510
2489
|
};
|
|
2511
2490
|
|
|
2512
2491
|
const source = parseString(payload.source)?.toLowerCase();
|
|
@@ -2829,9 +2808,9 @@ export const createExampleMcpServer = (
|
|
|
2829
2808
|
/projects[\\/].+projects[\\/]/i.test(message)
|
|
2830
2809
|
) {
|
|
2831
2810
|
details.hint =
|
|
2832
|
-
|
|
2811
|
+
'Repo selection is invalid. repoName must be "<repo>" or "<owner>/<repo>", for example "adl" or "F0/adl".';
|
|
2833
2812
|
details.suggestion =
|
|
2834
|
-
"Call mcp.workspace to see
|
|
2813
|
+
"Call mcp.workspace to see accepted repoName selectors.";
|
|
2835
2814
|
}
|
|
2836
2815
|
|
|
2837
2816
|
if (
|
|
@@ -2842,7 +2821,7 @@ export const createExampleMcpServer = (
|
|
|
2842
2821
|
details.hint =
|
|
2843
2822
|
"Repo selection might be wrong, or the server filesystem does not contain /projects for this workspace.";
|
|
2844
2823
|
details.suggestion =
|
|
2845
|
-
"Call mcp.workspace to see
|
|
2824
|
+
"Call mcp.workspace to see workspaceRoot and available repoName values.";
|
|
2846
2825
|
details.example = {
|
|
2847
2826
|
tool: prefix ? `${prefix}.mcp.workspace` : "mcp.workspace",
|
|
2848
2827
|
arguments: {},
|