@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.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,36 +217,64 @@ 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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
252
|
+
const fullSelector = await handler(
|
|
253
|
+
{
|
|
254
|
+
method: "tools/call",
|
|
255
|
+
params: {
|
|
256
|
+
name: "projects.listProjects",
|
|
257
|
+
arguments: {
|
|
258
|
+
repoName: "F0/adl",
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{},
|
|
263
|
+
);
|
|
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");
|
|
241
269
|
} finally {
|
|
270
|
+
process.chdir(originalCwd);
|
|
242
271
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
243
272
|
}
|
|
244
273
|
});
|
|
245
274
|
|
|
246
|
-
it("auto-detects
|
|
275
|
+
it("auto-detects Git workspace from process.cwd() when repoName is omitted", async () => {
|
|
247
276
|
const tempDir = await fs.mkdtemp(
|
|
248
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
277
|
+
path.join(os.tmpdir(), "f0-mcp-server-autoworkspace-"),
|
|
249
278
|
);
|
|
250
279
|
const originalCwd = process.cwd();
|
|
251
280
|
|
|
@@ -257,6 +286,15 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
257
286
|
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
258
287
|
recursive: true,
|
|
259
288
|
});
|
|
289
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
290
|
+
await fs.writeFile(
|
|
291
|
+
path.join(tempDir, ".git", "config"),
|
|
292
|
+
[
|
|
293
|
+
'[remote "origin"]',
|
|
294
|
+
"\turl = https://example.com/F0/adl.git",
|
|
295
|
+
"",
|
|
296
|
+
].join("\n"),
|
|
297
|
+
);
|
|
260
298
|
|
|
261
299
|
process.chdir(tempDir);
|
|
262
300
|
|
|
@@ -285,16 +323,35 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
285
323
|
}
|
|
286
324
|
});
|
|
287
325
|
|
|
288
|
-
it("
|
|
289
|
-
const
|
|
290
|
-
|
|
326
|
+
it("ignores legacy processRoot/repoRoot payload fields and uses Git workspace", async () => {
|
|
327
|
+
const originalCwd = process.cwd();
|
|
328
|
+
const workspaceA = await fs.mkdtemp(
|
|
329
|
+
path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-a-"),
|
|
330
|
+
);
|
|
331
|
+
const workspaceB = await fs.mkdtemp(
|
|
332
|
+
path.join(os.tmpdir(), "f0-mcp-server-legacy-fields-b-"),
|
|
291
333
|
);
|
|
292
334
|
try {
|
|
293
|
-
await fs.mkdir(path.join(
|
|
294
|
-
await fs.mkdir(path.join(
|
|
335
|
+
await fs.mkdir(path.join(workspaceA, "api"), { recursive: true });
|
|
336
|
+
await fs.mkdir(path.join(workspaceA, "projects", "adl", "docs"), {
|
|
337
|
+
recursive: true,
|
|
338
|
+
});
|
|
339
|
+
await fs.mkdir(path.join(workspaceA, ".git"), { recursive: true });
|
|
340
|
+
await fs.writeFile(
|
|
341
|
+
path.join(workspaceA, ".git", "config"),
|
|
342
|
+
[
|
|
343
|
+
'[remote "origin"]',
|
|
344
|
+
"\turl = https://example.com/F0/adl.git",
|
|
345
|
+
"",
|
|
346
|
+
].join("\n"),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
await fs.mkdir(path.join(workspaceB, "api"), { recursive: true });
|
|
350
|
+
await fs.mkdir(path.join(workspaceB, "projects", "beta", "docs"), {
|
|
295
351
|
recursive: true,
|
|
296
352
|
});
|
|
297
353
|
|
|
354
|
+
process.chdir(workspaceA);
|
|
298
355
|
const instance = createExampleMcpServer();
|
|
299
356
|
const handler = getToolHandler(instance);
|
|
300
357
|
|
|
@@ -304,7 +361,8 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
304
361
|
params: {
|
|
305
362
|
name: "projects.listProjects",
|
|
306
363
|
arguments: {
|
|
307
|
-
processRoot:
|
|
364
|
+
processRoot: workspaceB,
|
|
365
|
+
repoRoot: workspaceB,
|
|
308
366
|
},
|
|
309
367
|
},
|
|
310
368
|
},
|
|
@@ -315,24 +373,35 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
315
373
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
316
374
|
expect(payload.ok).toBe(true);
|
|
317
375
|
expect(payload.result).toContain("adl");
|
|
376
|
+
expect(payload.result).not.toContain("beta");
|
|
318
377
|
} finally {
|
|
319
|
-
|
|
378
|
+
process.chdir(originalCwd);
|
|
379
|
+
await fs.rm(workspaceA, { recursive: true, force: true });
|
|
380
|
+
await fs.rm(workspaceB, { recursive: true, force: true });
|
|
320
381
|
}
|
|
321
382
|
});
|
|
322
383
|
|
|
323
|
-
it("
|
|
384
|
+
it("exposes mcp.workspace with Git workspace context", async () => {
|
|
385
|
+
const originalCwd = process.cwd();
|
|
324
386
|
const tempDir = await fs.mkdtemp(
|
|
325
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
387
|
+
path.join(os.tmpdir(), "f0-mcp-server-workspace-"),
|
|
326
388
|
);
|
|
327
389
|
try {
|
|
328
390
|
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
329
391
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
330
392
|
recursive: true,
|
|
331
393
|
});
|
|
332
|
-
await fs.mkdir(path.join(tempDir, "
|
|
333
|
-
|
|
334
|
-
|
|
394
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
395
|
+
await fs.writeFile(
|
|
396
|
+
path.join(tempDir, ".git", "config"),
|
|
397
|
+
[
|
|
398
|
+
'[remote "origin"]',
|
|
399
|
+
"\turl = https://example.com/F0/adl.git",
|
|
400
|
+
"",
|
|
401
|
+
].join("\n"),
|
|
402
|
+
);
|
|
335
403
|
|
|
404
|
+
process.chdir(tempDir);
|
|
336
405
|
const instance = createExampleMcpServer();
|
|
337
406
|
const handler = getToolHandler(instance);
|
|
338
407
|
|
|
@@ -340,10 +409,8 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
340
409
|
{
|
|
341
410
|
method: "tools/call",
|
|
342
411
|
params: {
|
|
343
|
-
name: "
|
|
344
|
-
arguments: {
|
|
345
|
-
repoRoot: path.join(tempDir, "projects", "adl"),
|
|
346
|
-
},
|
|
412
|
+
name: "mcp.workspace",
|
|
413
|
+
arguments: {},
|
|
347
414
|
},
|
|
348
415
|
},
|
|
349
416
|
{},
|
|
@@ -352,64 +419,114 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
352
419
|
expect(result.isError).toBe(false);
|
|
353
420
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
354
421
|
expect(payload.ok).toBe(true);
|
|
355
|
-
expect(payload.result).
|
|
356
|
-
expect(payload.result).
|
|
422
|
+
expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
|
|
423
|
+
expect(payload.result.processRoot).toBe(path.resolve(tempDir));
|
|
424
|
+
expect(payload.result.defaultRepoName).toBe("adl");
|
|
425
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
426
|
+
expect(payload.result.availableRepoNames).toContain("adl");
|
|
427
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
428
|
+
expect(payload.result.hasProjectsDir).toBe(true);
|
|
429
|
+
expect(Array.isArray(payload.result.projects)).toBe(true);
|
|
357
430
|
} finally {
|
|
431
|
+
process.chdir(originalCwd);
|
|
358
432
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
359
433
|
}
|
|
360
434
|
});
|
|
361
435
|
|
|
362
|
-
it("
|
|
436
|
+
it("auto-detects repo identity from .git/config when no repoName is provided", async () => {
|
|
437
|
+
const originalCwd = process.cwd();
|
|
363
438
|
const tempDir = await fs.mkdtemp(
|
|
364
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
439
|
+
path.join(os.tmpdir(), "f0-mcp-server-git-detect-"),
|
|
365
440
|
);
|
|
366
441
|
try {
|
|
367
|
-
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
442
|
+
await fs.mkdir(path.join(tempDir, "api", "mcp"), { recursive: true });
|
|
368
443
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
369
444
|
recursive: true,
|
|
370
445
|
});
|
|
371
|
-
await fs.mkdir(path.join(tempDir, "
|
|
372
|
-
|
|
373
|
-
|
|
446
|
+
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
447
|
+
await fs.writeFile(
|
|
448
|
+
path.join(tempDir, ".git", "config"),
|
|
449
|
+
[
|
|
450
|
+
'[remote "origin"]',
|
|
451
|
+
"\turl = https://example.com/F0/adl.git",
|
|
452
|
+
"",
|
|
453
|
+
].join("\n"),
|
|
454
|
+
);
|
|
374
455
|
|
|
375
|
-
|
|
456
|
+
process.chdir(path.join(tempDir, "api", "mcp"));
|
|
457
|
+
const instance = createExampleMcpServer();
|
|
376
458
|
const handler = getToolHandler(instance);
|
|
377
459
|
|
|
378
|
-
const
|
|
460
|
+
const workspace = await handler(
|
|
379
461
|
{
|
|
380
462
|
method: "tools/call",
|
|
381
463
|
params: {
|
|
382
|
-
name: "
|
|
464
|
+
name: "mcp.workspace",
|
|
383
465
|
arguments: {},
|
|
384
466
|
},
|
|
385
467
|
},
|
|
386
468
|
{},
|
|
387
469
|
);
|
|
388
470
|
|
|
389
|
-
expect(
|
|
390
|
-
const payload = JSON.parse(
|
|
471
|
+
expect(workspace.isError).toBe(false);
|
|
472
|
+
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
391
473
|
expect(payload.ok).toBe(true);
|
|
392
|
-
expect(payload.result).
|
|
393
|
-
expect(payload.result).
|
|
474
|
+
expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
|
|
475
|
+
expect(payload.result.processRoot).toBe(path.resolve(tempDir));
|
|
476
|
+
expect(payload.result.defaultRepoName).toBe("adl");
|
|
477
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
478
|
+
expect(payload.result.repoName).toBe("adl");
|
|
479
|
+
expect(payload.result.availableRepoNames).toContain("adl");
|
|
480
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
481
|
+
|
|
482
|
+
const list = await handler(
|
|
483
|
+
{
|
|
484
|
+
method: "tools/call",
|
|
485
|
+
params: {
|
|
486
|
+
name: "projects.listProjects",
|
|
487
|
+
arguments: {},
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{},
|
|
491
|
+
);
|
|
492
|
+
expect(list.isError).toBe(false);
|
|
493
|
+
const listPayload = JSON.parse(list.content?.[0]?.text ?? "{}");
|
|
494
|
+
expect(listPayload.ok).toBe(true);
|
|
495
|
+
expect(listPayload.result).toContain("adl");
|
|
394
496
|
} finally {
|
|
497
|
+
process.chdir(originalCwd);
|
|
395
498
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
396
499
|
}
|
|
397
500
|
});
|
|
398
501
|
|
|
399
|
-
it("
|
|
502
|
+
it("supports worktree-style .git file (gitdir: ...) when deriving repoName from config", async () => {
|
|
503
|
+
const originalCwd = process.cwd();
|
|
400
504
|
const tempDir = await fs.mkdtemp(
|
|
401
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
505
|
+
path.join(os.tmpdir(), "f0-mcp-server-gitfile-detect-"),
|
|
402
506
|
);
|
|
403
507
|
try {
|
|
404
|
-
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
508
|
+
await fs.mkdir(path.join(tempDir, "api", "mcp"), { recursive: true });
|
|
405
509
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
406
510
|
recursive: true,
|
|
407
511
|
});
|
|
408
512
|
|
|
409
|
-
const
|
|
513
|
+
const gitDir = path.join(tempDir, ".gitdir");
|
|
514
|
+
await fs.mkdir(gitDir, { recursive: true });
|
|
515
|
+
await fs.writeFile(
|
|
516
|
+
path.join(gitDir, "config"),
|
|
517
|
+
[
|
|
518
|
+
'[remote "origin"]',
|
|
519
|
+
"\turl = git@github.com:F0/adl.git",
|
|
520
|
+
"",
|
|
521
|
+
].join("\n"),
|
|
522
|
+
);
|
|
523
|
+
await fs.writeFile(path.join(tempDir, ".git"), "gitdir: .gitdir\n");
|
|
524
|
+
|
|
525
|
+
process.chdir(path.join(tempDir, "api", "mcp"));
|
|
526
|
+
const instance = createExampleMcpServer();
|
|
410
527
|
const handler = getToolHandler(instance);
|
|
411
528
|
|
|
412
|
-
const
|
|
529
|
+
const workspace = await handler(
|
|
413
530
|
{
|
|
414
531
|
method: "tools/call",
|
|
415
532
|
params: {
|
|
@@ -420,14 +537,14 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
420
537
|
{},
|
|
421
538
|
);
|
|
422
539
|
|
|
423
|
-
expect(
|
|
424
|
-
const payload = JSON.parse(
|
|
540
|
+
expect(workspace.isError).toBe(false);
|
|
541
|
+
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
425
542
|
expect(payload.ok).toBe(true);
|
|
426
|
-
expect(payload.result.
|
|
427
|
-
expect(payload.result.
|
|
428
|
-
expect(payload.result.
|
|
429
|
-
expect(Array.isArray(payload.result.projects)).toBe(true);
|
|
543
|
+
expect(payload.result.defaultRepoName).toBe("adl");
|
|
544
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
545
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
430
546
|
} finally {
|
|
547
|
+
process.chdir(originalCwd);
|
|
431
548
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
432
549
|
}
|
|
433
550
|
});
|