@foundation0/api 1.1.8 → 1.1.11
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 +112 -67
- package/mcp/server.ts +272 -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
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const normalizeRepoRoot = (raw: string): string => {
|
|
279
|
-
const resolved = path.resolve(raw);
|
|
280
|
-
if (looksLikeRepoRoot(resolved)) return resolved;
|
|
281
|
-
|
|
282
|
-
// Common mistake: passing a project root like ".../projects/adl" as repoRoot.
|
|
283
|
-
// Try to find the containing repo root by walking up a few levels.
|
|
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;
|
|
305
|
+
return parseGitRemoteIdentityFromUrl(remoteUrl);
|
|
326
306
|
};
|
|
327
307
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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,105 @@ 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
|
+
if (repoSegment !== "." && repoSegment !== "..") {
|
|
397
|
+
next.processRoot = ctx.defaultProcessRoot;
|
|
398
|
+
next.repoName = repoSegment;
|
|
399
|
+
return next;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
433
404
|
const suggestions = ctx.repoNames
|
|
434
|
-
.filter((name) => name
|
|
405
|
+
.filter((name) => repoNameKey(name).includes(needle))
|
|
435
406
|
.slice(0, 8);
|
|
436
407
|
const hint =
|
|
437
408
|
suggestions.length > 0
|
|
438
409
|
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
439
|
-
: ctx.repoNames.
|
|
440
|
-
? ` Available repos: ${ctx.repoNames.join(", ")}.`
|
|
441
|
-
: "";
|
|
410
|
+
: ` Available repoName values: ${ctx.repoNames.join(", ")}.`;
|
|
442
411
|
throw new Error(
|
|
443
|
-
`Unknown repoName: ${repoName}.${hint} Tip:
|
|
412
|
+
`Unknown repoName: ${repoName}.${hint} Tip: use "adl" or "F0/adl" style selectors.`,
|
|
444
413
|
);
|
|
445
414
|
};
|
|
446
415
|
|
|
416
|
+
const normalizeRepoNameForDisplay = (
|
|
417
|
+
requestedRepoName: string | null,
|
|
418
|
+
ctx: RepoResolutionContext,
|
|
419
|
+
): string => {
|
|
420
|
+
if (!requestedRepoName) return ctx.defaultRepoName;
|
|
421
|
+
const canonical = ctx.repoNameByKey[repoNameKey(requestedRepoName)];
|
|
422
|
+
return canonical ?? requestedRepoName;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const resolveProcessRootFromGit = (fallbackDir: string): string => {
|
|
426
|
+
const cwdRoot = findGitRepoRoot(process.cwd());
|
|
427
|
+
if (cwdRoot) return cwdRoot;
|
|
428
|
+
const fallbackRoot = findGitRepoRoot(fallbackDir);
|
|
429
|
+
if (fallbackRoot) return fallbackRoot;
|
|
430
|
+
return path.resolve(process.cwd());
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const resolveRepoIdentityFromGit = (
|
|
434
|
+
processRoot: string,
|
|
435
|
+
): {
|
|
436
|
+
defaultRepoName: string;
|
|
437
|
+
defaultRepoFullName: string | null;
|
|
438
|
+
} => {
|
|
439
|
+
const gitIdentity = detectRepoIdentityFromGitConfig(processRoot);
|
|
440
|
+
if (gitIdentity) {
|
|
441
|
+
return {
|
|
442
|
+
defaultRepoName: gitIdentity.repo,
|
|
443
|
+
defaultRepoFullName: gitIdentity.fullName,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
defaultRepoName: path.basename(processRoot),
|
|
449
|
+
defaultRepoFullName: null,
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
|
|
447
453
|
type NormalizedToolPayload = {
|
|
448
454
|
args: unknown[];
|
|
449
455
|
options: Record<string, unknown>;
|
|
@@ -562,7 +568,7 @@ const normalizePayload = (payload: unknown): NormalizedToolPayload => {
|
|
|
562
568
|
|
|
563
569
|
return {
|
|
564
570
|
args,
|
|
565
|
-
options
|
|
571
|
+
options,
|
|
566
572
|
};
|
|
567
573
|
};
|
|
568
574
|
|
|
@@ -669,7 +675,7 @@ const coercePayloadForTool = (
|
|
|
669
675
|
|
|
670
676
|
switch (toolName) {
|
|
671
677
|
case "projects.listProjects": {
|
|
672
|
-
// No positional args.
|
|
678
|
+
// No positional args. processRoot is injected from the selected repoName.
|
|
673
679
|
break;
|
|
674
680
|
}
|
|
675
681
|
case "projects.resolveProjectRoot":
|
|
@@ -759,7 +765,7 @@ const coercePayloadForTool = (
|
|
|
759
765
|
break;
|
|
760
766
|
}
|
|
761
767
|
|
|
762
|
-
return { args, options
|
|
768
|
+
return { args, options };
|
|
763
769
|
};
|
|
764
770
|
|
|
765
771
|
const normalizeBatchToolCall = (
|
|
@@ -1099,7 +1105,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1099
1105
|
repoName: {
|
|
1100
1106
|
type: "string",
|
|
1101
1107
|
description:
|
|
1102
|
-
|
|
1108
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1103
1109
|
},
|
|
1104
1110
|
args: {
|
|
1105
1111
|
type: "array",
|
|
@@ -1125,7 +1131,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1125
1131
|
repoName: {
|
|
1126
1132
|
type: "string",
|
|
1127
1133
|
description:
|
|
1128
|
-
|
|
1134
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1129
1135
|
},
|
|
1130
1136
|
args: {
|
|
1131
1137
|
type: "array",
|
|
@@ -1156,7 +1162,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1156
1162
|
repoName: {
|
|
1157
1163
|
type: "string",
|
|
1158
1164
|
description:
|
|
1159
|
-
|
|
1165
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1160
1166
|
},
|
|
1161
1167
|
args: {
|
|
1162
1168
|
type: "array",
|
|
@@ -1234,7 +1240,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1234
1240
|
repoName: {
|
|
1235
1241
|
type: "string",
|
|
1236
1242
|
description:
|
|
1237
|
-
|
|
1243
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1238
1244
|
},
|
|
1239
1245
|
args: {
|
|
1240
1246
|
type: "array",
|
|
@@ -1312,7 +1318,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1312
1318
|
repoName: {
|
|
1313
1319
|
type: "string",
|
|
1314
1320
|
description:
|
|
1315
|
-
|
|
1321
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1316
1322
|
},
|
|
1317
1323
|
args: {
|
|
1318
1324
|
type: "array",
|
|
@@ -1429,7 +1435,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1429
1435
|
repoName: {
|
|
1430
1436
|
type: "string",
|
|
1431
1437
|
description:
|
|
1432
|
-
|
|
1438
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1433
1439
|
},
|
|
1434
1440
|
args: {
|
|
1435
1441
|
type: "array",
|
|
@@ -1471,7 +1477,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1471
1477
|
repoName: {
|
|
1472
1478
|
type: "string",
|
|
1473
1479
|
description:
|
|
1474
|
-
|
|
1480
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1475
1481
|
},
|
|
1476
1482
|
args: {
|
|
1477
1483
|
type: "array",
|
|
@@ -1529,7 +1535,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1529
1535
|
repoName: {
|
|
1530
1536
|
type: "string",
|
|
1531
1537
|
description:
|
|
1532
|
-
|
|
1538
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1533
1539
|
},
|
|
1534
1540
|
args: {
|
|
1535
1541
|
type: "array",
|
|
@@ -1564,7 +1570,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1564
1570
|
repoName: {
|
|
1565
1571
|
type: "string",
|
|
1566
1572
|
description:
|
|
1567
|
-
|
|
1573
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1568
1574
|
},
|
|
1569
1575
|
args: {
|
|
1570
1576
|
type: "array",
|
|
@@ -1586,7 +1592,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1586
1592
|
repoName: {
|
|
1587
1593
|
type: "string",
|
|
1588
1594
|
description:
|
|
1589
|
-
|
|
1595
|
+
'Optional repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1590
1596
|
},
|
|
1591
1597
|
},
|
|
1592
1598
|
required: [],
|
|
@@ -1654,7 +1660,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
|
|
|
1654
1660
|
repoName: {
|
|
1655
1661
|
type: "string",
|
|
1656
1662
|
description:
|
|
1657
|
-
|
|
1663
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1658
1664
|
},
|
|
1659
1665
|
},
|
|
1660
1666
|
$comment: safeJsonStringify({
|
|
@@ -1801,9 +1807,9 @@ const getInvocationPlanName = (toolName: string): string => {
|
|
|
1801
1807
|
const plan = toolInvocationPlans[toolName];
|
|
1802
1808
|
if (!plan) return "default";
|
|
1803
1809
|
if (plan === buildOptionsOnly) return "optionsOnly";
|
|
1804
|
-
if (plan ===
|
|
1805
|
-
if (plan ===
|
|
1806
|
-
if (plan ===
|
|
1810
|
+
if (plan === buildOptionsThenProcessRoot) return "optionsThenProcessRoot";
|
|
1811
|
+
if (plan === buildProcessRootThenOptions) return "processRootThenOptions";
|
|
1812
|
+
if (plan === buildProcessRootOnly) return "processRootOnly";
|
|
1807
1813
|
return "custom";
|
|
1808
1814
|
};
|
|
1809
1815
|
|
|
@@ -1815,16 +1821,16 @@ const buildInvocationExample = (toolName: string): Record<string, unknown> => {
|
|
|
1815
1821
|
const example: Record<string, unknown> = {};
|
|
1816
1822
|
if (requiredArgs && requiredArgs.length > 0) {
|
|
1817
1823
|
example.args = [...requiredArgs];
|
|
1818
|
-
} else if (plan !== "
|
|
1824
|
+
} else if (plan !== "processRootOnly") {
|
|
1819
1825
|
example.args = ["<arg0>"];
|
|
1820
1826
|
}
|
|
1821
1827
|
|
|
1822
|
-
if (plan === "
|
|
1828
|
+
if (plan === "processRootOnly") {
|
|
1823
1829
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1824
1830
|
return example;
|
|
1825
1831
|
}
|
|
1826
1832
|
|
|
1827
|
-
if (plan === "
|
|
1833
|
+
if (plan === "optionsThenProcessRoot" || plan === "processRootThenOptions") {
|
|
1828
1834
|
example.options = { repoName: "<repo-name>", ...defaultOptions };
|
|
1829
1835
|
return example;
|
|
1830
1836
|
}
|
|
@@ -1859,7 +1865,7 @@ const defaultToolInputSchema = (toolName: string) => ({
|
|
|
1859
1865
|
repoName: {
|
|
1860
1866
|
type: "string",
|
|
1861
1867
|
description:
|
|
1862
|
-
|
|
1868
|
+
'Repo selector. Accepts "<repo>" or "<owner>/<repo>". Omit to use the current Git workspace.',
|
|
1863
1869
|
},
|
|
1864
1870
|
},
|
|
1865
1871
|
$comment: safeJsonStringify({
|
|
@@ -1975,13 +1981,13 @@ const buildToolList = (
|
|
|
1975
1981
|
type ToolInvoker = (
|
|
1976
1982
|
args: unknown[],
|
|
1977
1983
|
options: Record<string, unknown>,
|
|
1978
|
-
|
|
1984
|
+
defaultProcessRoot: string,
|
|
1979
1985
|
) => unknown[];
|
|
1980
1986
|
|
|
1981
1987
|
const buildOptionsOnly = (
|
|
1982
1988
|
args: unknown[],
|
|
1983
1989
|
options: Record<string, unknown>,
|
|
1984
|
-
|
|
1990
|
+
_defaultProcessRoot: string,
|
|
1985
1991
|
): unknown[] => {
|
|
1986
1992
|
const invocationArgs: unknown[] = [...args];
|
|
1987
1993
|
if (Object.keys(options).length > 0) {
|
|
@@ -1990,44 +1996,48 @@ const buildOptionsOnly = (
|
|
|
1990
1996
|
return invocationArgs;
|
|
1991
1997
|
};
|
|
1992
1998
|
|
|
1993
|
-
const
|
|
1999
|
+
const buildOptionsThenProcessRoot = (
|
|
1994
2000
|
args: unknown[],
|
|
1995
2001
|
options: Record<string, unknown>,
|
|
1996
|
-
|
|
2002
|
+
defaultProcessRoot: string,
|
|
1997
2003
|
): unknown[] => {
|
|
1998
2004
|
const invocationArgs: unknown[] = [...args];
|
|
1999
2005
|
const remaining = { ...options };
|
|
2000
|
-
const
|
|
2001
|
-
if (typeof
|
|
2002
|
-
delete remaining.
|
|
2006
|
+
const processRoot = remaining.processRoot;
|
|
2007
|
+
if (typeof processRoot === "string") {
|
|
2008
|
+
delete remaining.processRoot;
|
|
2003
2009
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2010
|
+
delete remaining.repoRoot;
|
|
2011
|
+
const resolvedProcessRoot =
|
|
2012
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot;
|
|
2006
2013
|
|
|
2007
2014
|
if (Object.keys(remaining).length > 0) {
|
|
2008
2015
|
invocationArgs.push(remaining);
|
|
2009
|
-
} else if (
|
|
2010
|
-
// Preserve positional slot for signatures like fn(projectName, options?,
|
|
2016
|
+
} else if (resolvedProcessRoot) {
|
|
2017
|
+
// Preserve positional slot for signatures like fn(projectName, options?, processRoot?).
|
|
2011
2018
|
invocationArgs.push({});
|
|
2012
2019
|
}
|
|
2013
|
-
invocationArgs.push(
|
|
2020
|
+
invocationArgs.push(resolvedProcessRoot);
|
|
2014
2021
|
|
|
2015
2022
|
return invocationArgs;
|
|
2016
2023
|
};
|
|
2017
2024
|
|
|
2018
|
-
const
|
|
2025
|
+
const buildProcessRootThenOptions = (
|
|
2019
2026
|
args: unknown[],
|
|
2020
2027
|
options: Record<string, unknown>,
|
|
2021
|
-
|
|
2028
|
+
defaultProcessRoot: string,
|
|
2022
2029
|
): unknown[] => {
|
|
2023
2030
|
const invocationArgs: unknown[] = [...args];
|
|
2024
2031
|
const remaining = { ...options };
|
|
2025
|
-
const
|
|
2026
|
-
if (typeof
|
|
2027
|
-
delete remaining.
|
|
2032
|
+
const processRoot = remaining.processRoot;
|
|
2033
|
+
if (typeof processRoot === "string") {
|
|
2034
|
+
delete remaining.processRoot;
|
|
2028
2035
|
}
|
|
2036
|
+
delete remaining.repoRoot;
|
|
2029
2037
|
|
|
2030
|
-
invocationArgs.push(
|
|
2038
|
+
invocationArgs.push(
|
|
2039
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2040
|
+
);
|
|
2031
2041
|
if (Object.keys(remaining).length > 0) {
|
|
2032
2042
|
invocationArgs.push(remaining);
|
|
2033
2043
|
}
|
|
@@ -2035,39 +2045,42 @@ const buildRepoRootThenOptions = (
|
|
|
2035
2045
|
return invocationArgs;
|
|
2036
2046
|
};
|
|
2037
2047
|
|
|
2038
|
-
const
|
|
2048
|
+
const buildProcessRootOnly = (
|
|
2039
2049
|
args: unknown[],
|
|
2040
2050
|
options: Record<string, unknown>,
|
|
2041
|
-
|
|
2051
|
+
defaultProcessRoot: string,
|
|
2042
2052
|
): unknown[] => {
|
|
2043
2053
|
const invocationArgs: unknown[] = [...args];
|
|
2044
|
-
const
|
|
2045
|
-
invocationArgs.push(
|
|
2054
|
+
const processRoot = options.processRoot;
|
|
2055
|
+
invocationArgs.push(
|
|
2056
|
+
typeof processRoot === "string" ? processRoot : defaultProcessRoot,
|
|
2057
|
+
);
|
|
2046
2058
|
return invocationArgs;
|
|
2047
2059
|
};
|
|
2048
2060
|
|
|
2049
2061
|
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":
|
|
2062
|
+
"agents.setActive": buildProcessRootThenOptions,
|
|
2063
|
+
"agents.resolveAgentsRootFrom": buildProcessRootOnly,
|
|
2064
|
+
"projects.setActive": buildProcessRootThenOptions,
|
|
2065
|
+
"projects.generateSpec": buildOptionsThenProcessRoot,
|
|
2066
|
+
"projects.syncTasks": buildOptionsThenProcessRoot,
|
|
2067
|
+
"projects.clearIssues": buildOptionsThenProcessRoot,
|
|
2068
|
+
"projects.fetchGitTasks": buildOptionsThenProcessRoot,
|
|
2069
|
+
"projects.createGitIssue": buildOptionsThenProcessRoot,
|
|
2070
|
+
"projects.readGitTask": buildOptionsThenProcessRoot,
|
|
2071
|
+
"projects.writeGitTask": buildOptionsThenProcessRoot,
|
|
2060
2072
|
"agents.resolveTargetFile": buildOptionsOnly,
|
|
2061
2073
|
"projects.resolveProjectTargetFile": buildOptionsOnly,
|
|
2062
|
-
"agents.loadAgent":
|
|
2063
|
-
"agents.loadAgentPrompt":
|
|
2064
|
-
"projects.resolveImplementationPlan": (args, options,
|
|
2074
|
+
"agents.loadAgent": buildProcessRootOnly,
|
|
2075
|
+
"agents.loadAgentPrompt": buildProcessRootOnly,
|
|
2076
|
+
"projects.resolveImplementationPlan": (args, options, _defaultProcessRoot) => {
|
|
2065
2077
|
const invocationArgs: unknown[] = [...args];
|
|
2066
2078
|
const remaining = { ...options };
|
|
2067
|
-
const
|
|
2068
|
-
if (typeof
|
|
2069
|
-
delete remaining.
|
|
2079
|
+
const processRoot = remaining.processRoot;
|
|
2080
|
+
if (typeof processRoot === "string") {
|
|
2081
|
+
delete remaining.processRoot;
|
|
2070
2082
|
}
|
|
2083
|
+
delete remaining.repoRoot;
|
|
2071
2084
|
|
|
2072
2085
|
// This tool is a low-level helper: projects.resolveImplementationPlan(projectRoot, inputFile?, options?)
|
|
2073
2086
|
// If the caller provides options but no inputFile, preserve the positional slot.
|
|
@@ -2078,17 +2091,17 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
|
|
|
2078
2091
|
invocationArgs.push(remaining);
|
|
2079
2092
|
}
|
|
2080
2093
|
|
|
2081
|
-
// Intentionally do NOT append
|
|
2094
|
+
// Intentionally do NOT append processRoot: projectRoot is the first positional argument.
|
|
2082
2095
|
return invocationArgs;
|
|
2083
2096
|
},
|
|
2084
|
-
"agents.main":
|
|
2085
|
-
"agents.resolveAgentsRoot":
|
|
2086
|
-
"agents.listAgents":
|
|
2087
|
-
"projects.resolveProjectRoot":
|
|
2088
|
-
"projects.listProjects":
|
|
2089
|
-
"projects.listProjectDocs":
|
|
2090
|
-
"projects.readProjectDoc":
|
|
2091
|
-
"projects.main":
|
|
2097
|
+
"agents.main": buildProcessRootOnly,
|
|
2098
|
+
"agents.resolveAgentsRoot": buildProcessRootOnly,
|
|
2099
|
+
"agents.listAgents": buildProcessRootOnly,
|
|
2100
|
+
"projects.resolveProjectRoot": buildProcessRootOnly,
|
|
2101
|
+
"projects.listProjects": buildProcessRootOnly,
|
|
2102
|
+
"projects.listProjectDocs": buildProcessRootOnly,
|
|
2103
|
+
"projects.readProjectDoc": buildProcessRootOnly,
|
|
2104
|
+
"projects.main": buildProcessRootOnly,
|
|
2092
2105
|
};
|
|
2093
2106
|
|
|
2094
2107
|
const invokeTool = async (
|
|
@@ -2104,14 +2117,14 @@ const invokeTool = async (
|
|
|
2104
2117
|
};
|
|
2105
2118
|
const invoke =
|
|
2106
2119
|
toolInvocationPlans[tool.name] ??
|
|
2107
|
-
((rawArgs, rawOptions,
|
|
2120
|
+
((rawArgs, rawOptions, _processRoot) => {
|
|
2108
2121
|
const invocationArgs = [...rawArgs];
|
|
2109
2122
|
if (Object.keys(rawOptions).length > 0) {
|
|
2110
2123
|
invocationArgs.push(rawOptions);
|
|
2111
2124
|
}
|
|
2112
2125
|
return invocationArgs;
|
|
2113
2126
|
});
|
|
2114
|
-
const invocationArgs = invoke(args, options, repoCtx.
|
|
2127
|
+
const invocationArgs = invoke(args, options, repoCtx.defaultProcessRoot);
|
|
2115
2128
|
|
|
2116
2129
|
return Promise.resolve(tool.method(...invocationArgs));
|
|
2117
2130
|
};
|
|
@@ -2121,13 +2134,8 @@ export interface ExampleMcpServerOptions {
|
|
|
2121
2134
|
serverVersion?: string;
|
|
2122
2135
|
toolsPrefix?: string;
|
|
2123
2136
|
/**
|
|
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.
|
|
2137
|
+
* Optional default repo selector (for MCP repoName matching).
|
|
2138
|
+
* If omitted, auto-detected from git remote (fallback: workspace folder name).
|
|
2131
2139
|
*/
|
|
2132
2140
|
repoName?: string;
|
|
2133
2141
|
allowedRootEndpoints?: string[];
|
|
@@ -2262,48 +2270,23 @@ export const createExampleMcpServer = (
|
|
|
2262
2270
|
): ExampleMcpServerInstance => {
|
|
2263
2271
|
let toolCatalog: unknown[] = [];
|
|
2264
2272
|
|
|
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
2273
|
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
|
-
}
|
|
2274
|
+
const defaultProcessRoot = resolveProcessRootFromGit(serverFileDir);
|
|
2275
|
+
const configuredRepoName = options.repoName?.trim() || null;
|
|
2276
|
+
const resolvedRepoIdentity = resolveRepoIdentityFromGit(defaultProcessRoot);
|
|
2277
|
+
const defaultRepoName = configuredRepoName || resolvedRepoIdentity.defaultRepoName;
|
|
2278
|
+
const defaultRepoFullName = resolvedRepoIdentity.defaultRepoFullName;
|
|
2279
|
+
const { repoNames, repoNameByKey } = buildRepoNameContext(
|
|
2280
|
+
defaultRepoName,
|
|
2281
|
+
defaultRepoFullName,
|
|
2282
|
+
);
|
|
2283
|
+
|
|
2302
2284
|
const repoCtx: RepoResolutionContext = {
|
|
2303
|
-
|
|
2285
|
+
defaultProcessRoot,
|
|
2304
2286
|
defaultRepoName,
|
|
2305
|
-
|
|
2287
|
+
defaultRepoFullName,
|
|
2306
2288
|
repoNames,
|
|
2289
|
+
repoNameByKey,
|
|
2307
2290
|
};
|
|
2308
2291
|
|
|
2309
2292
|
const parseString = (value: unknown): string | null => {
|
|
@@ -2344,7 +2327,7 @@ export const createExampleMcpServer = (
|
|
|
2344
2327
|
"F0 MCP helper tools:",
|
|
2345
2328
|
"- mcp.listTools: returns tool catalog with access + invocation hints",
|
|
2346
2329
|
"- mcp.describeTool: describe one tool by name (prefixed or unprefixed)",
|
|
2347
|
-
"- mcp.workspace: explain
|
|
2330
|
+
"- mcp.workspace: explain Git workspace context (cwd, repoName selectors, projects)",
|
|
2348
2331
|
"- mcp.search: LLM-friendly search over project docs/spec (local-first)",
|
|
2349
2332
|
"",
|
|
2350
2333
|
'Tip: Prefer mcp.search for "search spec/docs" requests.',
|
|
@@ -2356,35 +2339,35 @@ export const createExampleMcpServer = (
|
|
|
2356
2339
|
? {
|
|
2357
2340
|
keys: Object.keys(input),
|
|
2358
2341
|
repoName: (input as any).repoName ?? null,
|
|
2359
|
-
repoRoot: (input as any).repoRoot ?? null,
|
|
2360
|
-
processRoot: (input as any).processRoot ?? null,
|
|
2361
2342
|
}
|
|
2362
|
-
: { keys: [], repoName: null
|
|
2343
|
+
: { keys: [], repoName: null };
|
|
2363
2344
|
const requestedRepoName = parseString(payload.repoName);
|
|
2364
2345
|
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(
|
|
2346
|
+
const processRoot =
|
|
2347
|
+
typeof resolved.processRoot === "string"
|
|
2348
|
+
? resolved.processRoot
|
|
2349
|
+
: repoCtx.defaultProcessRoot;
|
|
2350
|
+
const effectiveRepoName = normalizeRepoNameForDisplay(
|
|
2351
|
+
requestedRepoName,
|
|
2352
|
+
repoCtx,
|
|
2353
|
+
);
|
|
2354
|
+
|
|
2355
|
+
const projectsDir = path.join(processRoot, "projects");
|
|
2356
|
+
const apiDir = path.join(processRoot, "api");
|
|
2357
|
+
const agentsDir = path.join(processRoot, "agents");
|
|
2377
2358
|
const hasProjectsDir = isDir(projectsDir);
|
|
2378
2359
|
const hasApiDir = isDir(apiDir);
|
|
2379
2360
|
const hasAgentsDir = isDir(agentsDir);
|
|
2380
2361
|
|
|
2381
|
-
const projects = hasProjectsDir
|
|
2362
|
+
const projects = hasProjectsDir
|
|
2363
|
+
? projectsApi.listProjects(processRoot)
|
|
2364
|
+
: [];
|
|
2382
2365
|
const hint = (() => {
|
|
2383
2366
|
if (!hasProjectsDir) {
|
|
2384
2367
|
return [
|
|
2385
|
-
"
|
|
2386
|
-
"
|
|
2387
|
-
"
|
|
2368
|
+
"Git workspace does not contain /projects.",
|
|
2369
|
+
"Pass repoName as \"adl\" or \"F0/adl\".",
|
|
2370
|
+
"For single-repo layouts, use source:\"auto\" or source:\"gitea\" on search tools.",
|
|
2388
2371
|
].join(" ");
|
|
2389
2372
|
}
|
|
2390
2373
|
if (hasProjectsDir && projects.length === 0) {
|
|
@@ -2400,9 +2383,10 @@ export const createExampleMcpServer = (
|
|
|
2400
2383
|
received,
|
|
2401
2384
|
cwd: process.cwd(),
|
|
2402
2385
|
defaultRepoName: repoCtx.defaultRepoName,
|
|
2403
|
-
|
|
2386
|
+
defaultRepoFullName: repoCtx.defaultRepoFullName,
|
|
2387
|
+
workspaceRoot: repoCtx.defaultProcessRoot,
|
|
2404
2388
|
repoName: effectiveRepoName,
|
|
2405
|
-
|
|
2389
|
+
processRoot,
|
|
2406
2390
|
availableRepoNames: repoCtx.repoNames,
|
|
2407
2391
|
hasProjectsDir,
|
|
2408
2392
|
hasApiDir,
|
|
@@ -2452,10 +2436,10 @@ export const createExampleMcpServer = (
|
|
|
2452
2436
|
search: async (input: unknown) => {
|
|
2453
2437
|
const payload = isRecord(input) ? input : {};
|
|
2454
2438
|
const resolved = resolveRepoSelectorOptions(payload, repoCtx);
|
|
2455
|
-
const
|
|
2456
|
-
typeof resolved.
|
|
2457
|
-
? resolved.
|
|
2458
|
-
: repoCtx.
|
|
2439
|
+
const processRoot =
|
|
2440
|
+
typeof resolved.processRoot === "string"
|
|
2441
|
+
? resolved.processRoot
|
|
2442
|
+
: repoCtx.defaultProcessRoot;
|
|
2459
2443
|
|
|
2460
2444
|
const sectionRaw = parseString(payload.section)?.toLowerCase();
|
|
2461
2445
|
const section = sectionRaw === "docs" ? "docs" : "spec";
|
|
@@ -2502,11 +2486,11 @@ export const createExampleMcpServer = (
|
|
|
2502
2486
|
|
|
2503
2487
|
const projectName = ensureProjectName(
|
|
2504
2488
|
parseString(payload.projectName),
|
|
2505
|
-
|
|
2489
|
+
processRoot,
|
|
2506
2490
|
);
|
|
2507
2491
|
|
|
2508
2492
|
const searchOptions: Record<string, unknown> = {
|
|
2509
|
-
processRoot
|
|
2493
|
+
processRoot,
|
|
2510
2494
|
};
|
|
2511
2495
|
|
|
2512
2496
|
const source = parseString(payload.source)?.toLowerCase();
|
|
@@ -2829,9 +2813,9 @@ export const createExampleMcpServer = (
|
|
|
2829
2813
|
/projects[\\/].+projects[\\/]/i.test(message)
|
|
2830
2814
|
) {
|
|
2831
2815
|
details.hint =
|
|
2832
|
-
|
|
2816
|
+
'Repo selection is invalid. repoName must be "<repo>" or "<owner>/<repo>", for example "adl" or "F0/adl".';
|
|
2833
2817
|
details.suggestion =
|
|
2834
|
-
"Call mcp.workspace to see
|
|
2818
|
+
"Call mcp.workspace to see accepted repoName selectors.";
|
|
2835
2819
|
}
|
|
2836
2820
|
|
|
2837
2821
|
if (
|
|
@@ -2842,7 +2826,7 @@ export const createExampleMcpServer = (
|
|
|
2842
2826
|
details.hint =
|
|
2843
2827
|
"Repo selection might be wrong, or the server filesystem does not contain /projects for this workspace.";
|
|
2844
2828
|
details.suggestion =
|
|
2845
|
-
"Call mcp.workspace to see
|
|
2829
|
+
"Call mcp.workspace to see workspaceRoot and available repoName values.";
|
|
2846
2830
|
details.example = {
|
|
2847
2831
|
tool: prefix ? `${prefix}.mcp.workspace` : "mcp.workspace",
|
|
2848
2832
|
arguments: {},
|