@foundation0/api 1.1.1 → 1.1.3
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/agents.ts +7 -6
- package/git.ts +2 -2
- package/mcp/AGENTS.md +130 -0
- package/mcp/cli.mjs +1 -1
- package/mcp/cli.ts +3 -3
- package/mcp/client.test.ts +13 -0
- package/mcp/client.ts +12 -4
- package/mcp/server.test.ts +464 -117
- package/mcp/server.ts +2497 -484
- package/package.json +2 -2
- package/projects.ts +1791 -99
package/mcp/server.test.ts
CHANGED
|
@@ -1,117 +1,464 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createExampleMcpServer } from "./server";
|
|
6
|
+
|
|
7
|
+
describe("createExampleMcpServer endpoint whitelist", () => {
|
|
8
|
+
it("exposes all root endpoints by default", () => {
|
|
9
|
+
const instance = createExampleMcpServer();
|
|
10
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
11
|
+
|
|
12
|
+
expect(names.some((name) => name.startsWith("agents."))).toBe(true);
|
|
13
|
+
expect(names.some((name) => name.startsWith("net."))).toBe(true);
|
|
14
|
+
expect(names.some((name) => name.startsWith("projects."))).toBe(true);
|
|
15
|
+
expect(names).toContain("projects.createGitIssue");
|
|
16
|
+
expect(names).not.toContain("projects.syncTasks");
|
|
17
|
+
expect(names).not.toContain("projects.clearIssues");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("exposes openai-safe alias net_curl for net.curl", () => {
|
|
21
|
+
const instance = createExampleMcpServer();
|
|
22
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
23
|
+
|
|
24
|
+
expect(names).toContain("net.curl");
|
|
25
|
+
expect(names).toContain("net_curl");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("filters tools to selected root endpoints", () => {
|
|
29
|
+
const instance = createExampleMcpServer({
|
|
30
|
+
allowedRootEndpoints: ["projects"],
|
|
31
|
+
});
|
|
32
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
33
|
+
|
|
34
|
+
expect(names.length).toBeGreaterThan(0);
|
|
35
|
+
expect(names.every((name) => name.startsWith("projects."))).toBe(true);
|
|
36
|
+
expect(names.some((name) => name.startsWith("agents."))).toBe(false);
|
|
37
|
+
expect(names.some((name) => name.startsWith("net."))).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("throws on unknown root endpoints", () => {
|
|
41
|
+
expect(() =>
|
|
42
|
+
createExampleMcpServer({
|
|
43
|
+
allowedRootEndpoints: ["unknown-root"],
|
|
44
|
+
}),
|
|
45
|
+
).toThrow("Unknown root endpoints");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("disables write-capable tools when disableWrite=true", () => {
|
|
49
|
+
const instance = createExampleMcpServer({
|
|
50
|
+
disableWrite: true,
|
|
51
|
+
});
|
|
52
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
53
|
+
|
|
54
|
+
expect(names).toContain("projects.readGitTask");
|
|
55
|
+
expect(names).toContain("projects.listProjects");
|
|
56
|
+
expect(names).toContain("agents.listAgents");
|
|
57
|
+
expect(names).toContain("net.curl");
|
|
58
|
+
expect(names).not.toContain("projects.createGitIssue");
|
|
59
|
+
expect(names).not.toContain("projects.writeGitTask");
|
|
60
|
+
expect(names).not.toContain("projects.syncTasks");
|
|
61
|
+
expect(names).not.toContain("projects.clearIssues");
|
|
62
|
+
expect(names).not.toContain("projects.generateSpec");
|
|
63
|
+
expect(names).not.toContain("projects.setActive");
|
|
64
|
+
expect(names).not.toContain("projects.main");
|
|
65
|
+
expect(names).not.toContain("agents.createAgent");
|
|
66
|
+
expect(names).not.toContain("agents.setActive");
|
|
67
|
+
expect(names).not.toContain("agents.setActiveLink");
|
|
68
|
+
expect(names).not.toContain("agents.runAgent");
|
|
69
|
+
expect(names).not.toContain("agents.main");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("applies disableWrite with root endpoint whitelist", () => {
|
|
73
|
+
const instance = createExampleMcpServer({
|
|
74
|
+
allowedRootEndpoints: ["projects"],
|
|
75
|
+
disableWrite: true,
|
|
76
|
+
});
|
|
77
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
78
|
+
|
|
79
|
+
expect(names.length).toBeGreaterThan(0);
|
|
80
|
+
expect(names.every((name) => name.startsWith("projects."))).toBe(true);
|
|
81
|
+
expect(names).toContain("projects.readGitTask");
|
|
82
|
+
expect(names).toContain("projects.fetchGitTasks");
|
|
83
|
+
expect(names).not.toContain("projects.createGitIssue");
|
|
84
|
+
expect(names).not.toContain("projects.writeGitTask");
|
|
85
|
+
expect(names).not.toContain("projects.syncTasks");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("re-enables issue write endpoints when enableIssues=true with disableWrite", () => {
|
|
89
|
+
const instance = createExampleMcpServer({
|
|
90
|
+
disableWrite: true,
|
|
91
|
+
enableIssues: true,
|
|
92
|
+
});
|
|
93
|
+
const names = instance.tools.map((tool) => tool.name);
|
|
94
|
+
|
|
95
|
+
expect(names).toContain("projects.fetchGitTasks");
|
|
96
|
+
expect(names).toContain("projects.readGitTask");
|
|
97
|
+
expect(names).toContain("projects.createGitIssue");
|
|
98
|
+
expect(names).toContain("projects.writeGitTask");
|
|
99
|
+
expect(names).not.toContain("projects.syncTasks");
|
|
100
|
+
expect(names).not.toContain("projects.clearIssues");
|
|
101
|
+
expect(names).not.toContain("projects.generateSpec");
|
|
102
|
+
expect(names).not.toContain("projects.setActive");
|
|
103
|
+
expect(names).not.toContain("agents.createAgent");
|
|
104
|
+
expect(names).not.toContain("agents.runAgent");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("exposes syncTasks and clearIssues only when admin=true", () => {
|
|
108
|
+
const defaultInstance = createExampleMcpServer();
|
|
109
|
+
const defaultNames = defaultInstance.tools.map((tool) => tool.name);
|
|
110
|
+
expect(defaultNames).not.toContain("projects.syncTasks");
|
|
111
|
+
expect(defaultNames).not.toContain("projects.clearIssues");
|
|
112
|
+
|
|
113
|
+
const adminInstance = createExampleMcpServer({ admin: true });
|
|
114
|
+
const adminNames = adminInstance.tools.map((tool) => tool.name);
|
|
115
|
+
expect(adminNames).toContain("projects.syncTasks");
|
|
116
|
+
expect(adminNames).toContain("projects.clearIssues");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("requires admin along with enableIssues for destructive issue endpoints under disableWrite", () => {
|
|
120
|
+
const nonAdmin = createExampleMcpServer({
|
|
121
|
+
disableWrite: true,
|
|
122
|
+
enableIssues: true,
|
|
123
|
+
});
|
|
124
|
+
const nonAdminNames = nonAdmin.tools.map((tool) => tool.name);
|
|
125
|
+
expect(nonAdminNames).not.toContain("projects.syncTasks");
|
|
126
|
+
expect(nonAdminNames).not.toContain("projects.clearIssues");
|
|
127
|
+
|
|
128
|
+
const admin = createExampleMcpServer({
|
|
129
|
+
disableWrite: true,
|
|
130
|
+
enableIssues: true,
|
|
131
|
+
admin: true,
|
|
132
|
+
});
|
|
133
|
+
const adminNames = admin.tools.map((tool) => tool.name);
|
|
134
|
+
expect(adminNames).toContain("projects.syncTasks");
|
|
135
|
+
expect(adminNames).toContain("projects.clearIssues");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("createExampleMcpServer request handling", () => {
|
|
140
|
+
const getToolHandler = (
|
|
141
|
+
instance: ReturnType<typeof createExampleMcpServer>,
|
|
142
|
+
) => {
|
|
143
|
+
const handler = (instance.server as any)._requestHandlers?.get(
|
|
144
|
+
"tools/call",
|
|
145
|
+
);
|
|
146
|
+
if (!handler) {
|
|
147
|
+
throw new Error("tools/call handler not registered");
|
|
148
|
+
}
|
|
149
|
+
return handler as (request: any, extra: any) => Promise<any>;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
it("passes structured args without stringifying", async () => {
|
|
153
|
+
const tempDir = await fs.mkdtemp(
|
|
154
|
+
path.join(os.tmpdir(), "f0-mcp-server-test-"),
|
|
155
|
+
);
|
|
156
|
+
try {
|
|
157
|
+
await fs.writeFile(
|
|
158
|
+
path.join(tempDir, "boot.v0.0.1.md"),
|
|
159
|
+
"# boot\n",
|
|
160
|
+
"utf8",
|
|
161
|
+
);
|
|
162
|
+
const instance = createExampleMcpServer();
|
|
163
|
+
const handler = getToolHandler(instance);
|
|
164
|
+
|
|
165
|
+
const result = await handler(
|
|
166
|
+
{
|
|
167
|
+
method: "tools/call",
|
|
168
|
+
params: {
|
|
169
|
+
name: "agents.resolveTargetFile",
|
|
170
|
+
arguments: {
|
|
171
|
+
args: [
|
|
172
|
+
tempDir,
|
|
173
|
+
{ parts: [], base: "boot", version: "v0.0.1", ext: "md" },
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
182
|
+
expect(payload.ok).toBe(true);
|
|
183
|
+
expect(payload.result).toBe("boot.v0.0.1.md");
|
|
184
|
+
} finally {
|
|
185
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('parses continueOnError from string "false" (fails fast)', async () => {
|
|
190
|
+
const instance = createExampleMcpServer();
|
|
191
|
+
const handler = getToolHandler(instance);
|
|
192
|
+
|
|
193
|
+
const result = await handler(
|
|
194
|
+
{
|
|
195
|
+
method: "tools/call",
|
|
196
|
+
params: {
|
|
197
|
+
name: "batch",
|
|
198
|
+
arguments: {
|
|
199
|
+
calls: [{ tool: "projects.usage" }, { tool: "unknown.tool" }],
|
|
200
|
+
continueOnError: "false",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(result.isError).toBe(true);
|
|
208
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
209
|
+
expect(payload.ok).toBe(false);
|
|
210
|
+
expect(payload.error?.message).toContain("Unknown tool");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('parses continueOnError from string "true" (returns per-call errors)', async () => {
|
|
214
|
+
const instance = createExampleMcpServer();
|
|
215
|
+
const handler = getToolHandler(instance);
|
|
216
|
+
|
|
217
|
+
const result = await handler(
|
|
218
|
+
{
|
|
219
|
+
method: "tools/call",
|
|
220
|
+
params: {
|
|
221
|
+
name: "batch",
|
|
222
|
+
arguments: {
|
|
223
|
+
calls: [{ tool: "projects.usage" }, { tool: "unknown.tool" }],
|
|
224
|
+
continueOnError: "true",
|
|
225
|
+
maxConcurrency: 2,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(result.isError).toBe(true);
|
|
233
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
234
|
+
expect(payload.ok).toBe(false);
|
|
235
|
+
expect(payload.error?.message).toContain("batch calls failed");
|
|
236
|
+
expect(Array.isArray(payload.error?.details?.results)).toBe(true);
|
|
237
|
+
expect(payload.error.details.results[1].isError).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("accepts unprefixed tool name when toolsPrefix is set", async () => {
|
|
241
|
+
const instance = createExampleMcpServer({ toolsPrefix: "api" });
|
|
242
|
+
const handler = getToolHandler(instance);
|
|
243
|
+
|
|
244
|
+
const result = await handler(
|
|
245
|
+
{
|
|
246
|
+
method: "tools/call",
|
|
247
|
+
params: {
|
|
248
|
+
name: "projects.usage",
|
|
249
|
+
arguments: {},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(result.isError).toBe(false);
|
|
256
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
257
|
+
expect(payload.ok).toBe(true);
|
|
258
|
+
expect(typeof payload.result).toBe("string");
|
|
259
|
+
expect(payload.result).toContain("Usage");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("exposes mcp.describeTool for tool discovery", async () => {
|
|
263
|
+
const instance = createExampleMcpServer({ toolsPrefix: "api" });
|
|
264
|
+
const handler = getToolHandler(instance);
|
|
265
|
+
|
|
266
|
+
const result = await handler(
|
|
267
|
+
{
|
|
268
|
+
method: "tools/call",
|
|
269
|
+
params: {
|
|
270
|
+
name: "mcp.describeTool",
|
|
271
|
+
arguments: {
|
|
272
|
+
args: ["projects.usage"],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(result.isError).toBe(false);
|
|
280
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
281
|
+
expect(payload.ok).toBe(true);
|
|
282
|
+
expect(payload.result.name).toBe("api.projects.usage");
|
|
283
|
+
expect(payload.result.unprefixedName).toBe("projects.usage");
|
|
284
|
+
expect(payload.result.access).toBe("read");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("exposes net.curl under toolsPrefix", async () => {
|
|
288
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
289
|
+
const handler = getToolHandler(instance);
|
|
290
|
+
|
|
291
|
+
const result = await handler(
|
|
292
|
+
{
|
|
293
|
+
method: "tools/call",
|
|
294
|
+
params: {
|
|
295
|
+
name: "mcp.describeTool",
|
|
296
|
+
arguments: {
|
|
297
|
+
args: ["net.curl"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{},
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(result.isError).toBe(false);
|
|
305
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
306
|
+
expect(payload.ok).toBe(true);
|
|
307
|
+
expect(payload.result.name).toBe("f0.net.curl");
|
|
308
|
+
expect(payload.result.unprefixedName).toBe("net.curl");
|
|
309
|
+
expect(payload.result.access).toBe("read");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("exposes net_curl under toolsPrefix", async () => {
|
|
313
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
314
|
+
const handler = getToolHandler(instance);
|
|
315
|
+
|
|
316
|
+
const result = await handler(
|
|
317
|
+
{
|
|
318
|
+
method: "tools/call",
|
|
319
|
+
params: {
|
|
320
|
+
name: "mcp.describeTool",
|
|
321
|
+
arguments: {
|
|
322
|
+
args: ["net_curl"],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
{},
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(result.isError).toBe(false);
|
|
330
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
331
|
+
expect(payload.ok).toBe(true);
|
|
332
|
+
expect(payload.result.name).toBe("f0.net_curl");
|
|
333
|
+
expect(payload.result.unprefixedName).toBe("net_curl");
|
|
334
|
+
expect(payload.result.access).toBe("read");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("can execute net.curl with curl-style args", async () => {
|
|
338
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
339
|
+
const handler = getToolHandler(instance);
|
|
340
|
+
|
|
341
|
+
const server = Bun.serve({
|
|
342
|
+
port: 0,
|
|
343
|
+
fetch: () => new Response("ok", { headers: { "x-test": "1" } }),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const result = await handler(
|
|
347
|
+
{
|
|
348
|
+
method: "tools/call",
|
|
349
|
+
params: {
|
|
350
|
+
name: "f0.net.curl",
|
|
351
|
+
arguments: {
|
|
352
|
+
args: [`http://127.0.0.1:${server.port}/hello`],
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
server.stop(true);
|
|
360
|
+
|
|
361
|
+
expect(result.isError).toBe(false);
|
|
362
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
363
|
+
expect(payload.ok).toBe(true);
|
|
364
|
+
expect(payload.result.exitCode).toBe(0);
|
|
365
|
+
expect(String(payload.result.stdout ?? "")).toBe("ok");
|
|
366
|
+
expect(payload.result.results?.[0]?.httpCode).toBe(200);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("can execute net_curl with curl-style args", async () => {
|
|
370
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
371
|
+
const handler = getToolHandler(instance);
|
|
372
|
+
|
|
373
|
+
const server = Bun.serve({
|
|
374
|
+
port: 0,
|
|
375
|
+
fetch: () => new Response("ok", { headers: { "x-test": "1" } }),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const result = await handler(
|
|
379
|
+
{
|
|
380
|
+
method: "tools/call",
|
|
381
|
+
params: {
|
|
382
|
+
name: "f0.net_curl",
|
|
383
|
+
arguments: {
|
|
384
|
+
args: [`http://127.0.0.1:${server.port}/hello`],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
{},
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
server.stop(true);
|
|
392
|
+
|
|
393
|
+
expect(result.isError).toBe(false);
|
|
394
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
395
|
+
expect(payload.ok).toBe(true);
|
|
396
|
+
expect(payload.result.exitCode).toBe(0);
|
|
397
|
+
expect(String(payload.result.stdout ?? "")).toBe("ok");
|
|
398
|
+
expect(payload.result.results?.[0]?.httpCode).toBe(200);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("can execute net.curl in structured mode (url/method)", async () => {
|
|
402
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
403
|
+
const handler = getToolHandler(instance);
|
|
404
|
+
|
|
405
|
+
const server = Bun.serve({
|
|
406
|
+
port: 0,
|
|
407
|
+
fetch: (req) => Response.json({ ok: true, method: req.method }),
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const result = await handler(
|
|
411
|
+
{
|
|
412
|
+
method: "tools/call",
|
|
413
|
+
params: {
|
|
414
|
+
name: "f0.net.curl",
|
|
415
|
+
arguments: {
|
|
416
|
+
url: `http://127.0.0.1:${server.port}/json`,
|
|
417
|
+
method: "GET",
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{},
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
server.stop(true);
|
|
425
|
+
|
|
426
|
+
expect(result.isError).toBe(false);
|
|
427
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
428
|
+
expect(payload.ok).toBe(true);
|
|
429
|
+
expect(payload.result.exitCode).toBe(0);
|
|
430
|
+
expect(JSON.parse(payload.result.stdout).method).toBe("GET");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("can execute net_curl in structured mode (url/method)", async () => {
|
|
434
|
+
const instance = createExampleMcpServer({ toolsPrefix: "f0" });
|
|
435
|
+
const handler = getToolHandler(instance);
|
|
436
|
+
|
|
437
|
+
const server = Bun.serve({
|
|
438
|
+
port: 0,
|
|
439
|
+
fetch: (req) => Response.json({ ok: true, method: req.method }),
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const result = await handler(
|
|
443
|
+
{
|
|
444
|
+
method: "tools/call",
|
|
445
|
+
params: {
|
|
446
|
+
name: "f0.net_curl",
|
|
447
|
+
arguments: {
|
|
448
|
+
url: `http://127.0.0.1:${server.port}/json`,
|
|
449
|
+
method: "GET",
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
{},
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
server.stop(true);
|
|
457
|
+
|
|
458
|
+
expect(result.isError).toBe(false);
|
|
459
|
+
const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
|
|
460
|
+
expect(payload.ok).toBe(true);
|
|
461
|
+
expect(payload.result.exitCode).toBe(0);
|
|
462
|
+
expect(JSON.parse(payload.result.stdout).method).toBe("GET");
|
|
463
|
+
});
|
|
464
|
+
});
|