@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.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,114 +217,67 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
216
217
|
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
217
218
|
recursive: true,
|
|
218
219
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
name: "projects.listProjects",
|
|
228
|
-
arguments: {
|
|
229
|
-
repoName: "test",
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
{},
|
|
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"),
|
|
234
228
|
);
|
|
235
229
|
|
|
236
|
-
expect(result.isError).toBe(false);
|
|
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
230
|
process.chdir(tempDir);
|
|
262
|
-
|
|
263
231
|
const instance = createExampleMcpServer();
|
|
264
232
|
const handler = getToolHandler(instance);
|
|
265
233
|
|
|
266
|
-
const
|
|
234
|
+
const shortSelector = await handler(
|
|
267
235
|
{
|
|
268
236
|
method: "tools/call",
|
|
269
237
|
params: {
|
|
270
238
|
name: "projects.listProjects",
|
|
271
|
-
arguments: {
|
|
239
|
+
arguments: {
|
|
240
|
+
repoName: "adl",
|
|
241
|
+
},
|
|
272
242
|
},
|
|
273
243
|
},
|
|
274
244
|
{},
|
|
275
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");
|
|
276
251
|
|
|
277
|
-
|
|
278
|
-
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
279
|
-
expect(payload.ok).toBe(true);
|
|
280
|
-
expect(payload.result).toContain("adl");
|
|
281
|
-
expect(payload.result).toContain("beta");
|
|
282
|
-
} finally {
|
|
283
|
-
process.chdir(originalCwd);
|
|
284
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("still accepts legacy processRoot for backwards compatibility", async () => {
|
|
289
|
-
const tempDir = await fs.mkdtemp(
|
|
290
|
-
path.join(os.tmpdir(), "f0-mcp-server-processroot-"),
|
|
291
|
-
);
|
|
292
|
-
try {
|
|
293
|
-
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
294
|
-
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
295
|
-
recursive: true,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
const instance = createExampleMcpServer();
|
|
299
|
-
const handler = getToolHandler(instance);
|
|
300
|
-
|
|
301
|
-
const result = await handler(
|
|
252
|
+
const fullSelector = await handler(
|
|
302
253
|
{
|
|
303
254
|
method: "tools/call",
|
|
304
255
|
params: {
|
|
305
256
|
name: "projects.listProjects",
|
|
306
257
|
arguments: {
|
|
307
|
-
|
|
258
|
+
repoName: "F0/adl",
|
|
308
259
|
},
|
|
309
260
|
},
|
|
310
261
|
},
|
|
311
262
|
{},
|
|
312
263
|
);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
expect(
|
|
317
|
-
expect(
|
|
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");
|
|
318
269
|
} finally {
|
|
270
|
+
process.chdir(originalCwd);
|
|
319
271
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
320
272
|
}
|
|
321
273
|
});
|
|
322
274
|
|
|
323
|
-
it("
|
|
275
|
+
it("auto-detects Git workspace from process.cwd() when repoName is omitted", async () => {
|
|
324
276
|
const tempDir = await fs.mkdtemp(
|
|
325
|
-
path.join(os.tmpdir(), "f0-mcp-server-
|
|
277
|
+
path.join(os.tmpdir(), "f0-mcp-server-autoworkspace-"),
|
|
326
278
|
);
|
|
279
|
+
const originalCwd = process.cwd();
|
|
280
|
+
|
|
327
281
|
try {
|
|
328
282
|
await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
|
|
329
283
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
@@ -332,6 +286,17 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
332
286
|
await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
|
|
333
287
|
recursive: true,
|
|
334
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
|
+
);
|
|
298
|
+
|
|
299
|
+
process.chdir(tempDir);
|
|
335
300
|
|
|
336
301
|
const instance = createExampleMcpServer();
|
|
337
302
|
const handler = getToolHandler(instance);
|
|
@@ -341,9 +306,7 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
341
306
|
method: "tools/call",
|
|
342
307
|
params: {
|
|
343
308
|
name: "projects.listProjects",
|
|
344
|
-
arguments: {
|
|
345
|
-
repoRoot: path.join(tempDir, "projects", "adl"),
|
|
346
|
-
},
|
|
309
|
+
arguments: {},
|
|
347
310
|
},
|
|
348
311
|
},
|
|
349
312
|
{},
|
|
@@ -355,24 +318,41 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
355
318
|
expect(payload.result).toContain("adl");
|
|
356
319
|
expect(payload.result).toContain("beta");
|
|
357
320
|
} finally {
|
|
321
|
+
process.chdir(originalCwd);
|
|
358
322
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
359
323
|
}
|
|
360
324
|
});
|
|
361
325
|
|
|
362
|
-
it("
|
|
363
|
-
const
|
|
364
|
-
|
|
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-"),
|
|
365
333
|
);
|
|
366
334
|
try {
|
|
367
|
-
await fs.mkdir(path.join(
|
|
368
|
-
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"), {
|
|
369
337
|
recursive: true,
|
|
370
338
|
});
|
|
371
|
-
await fs.mkdir(path.join(
|
|
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"), {
|
|
372
351
|
recursive: true,
|
|
373
352
|
});
|
|
374
353
|
|
|
375
|
-
|
|
354
|
+
process.chdir(workspaceA);
|
|
355
|
+
const instance = createExampleMcpServer();
|
|
376
356
|
const handler = getToolHandler(instance);
|
|
377
357
|
|
|
378
358
|
const result = await handler(
|
|
@@ -380,7 +360,10 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
380
360
|
method: "tools/call",
|
|
381
361
|
params: {
|
|
382
362
|
name: "projects.listProjects",
|
|
383
|
-
arguments: {
|
|
363
|
+
arguments: {
|
|
364
|
+
processRoot: workspaceB,
|
|
365
|
+
repoRoot: workspaceB,
|
|
366
|
+
},
|
|
384
367
|
},
|
|
385
368
|
},
|
|
386
369
|
{},
|
|
@@ -390,13 +373,16 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
390
373
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
391
374
|
expect(payload.ok).toBe(true);
|
|
392
375
|
expect(payload.result).toContain("adl");
|
|
393
|
-
expect(payload.result).toContain("beta");
|
|
376
|
+
expect(payload.result).not.toContain("beta");
|
|
394
377
|
} finally {
|
|
395
|
-
|
|
378
|
+
process.chdir(originalCwd);
|
|
379
|
+
await fs.rm(workspaceA, { recursive: true, force: true });
|
|
380
|
+
await fs.rm(workspaceB, { recursive: true, force: true });
|
|
396
381
|
}
|
|
397
382
|
});
|
|
398
383
|
|
|
399
|
-
it("exposes mcp.workspace
|
|
384
|
+
it("exposes mcp.workspace with Git workspace context", async () => {
|
|
385
|
+
const originalCwd = process.cwd();
|
|
400
386
|
const tempDir = await fs.mkdtemp(
|
|
401
387
|
path.join(os.tmpdir(), "f0-mcp-server-workspace-"),
|
|
402
388
|
);
|
|
@@ -405,8 +391,18 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
405
391
|
await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
|
|
406
392
|
recursive: true,
|
|
407
393
|
});
|
|
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
|
+
);
|
|
408
403
|
|
|
409
|
-
|
|
404
|
+
process.chdir(tempDir);
|
|
405
|
+
const instance = createExampleMcpServer();
|
|
410
406
|
const handler = getToolHandler(instance);
|
|
411
407
|
|
|
412
408
|
const result = await handler(
|
|
@@ -423,16 +419,21 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
423
419
|
expect(result.isError).toBe(false);
|
|
424
420
|
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
425
421
|
expect(payload.ok).toBe(true);
|
|
426
|
-
expect(payload.result.
|
|
427
|
-
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
428
|
expect(payload.result.hasProjectsDir).toBe(true);
|
|
429
429
|
expect(Array.isArray(payload.result.projects)).toBe(true);
|
|
430
430
|
} finally {
|
|
431
|
+
process.chdir(originalCwd);
|
|
431
432
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
432
433
|
}
|
|
433
434
|
});
|
|
434
435
|
|
|
435
|
-
it("auto-detects repo
|
|
436
|
+
it("auto-detects repo identity from .git/config when no repoName is provided", async () => {
|
|
436
437
|
const originalCwd = process.cwd();
|
|
437
438
|
const tempDir = await fs.mkdtemp(
|
|
438
439
|
path.join(os.tmpdir(), "f0-mcp-server-git-detect-"),
|
|
@@ -470,11 +471,13 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
470
471
|
expect(workspace.isError).toBe(false);
|
|
471
472
|
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
472
473
|
expect(payload.ok).toBe(true);
|
|
473
|
-
expect(payload.result.
|
|
474
|
-
expect(payload.result.
|
|
474
|
+
expect(payload.result.workspaceRoot).toBe(path.resolve(tempDir));
|
|
475
|
+
expect(payload.result.processRoot).toBe(path.resolve(tempDir));
|
|
475
476
|
expect(payload.result.defaultRepoName).toBe("adl");
|
|
477
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
476
478
|
expect(payload.result.repoName).toBe("adl");
|
|
477
479
|
expect(payload.result.availableRepoNames).toContain("adl");
|
|
480
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
478
481
|
|
|
479
482
|
const list = await handler(
|
|
480
483
|
{
|
|
@@ -538,6 +541,8 @@ describe("createExampleMcpServer request handling", () => {
|
|
|
538
541
|
const payload = JSON.parse(workspace.content?.[0]?.text ?? "{}");
|
|
539
542
|
expect(payload.ok).toBe(true);
|
|
540
543
|
expect(payload.result.defaultRepoName).toBe("adl");
|
|
544
|
+
expect(payload.result.defaultRepoFullName).toBe("F0/adl");
|
|
545
|
+
expect(payload.result.availableRepoNames).toContain("F0/adl");
|
|
541
546
|
} finally {
|
|
542
547
|
process.chdir(originalCwd);
|
|
543
548
|
await fs.rm(tempDir, { recursive: true, force: true });
|