@slashfi/agents-sdk 0.62.0 → 0.63.0

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.
Files changed (47) hide show
  1. package/dist/cjs/index.js +1 -29
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/index.d.ts +1 -10
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +0 -18
  6. package/dist/index.js.map +1 -1
  7. package/dist/types.d.ts +14 -0
  8. package/dist/types.d.ts.map +1 -1
  9. package/package.json +1 -1
  10. package/src/index.ts +1 -44
  11. package/src/types.ts +8 -0
  12. package/dist/cjs/client.js +0 -193
  13. package/dist/cjs/client.js.map +0 -1
  14. package/dist/cjs/codegen.js +0 -1071
  15. package/dist/cjs/codegen.js.map +0 -1
  16. package/dist/cjs/introspect.js +0 -136
  17. package/dist/cjs/introspect.js.map +0 -1
  18. package/dist/cjs/jsonc.js +0 -74
  19. package/dist/cjs/jsonc.js.map +0 -1
  20. package/dist/cjs/pack.js +0 -256
  21. package/dist/cjs/pack.js.map +0 -1
  22. package/dist/client.d.ts +0 -49
  23. package/dist/client.d.ts.map +0 -1
  24. package/dist/client.js +0 -190
  25. package/dist/client.js.map +0 -1
  26. package/dist/codegen.d.ts +0 -163
  27. package/dist/codegen.d.ts.map +0 -1
  28. package/dist/codegen.js +0 -1059
  29. package/dist/codegen.js.map +0 -1
  30. package/dist/introspect.d.ts +0 -16
  31. package/dist/introspect.d.ts.map +0 -1
  32. package/dist/introspect.js +0 -133
  33. package/dist/introspect.js.map +0 -1
  34. package/dist/jsonc.d.ts +0 -15
  35. package/dist/jsonc.d.ts.map +0 -1
  36. package/dist/jsonc.js +0 -70
  37. package/dist/jsonc.js.map +0 -1
  38. package/dist/pack.d.ts +0 -59
  39. package/dist/pack.d.ts.map +0 -1
  40. package/dist/pack.js +0 -252
  41. package/dist/pack.js.map +0 -1
  42. package/src/client.ts +0 -273
  43. package/src/codegen.test.ts +0 -537
  44. package/src/codegen.ts +0 -1423
  45. package/src/introspect.ts +0 -171
  46. package/src/jsonc.ts +0 -83
  47. package/src/pack.ts +0 -394
@@ -1,537 +0,0 @@
1
- /**
2
- * Tests for MCP Codegen
3
- *
4
- * Tests the codegen pipeline using a mock MCP server that responds
5
- * via HTTP JSON-RPC.
6
- */
7
-
8
- import { describe, test, expect, beforeAll, afterAll } from "bun:test";
9
- import { readFileSync, existsSync, rmSync } from "node:fs";
10
- import { join } from "node:path";
11
- import { codegen, useAgent, listAgentTools } from "./codegen.js";
12
- import type { McpToolDefinition } from "./codegen.js";
13
-
14
- // ============================================
15
- // Mock MCP Server
16
- // ============================================
17
-
18
- const MOCK_TOOLS: McpToolDefinition[] = [
19
- {
20
- name: "search_pages",
21
- description: "Search for pages by query",
22
- inputSchema: {
23
- type: "object",
24
- properties: {
25
- query: { type: "string", description: "Search query" },
26
- limit: { type: "number", description: "Max results" },
27
- },
28
- required: ["query"],
29
- },
30
- },
31
- {
32
- name: "create_page",
33
- description: "Create a new page",
34
- inputSchema: {
35
- type: "object",
36
- properties: {
37
- title: { type: "string", description: "Page title" },
38
- content: { type: "string", description: "Page content" },
39
- parent_id: { type: "string", description: "Parent page ID" },
40
- },
41
- required: ["title"],
42
- },
43
- },
44
- {
45
- name: "get_page",
46
- description: "Get a page by ID",
47
- inputSchema: {
48
- type: "object",
49
- properties: {
50
- page_id: { type: "string", description: "Page ID" },
51
- },
52
- required: ["page_id"],
53
- },
54
- },
55
- ];
56
-
57
- let mockServer: ReturnType<typeof Bun.serve>;
58
- let mockPort: number;
59
-
60
- beforeAll(() => {
61
- mockServer = Bun.serve({
62
- port: 0,
63
- fetch(req) {
64
- return (async () => {
65
- // Handle GET requests (well-known endpoints, etc.)
66
- if (req.method === "GET") {
67
- return new Response("Not Found", { status: 404 });
68
- }
69
- const body = (await req.json()) as {
70
- id?: number;
71
- method: string;
72
- params?: Record<string, unknown>;
73
- };
74
-
75
- // Handle notifications (no id) — return 202 Accepted
76
- if (body.id === undefined) {
77
- return new Response(null, { status: 202 });
78
- }
79
-
80
- let result: unknown;
81
-
82
- switch (body.method) {
83
- case "initialize":
84
- result = {
85
- protocolVersion: "2025-03-26",
86
- serverInfo: {
87
- name: "mock-notion",
88
- version: "1.0.0",
89
- },
90
- capabilities: { tools: {} },
91
- };
92
- break;
93
-
94
- case "notifications/initialized":
95
- result = {};
96
- break;
97
-
98
- case "tools/list":
99
- result = { tools: MOCK_TOOLS };
100
- break;
101
-
102
- case "tools/call": {
103
- const params = body.params as {
104
- name: string;
105
- arguments: Record<string, unknown>;
106
- };
107
- result = {
108
- content: [
109
- {
110
- type: "text",
111
- text: JSON.stringify({
112
- tool: params.name,
113
- args: params.arguments,
114
- mock: true,
115
- }),
116
- },
117
- ],
118
- };
119
- break;
120
- }
121
-
122
- default:
123
- return Response.json(
124
- {
125
- jsonrpc: "2.0",
126
- id: body.id,
127
- error: { code: -32601, message: `Unknown method: ${body.method}` },
128
- },
129
- { status: 200 },
130
- );
131
- }
132
-
133
- return Response.json({
134
- jsonrpc: "2.0",
135
- id: body.id,
136
- result,
137
- });
138
- })();
139
- },
140
- });
141
- mockPort = mockServer.port;
142
- });
143
-
144
- afterAll(() => {
145
- mockServer.stop();
146
- });
147
-
148
- // ============================================
149
- // Tests
150
- // ============================================
151
-
152
- const TEST_OUT_DIR = "/tmp/agents-sdk-codegen-test";
153
-
154
- describe("codegen", () => {
155
- beforeAll(async () => {
156
- // Clean up previous test output
157
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
158
-
159
- await codegen({
160
- server: `http://localhost:${mockPort}`,
161
- outDir: TEST_OUT_DIR,
162
- name: "notion",
163
- agentPath: "@notion",
164
- });
165
- });
166
-
167
- afterAll(() => {
168
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
169
- });
170
-
171
- test("generates tool files for each MCP tool", () => {
172
- expect(existsSync(join(TEST_OUT_DIR, "search-pages.tool.md"))).toBe(true);
173
- expect(existsSync(join(TEST_OUT_DIR, "create-page.tool.md"))).toBe(true);
174
- expect(existsSync(join(TEST_OUT_DIR, "get-page.tool.md"))).toBe(true);
175
- });
176
-
177
- test("generates agent.config.ts", () => {
178
- const content = readFileSync(
179
- join(TEST_OUT_DIR, "agent.config.ts"),
180
- "utf-8",
181
- );
182
- expect(content).toContain("defineAgent");
183
- expect(content).toContain("'@notion'");
184
- });
185
-
186
- test("generates entrypoint.md", () => {
187
- const content = readFileSync(
188
- join(TEST_OUT_DIR, "entrypoint.md"),
189
- "utf-8",
190
- );
191
- expect(content).toContain("# mock-notion");
192
- expect(content).toContain("search_pages");
193
- expect(content).toContain("create_page");
194
- expect(content).toContain("get_page");
195
- });
196
-
197
- test("generates index.ts with agent export", () => {
198
- const content = readFileSync(join(TEST_OUT_DIR, "index.ts"), "utf-8");
199
- expect(content).toContain("agent.config");
200
- });
201
-
202
- test("generates cli.ts", () => {
203
- const content = readFileSync(join(TEST_OUT_DIR, "cli.ts"), "utf-8");
204
- expect(content).toContain("search_pages");
205
- expect(content).toContain("--list");
206
- expect(content).toContain("--help");
207
- });
208
-
209
- test("generates .codegen-manifest.json", () => {
210
- const content = readFileSync(
211
- join(TEST_OUT_DIR, ".codegen-manifest.json"),
212
- "utf-8",
213
- );
214
- const manifest = JSON.parse(content);
215
- expect(manifest.agentPath).toBe("@notion");
216
- expect(manifest.serverInfo.name).toBe("mock-notion");
217
- expect(manifest.tools).toHaveLength(3);
218
- expect(manifest.tools[0].name).toBe("search_pages");
219
- });
220
-
221
- test("tool files contain markdown with parameters", () => {
222
- const content = readFileSync(
223
- join(TEST_OUT_DIR, "search-pages.tool.md"),
224
- "utf-8",
225
- );
226
- expect(content).toContain("# search_pages");
227
- expect(content).toContain("Search for pages by query");
228
- expect(content).toContain("| query");
229
- expect(content).toContain("| limit");
230
- expect(content).toContain("## Parameters");
231
- });
232
-
233
- test("tool files include type and required info", () => {
234
- const content = readFileSync(
235
- join(TEST_OUT_DIR, "search-pages.tool.md"),
236
- "utf-8",
237
- );
238
- expect(content).toContain("`string`");
239
- expect(content).toContain("✓"); // required marker for query
240
- });
241
-
242
- test("agent.config.ts has defineAgent", () => {
243
- const content = readFileSync(
244
- join(TEST_OUT_DIR, "agent.config.ts"),
245
- "utf-8",
246
- );
247
- expect(content).toContain("defineAgent");
248
- expect(content).toContain("entrypoint");
249
- });
250
- });
251
-
252
- describe("listAgentTools", () => {
253
- beforeAll(async () => {
254
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
255
- await codegen({
256
- server: `http://localhost:${mockPort}`,
257
- outDir: TEST_OUT_DIR,
258
- name: "notion",
259
- });
260
- });
261
-
262
- afterAll(() => {
263
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
264
- });
265
-
266
- test("lists tools from manifest", () => {
267
- const tools = listAgentTools(TEST_OUT_DIR);
268
- expect(tools).toHaveLength(3);
269
- expect(tools[0].name).toBe("search_pages");
270
- expect(tools[1].name).toBe("create_page");
271
- expect(tools[2].name).toBe("get_page");
272
- });
273
- });
274
-
275
- describe("codegen JSON Schema support", () => {
276
- const SCHEMA_OUT_DIR = "/tmp/agents-sdk-codegen-schema-test";
277
- let schemaServer: ReturnType<typeof Bun.serve>;
278
- let schemaPort: number;
279
-
280
- const COMPLEX_TOOLS: McpToolDefinition[] = [
281
- {
282
- name: "complex_tool",
283
- description: "Tool with advanced JSON Schema features",
284
- inputSchema: {
285
- type: "object",
286
- properties: {
287
- status: { enum: ["open", "closed", "pending"] },
288
- value: { oneOf: [{ type: "string" }, { type: "number" }] },
289
- merged: { allOf: [{ type: "object", properties: { a: { type: "string" } } }, { type: "object", properties: { b: { type: "number" } } }] },
290
- flexible: { anyOf: [{ type: "string" }, { type: "null" }] },
291
- literal: { const: "fixed_value" },
292
- excluded: { not: { type: "string" } },
293
- email: { type: "string", format: "email" },
294
- age: { type: "integer" },
295
- nullable: { type: ["string", "null"] },
296
- tags: { type: "array", items: { type: "string" } },
297
- metadata: { type: "object", additionalProperties: { type: "string" } },
298
- coords: { type: "array", prefixItems: [{ type: "number" }, { type: "number" }] },
299
- },
300
- required: ["status", "value"],
301
- },
302
- },
303
- ];
304
-
305
- beforeAll(async () => {
306
- rmSync(SCHEMA_OUT_DIR, { recursive: true, force: true });
307
-
308
- schemaServer = Bun.serve({
309
- port: 0,
310
- fetch(req) {
311
- return (async () => {
312
- if (req.method === "GET") {
313
- return new Response("Not Found", { status: 404 });
314
- }
315
- const body = (await req.json()) as { id?: number; method: string; params?: Record<string, unknown> };
316
- if (body.id === undefined) return new Response(null, { status: 202 });
317
-
318
- let result: unknown;
319
- switch (body.method) {
320
- case "initialize":
321
- result = { protocolVersion: "2025-03-26", serverInfo: { name: "schema-test", version: "1.0.0" }, capabilities: { tools: {} } };
322
- break;
323
- case "tools/list":
324
- result = { tools: COMPLEX_TOOLS };
325
- break;
326
- default:
327
- return Response.json({ jsonrpc: "2.0", id: body.id, error: { code: -32601, message: `Unknown: ${body.method}` } });
328
- }
329
- return Response.json({ jsonrpc: "2.0", id: body.id, result });
330
- })();
331
- },
332
- });
333
- schemaPort = schemaServer.port;
334
-
335
- await codegen({
336
- server: `http://localhost:${schemaPort}`,
337
- outDir: SCHEMA_OUT_DIR,
338
- name: "schema-test",
339
- });
340
- });
341
-
342
- afterAll(() => {
343
- schemaServer.stop();
344
- rmSync(SCHEMA_OUT_DIR, { recursive: true, force: true });
345
- });
346
-
347
- test("renders enum as union", () => {
348
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
349
- expect(content).toContain('"open" | "closed" | "pending"');
350
- });
351
-
352
- test("renders oneOf as union", () => {
353
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
354
- expect(content).toContain("string | number");
355
- });
356
-
357
- test("renders allOf as intersection", () => {
358
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
359
- expect(content).toContain("&");
360
- });
361
-
362
- test("renders const as literal", () => {
363
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
364
- expect(content).toContain('"fixed_value"');
365
- });
366
-
367
- test("renders not", () => {
368
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
369
- expect(content).toContain("Exclude");
370
- });
371
-
372
- test("renders format hint", () => {
373
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
374
- expect(content).toContain("email");
375
- });
376
-
377
- test("renders integer as number", () => {
378
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
379
- expect(content).toContain("integer");
380
- });
381
-
382
- test("renders nullable type array", () => {
383
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
384
- expect(content).toContain("string | null");
385
- });
386
-
387
- test("renders additionalProperties as Record", () => {
388
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
389
- expect(content).toContain("Record<string, string>");
390
- });
391
-
392
- test("renders tuple arrays", () => {
393
- const content = readFileSync(join(SCHEMA_OUT_DIR, "complex-tool.tool.md"), "utf-8");
394
- expect(content).toContain("[number, number]");
395
- });
396
- });
397
-
398
- describe("codegen pagination", () => {
399
- const PAGINATED_OUT_DIR = "/tmp/agents-sdk-codegen-paginated-test";
400
- let paginatedServer: ReturnType<typeof Bun.serve>;
401
- let paginatedPort: number;
402
-
403
- beforeAll(async () => {
404
- rmSync(PAGINATED_OUT_DIR, { recursive: true, force: true });
405
-
406
- // Mock server that paginates tools across 2 pages
407
- paginatedServer = Bun.serve({
408
- port: 0,
409
- fetch(req) {
410
- return (async () => {
411
- if (req.method === "GET") {
412
- return new Response("Not Found", { status: 404 });
413
- }
414
- const body = (await req.json()) as {
415
- id?: number;
416
- method: string;
417
- params?: Record<string, unknown>;
418
- };
419
-
420
- if (body.id === undefined) {
421
- return new Response(null, { status: 202 });
422
- }
423
-
424
- let result: unknown;
425
-
426
- switch (body.method) {
427
- case "initialize":
428
- result = {
429
- protocolVersion: "2025-03-26",
430
- serverInfo: { name: "paginated-server", version: "1.0.0" },
431
- capabilities: { tools: {} },
432
- };
433
- break;
434
-
435
- case "tools/list": {
436
- const cursor = (body.params as { cursor?: string })?.cursor;
437
- if (!cursor) {
438
- // Page 1: return first 2 tools + nextCursor
439
- result = {
440
- tools: [MOCK_TOOLS[0], MOCK_TOOLS[1]],
441
- nextCursor: "page2",
442
- };
443
- } else {
444
- // Page 2: return last tool, no nextCursor
445
- result = {
446
- tools: [MOCK_TOOLS[2]],
447
- };
448
- }
449
- break;
450
- }
451
-
452
- default:
453
- return Response.json(
454
- {
455
- jsonrpc: "2.0",
456
- id: body.id,
457
- error: { code: -32601, message: `Unknown: ${body.method}` },
458
- },
459
- { status: 200 },
460
- );
461
- }
462
-
463
- return Response.json({
464
- jsonrpc: "2.0",
465
- id: body.id,
466
- result,
467
- });
468
- })();
469
- },
470
- });
471
- paginatedPort = paginatedServer.port;
472
-
473
- await codegen({
474
- server: `http://localhost:${paginatedPort}`,
475
- outDir: PAGINATED_OUT_DIR,
476
- name: "paginated",
477
- });
478
- });
479
-
480
- afterAll(() => {
481
- paginatedServer.stop();
482
- rmSync(PAGINATED_OUT_DIR, { recursive: true, force: true });
483
- });
484
-
485
- test("collects all tools across paginated responses", () => {
486
- // All 3 tools should be present despite being split across 2 pages
487
- expect(existsSync(join(PAGINATED_OUT_DIR, "search-pages.tool.md"))).toBe(true);
488
- expect(existsSync(join(PAGINATED_OUT_DIR, "create-page.tool.md"))).toBe(true);
489
- expect(existsSync(join(PAGINATED_OUT_DIR, "get-page.tool.md"))).toBe(true);
490
- });
491
-
492
- test("manifest contains all paginated tools", () => {
493
- const manifest = JSON.parse(
494
- readFileSync(join(PAGINATED_OUT_DIR, ".codegen-manifest.json"), "utf-8"),
495
- );
496
- expect(manifest.tools.length).toBe(3);
497
- });
498
- });
499
-
500
- describe("useAgent", () => {
501
- beforeAll(async () => {
502
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
503
- await codegen({
504
- server: `http://localhost:${mockPort}`,
505
- outDir: TEST_OUT_DIR,
506
- name: "notion",
507
- });
508
- });
509
-
510
- afterAll(() => {
511
- rmSync(TEST_OUT_DIR, { recursive: true, force: true });
512
- });
513
-
514
- test("executes a tool via MCP server", async () => {
515
- const result = (await useAgent({
516
- agentDir: TEST_OUT_DIR,
517
- tool: "search_pages",
518
- params: { query: "hello" },
519
- })) as { content: { type: string; text: string }[] };
520
-
521
- expect(result.content).toBeDefined();
522
- expect(result.content[0].type).toBe("text");
523
- const parsed = JSON.parse(result.content[0].text);
524
- expect(parsed.tool).toBe("search_pages");
525
- expect(parsed.args.query).toBe("hello");
526
- expect(parsed.mock).toBe(true);
527
- });
528
-
529
- test("rejects unknown tool", async () => {
530
- await expect(
531
- useAgent({
532
- agentDir: TEST_OUT_DIR,
533
- tool: "nonexistent_tool",
534
- }),
535
- ).rejects.toThrow("Unknown tool");
536
- });
537
- });