@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.test.ts
CHANGED
|
@@ -204,7 +204,8 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
204
204
|
}
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
it("accepts repoName
|
|
207
|
+
it("accepts repoName selectors in short and owner/repo format", async () => {
|
|
208
|
+
const originalCwd = process.cwd();
|
|
208
209
|
const tempDir = await fs.mkdtemp(
|
|
209
210
|
path.join(os.tmpdir(), "f0-mcp-server-reponame-"),
|
|
210
211
|
);
|
|
@@ -216,85 +217,75 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
216
217
|
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
217
218
|
recursive: true,
|
|
218
219
|
});
|
|
220
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
221
|
+
await fs.writeFile(
|
|
222
|
+
path.join(tempDir, ".git", "config"),
|
|
223
|
+
[
|
|
224
|
+
'[remote "origin"]',
|
|
225
|
+
"\turl = https://example.com/F0/adl.git",
|
|
226
|
+
"",
|
|
227
|
+
].join("\n"),
|
|
228
|
+
);
|
|
219
229
|
|
|
220
|
-
|
|
230
|
+
process.chdir(tempDir);
|
|
231
|
+
const instance = createExampleMcpServer();
|
|
221
232
|
const handler = getToolHandler(instance);
|
|
222
233
|
|
|
223
|
-
const
|
|
234
|
+
const shortSelector = await handler(
|
|
224
235
|
{
|
|
225
236
|
method: "tools/call",
|
|
226
237
|
params: {
|
|
227
238
|
name: "projects.listProjects",
|
|
228
239
|
arguments: {
|
|
229
|
-
repoName: "
|
|
240
|
+
repoName: "adl",
|
|
230
241
|
},
|
|
231
242
|
},
|
|
232
243
|
},
|
|
233
244
|
{},
|
|
234
245
|
);
|
|
246
|
+
expect(shortSelector.isError).toBe(false);
|
|
247
|
+
const shortPayload = JSON.parse(shortSelector.content?.[0]?.text ?? "{}");
|
|
248
|
+
expect(shortPayload.ok).toBe(true);
|
|
249
|
+
expect(shortPayload.result).toContain("adl");
|
|
250
|
+
expect(shortPayload.result).toContain("beta");
|
|
235
251
|
|
|
236
|
-
|
|
237
|
-
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
238
|
-
expect(payload.ok).toBe(true);
|
|
239
|
-
expect(payload.result).toContain("adl");
|
|
240
|
-
expect(payload.result).toContain("beta");
|
|
241
|
-
} finally {
|
|
242
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("auto-detects repoRoot from process.cwd() when repoName is omitted", async () => {
|
|
247
|
-
const tempDir = await fs.mkdtemp(
|
|
248
|
-
path.join(os.tmpdir(), "f0-mcp-server-autoreporoot-"),
|
|
249
|
-
);
|
|
250
|
-
const originalCwd = process.cwd();
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
254
|
-
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
255
|
-
recursive: true,
|
|
256
|
-
});
|
|
257
|
-
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
258
|
-
recursive: true,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
process.chdir(tempDir);
|
|
262
|
-
|
|
263
|
-
const instance = createExampleMcpServer();
|
|
264
|
-
const handler = getToolHandler(instance);
|
|
265
|
-
|
|
266
|
-
const result = await handler(
|
|
252
|
+
const fullSelector = await handler(
|
|
267
253
|
{
|
|
268
254
|
method: "tools/call",
|
|
269
255
|
params: {
|
|
270
256
|
name: "projects.listProjects",
|
|
271
|
-
arguments: {
|
|
257
|
+
arguments: {
|
|
258
|
+
repoName: "F0/adl",
|
|
259
|
+
},
|
|
272
260
|
},
|
|
273
261
|
},
|
|
274
262
|
{},
|
|
275
263
|
);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
expect(
|
|
280
|
-
expect(
|
|
281
|
-
expect(payload.result).toContain("beta");
|
|
264
|
+
expect(fullSelector.isError).toBe(false);
|
|
265
|
+
const fullPayload = JSON.parse(fullSelector.content?.[0]?.text ?? "{}");
|
|
266
|
+
expect(fullPayload.ok).toBe(true);
|
|
267
|
+
expect(fullPayload.result).toContain("adl");
|
|
268
|
+
expect(fullPayload.result).toContain("beta");
|
|
282
269
|
} finally {
|
|
283
270
|
process.chdir(originalCwd);
|
|
284
271
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
285
272
|
}
|
|
286
273
|
});
|
|
287
274
|
|
|
288
|
-
it("
|
|
275
|
+
it("parses owner/repo repoName into repo segment even without full-name metadata", async () => {
|
|
276
|
+
const originalCwd = process.cwd();
|
|
289
277
|
const tempDir = await fs.mkdtemp(
|
|
290
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
278
|
+
path.join(os.tmpdir(), "f0-mcp-server-reponame-segment-fallback-"),
|
|
291
279
|
);
|
|
292
280
|
try {
|
|
293
281
|
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
294
282
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
295
283
|
recursive: true,
|
|
296
284
|
});
|
|
285
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
286
|
+
await fs.writeFile(path.join(tempDir, ".git", "config"), "", "utf8");
|
|
297
287
|
|
|
288
|
+
process.chdir(tempDir);
|
|
298
289
|
const instance = createExampleMcpServer();
|
|
299
290
|
const handler = getToolHandler(instance);
|
|
300
291
|
|
|
@@ -304,7 +295,7 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
304
295
|
params: {
|
|
305
296
|
name: "projects.listProjects",
|
|
306
297
|
arguments: {
|
|
307
|
-
|
|
298
|
+
repoName: "F0/adl",
|
|
308
299
|
},
|
|
309
300
|
},
|
|
310
301
|
},
|
|
@@ -316,14 +307,17 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
316
307
|
expect(payload.ok).toBe(true);
|
|
317
308
|
expect(payload.result).toContain("adl");
|
|
318
309
|
} finally {
|
|
310
|
+
process.chdir(originalCwd);
|
|
319
311
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
320
312
|
}
|
|
321
313
|
});
|
|
322
314
|
|
|
323
|
-
it("
|
|
315
|
+
it("auto-detects Git workspace from process.cwd() when repoName is omitted", async () => {
|
|
324
316
|
const tempDir = await fs.mkdtemp(
|
|
325
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
317
|
+
path.join(os.tmpdir(), "f0-mcp-server-autoworkspace-"),
|
|
326
318
|
);
|
|
319
|
+
const originalCwd = process.cwd();
|
|
320
|
+
|
|
327
321
|
try {
|
|
328
322
|
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
329
323
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
@@ -332,6 +326,17 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
332
326
|
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
333
327
|
recursive: true,
|
|
334
328
|
});
|
|
329
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
330
|
+
await fs.writeFile(
|
|
331
|
+
path.join(tempDir, ".git", "config"),
|
|
332
|
+
[
|
|
333
|
+
'[remote "origin"]',
|
|
334
|
+
"\turl = https://example.com/F0/adl.git",
|
|
335
|
+
"",
|
|
336
|
+
].join("\n"),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
process.chdir(tempDir);
|
|
335
340
|
|
|
336
341
|
const instance = createExampleMcpServer();
|
|
337
342
|
const handler = getToolHandler(instance);
|
|
@@ -341,9 +346,7 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
341
346
|
method: "tools/call",
|
|
342
347
|
params: {
|
|
343
348
|
name: "projects.listProjects",
|
|
344
|
-
arguments: {
|
|
345
|
-
repoRoot: path.join(tempDir, "projects", "adl"),
|
|
346
|
-
},
|
|
349
|
+
arguments: {},
|
|
347
350
|
},
|
|
348
351
|
},
|
|
349
352
|
{},
|
|
@@ -355,24 +358,41 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
355
358
|
expect(payload.result).toContain("adl");
|
|
356
359
|
expect(payload.result).toContain("beta");
|
|
357
360
|
} finally {
|
|
361
|
+
process.chdir(originalCwd);
|
|
358
362
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
359
363
|
}
|
|
360
364
|
});
|
|
361
365
|
|
|
362
|
-
it("
|
|
363
|
-
const
|
|
364
|
-
|
|
366
|
+
it("ignores legacy processRoot/repoRoot payload fields and uses Git workspace", async () => {
|
|
367
|
+
const originalCwd = process.cwd();
|
|
368
|
+
const workspaceA = await fs.mkdtemp(
|
|
369
|
+
path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-a-"),
|
|
370
|
+
);
|
|
371
|
+
const workspaceB = await fs.mkdtemp(
|
|
372
|
+
path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-b-"),
|
|
365
373
|
);
|
|
366
374
|
try {
|
|
367
|
-
await fs.mkdir(path.join(
|
|
368
|
-
await fs.mkdir(path.join(
|
|
375
|
+
await fs.mkdir(path.join(workspaceA, "api"), { recursive: true });
|
|
376
|
+
await fs.mkdir(path.join(workspaceA, "projects", "adl", "docs"), {
|
|
369
377
|
recursive: true,
|
|
370
378
|
});
|
|
371
|
-
await fs.mkdir(path.join(
|
|
379
|
+
await fs.mkdir(path.join(workspaceA, ".git"), { recursive: true });
|
|
380
|
+
await fs.writeFile(
|
|
381
|
+
path.join(workspaceA, ".git", "config"),
|
|
382
|
+
[
|
|
383
|
+
'[remote "origin"]',
|
|
384
|
+
"\turl = https://example.com/F0/adl.git",
|
|
385
|
+
"",
|
|
386
|
+
].join("\n"),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await fs.mkdir(path.join(workspaceB, "api"), { recursive: true });
|
|
390
|
+
await fs.mkdir(path.join(workspaceB, "projects", "beta", "docs"), {
|
|
372
391
|
recursive: true,
|
|
373
392
|
});
|
|
374
393
|
|
|
375
|
-
|
|
394
|
+
process.chdir(workspaceA);
|
|
395
|
+
const instance = createExampleMcpServer();
|
|
376
396
|
const handler = getToolHandler(instance);
|
|
377
397
|
|
|
378
398
|
const result = await handler(
|
|
@@ -380,7 +400,10 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
380
400
|
method: "tools/call",
|
|
381
401
|
params: {
|
|
382
402
|
name: "projects.listProjects",
|
|
383
|
-
arguments: {
|
|
403
|
+
arguments: {
|
|
404
|
+
processRoot: workspaceB,
|
|
405
|
+
repoRoot: workspaceB,
|
|
406
|
+
},
|
|
384
407
|
},
|
|
385
408
|
},
|
|
386
409
|
{},
|
|
@@ -390,13 +413,16 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
390
413
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
391
414
|
expect(payload.ok).toBe(true);
|
|
392
415
|
expect(payload.result).toContain("adl");
|
|
393
|
-
expect(payload.result).toContain("beta");
|
|
416
|
+
expect(payload.result).not.toContain("beta");
|
|
394
417
|
} finally {
|
|
395
|
-
|
|
418
|
+
process.chdir(originalCwd);
|
|
419
|
+
await fs.rm(workspaceA, { recursive: true, force: true });
|
|
420
|
+
await fs.rm(workspaceB, { recursive: true, force: true });
|
|
396
421
|
}
|
|
397
422
|
});
|
|
398
423
|
|
|
399
|
-
it("exposes mcp.workspace
|
|
424
|
+
it("exposes mcp.workspace with Git workspace context", async () => {
|
|
425
|
+
const originalCwd = process.cwd();
|
|
400
426
|
const tempDir = await fs.mkdtemp(
|
|
401
427
|
path.join(os.tmpdir(), "f0-mcp-server-workspace-"),
|
|
402
428
|
);
|
|
@@ -405,8 +431,18 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
405
431
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
406
432
|
recursive: true,
|
|
407
433
|
});
|
|
434
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
435
|
+
await fs.writeFile(
|
|
436
|
+
path.join(tempDir, ".git", "config"),
|
|
437
|
+
[
|
|
438
|
+
'[remote "origin"]',
|
|
439
|
+
"\turl = https://example.com/F0/adl.git",
|
|
440
|
+
"",
|
|
441
|
+
].join("\n"),
|
|
442
|
+
);
|
|
408
443
|
|
|
409
|
-
|
|
444
|
+
process.chdir(tempDir);
|
|
445
|
+
const instance = createExampleMcpServer();
|
|
410
446
|
const handler = getToolHandler(instance);
|
|
411
447
|
|
|
412
448
|
const result = await handler(
|
|
@@ -423,16 +459,21 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
423
459
|
expect(result.isError).toBe(false);
|
|
424
460
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
425
461
|
expect(payload.ok).toBe(true);
|
|
426
|
-
expect(payload.result.
|
|
427
|
-
expect(payload.result.
|
|
462
|
+
expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
|
|
463
|
+
expect(payload.result.processRoot).toBe(path.resolve(tempDir));
|
|
464
|
+
expect(payload.result.defaultRepoName).toBe("adl");
|
|
465
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
466
|
+
expect(payload.result.availableRepoNames).toContain("adl");
|
|
467
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
428
468
|
expect(payload.result.hasProjectsDir).toBe(true);
|
|
429
469
|
expect(Array.isArray(payload.result.projects)).toBe(true);
|
|
430
470
|
} finally {
|
|
471
|
+
process.chdir(originalCwd);
|
|
431
472
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
432
473
|
}
|
|
433
474
|
});
|
|
434
475
|
|
|
435
|
-
it("auto-detects repo
|
|
476
|
+
it("auto-detects repo identity from .git/config when no repoName is provided", async () => {
|
|
436
477
|
const originalCwd = process.cwd();
|
|
437
478
|
const tempDir = await fs.mkdtemp(
|
|
438
479
|
path.join(os.tmpdir(), "f0-mcp-server-git-detect-"),
|
|
@@ -470,11 +511,13 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
470
511
|
expect(workspace.isError).toBe(false);
|
|
471
512
|
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
472
513
|
expect(payload.ok).toBe(true);
|
|
473
|
-
expect(payload.result.
|
|
474
|
-
expect(payload.result.
|
|
514
|
+
expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
|
|
515
|
+
expect(payload.result.processRoot).toBe(path.resolve(tempDir));
|
|
475
516
|
expect(payload.result.defaultRepoName).toBe("adl");
|
|
517
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
476
518
|
expect(payload.result.repoName).toBe("adl");
|
|
477
519
|
expect(payload.result.availableRepoNames).toContain("adl");
|
|
520
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
478
521
|
|
|
479
522
|
const list = await handler(
|
|
480
523
|
{
|
|
@@ -538,6 +581,8 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
538
581
|
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
539
582
|
expect(payload.ok).toBe(true);
|
|
540
583
|
expect(payload.result.defaultRepoName).toBe("adl");
|
|
584
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
585
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
541
586
|
} finally {
|
|
542
587
|
process.chdir(originalCwd);
|
|
543
588
|
await fs.rm(tempDir, { recursive: true, force: true });
|