@slashfi/agents-sdk 0.33.1 → 0.34.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.
@@ -0,0 +1,306 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { z } from "zod";
3
+ import {
4
+ stripNulls,
5
+ nullTolerant,
6
+ zodToOpenAiJsonSchema,
7
+ callAgentValidationSchema,
8
+ callAgentRequestSchema,
9
+ listAgentsValidationSchema,
10
+ } from "./call-agent-schema";
11
+
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // stripNulls
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+
16
+ describe("stripNulls", () => {
17
+ test("converts null to undefined", () => {
18
+ expect(stripNulls(null)).toBe(undefined);
19
+ });
20
+
21
+ test("passes through primitives", () => {
22
+ expect(stripNulls("hello")).toBe("hello");
23
+ expect(stripNulls(42)).toBe(42);
24
+ expect(stripNulls(true)).toBe(true);
25
+ expect(stripNulls(undefined)).toBe(undefined);
26
+ });
27
+
28
+ test("strips null from flat objects", () => {
29
+ expect(stripNulls({ a: null, b: "hello" })).toEqual({ b: "hello" });
30
+ });
31
+
32
+ test("strips null from nested objects", () => {
33
+ expect(stripNulls({ a: { b: null, c: "ok" }, d: null })).toEqual({
34
+ a: { c: "ok" },
35
+ });
36
+ });
37
+
38
+ test("strips null from arrays", () => {
39
+ expect(stripNulls([1, null, 3])).toEqual([1, undefined, 3]);
40
+ });
41
+
42
+ test("handles deeply nested structures", () => {
43
+ const input = {
44
+ action: "execute_tool",
45
+ path: "/agents/@test",
46
+ tool: "my_tool",
47
+ params: null,
48
+ callerId: null,
49
+ callerType: null,
50
+ metadata: { nested: null, keep: "this" },
51
+ };
52
+ const result = stripNulls(input) as Record<string, unknown>;
53
+ expect(result.action).toBe("execute_tool");
54
+ expect(result.path).toBe("/agents/@test");
55
+ expect(result.params).toBe(undefined);
56
+ expect(result.callerId).toBe(undefined);
57
+ expect(result.callerType).toBe(undefined);
58
+ expect(result.metadata).toEqual({ keep: "this" });
59
+ });
60
+
61
+ test("handles empty objects and arrays", () => {
62
+ expect(stripNulls({})).toEqual({});
63
+ expect(stripNulls([])).toEqual([]);
64
+ });
65
+ });
66
+
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+ // nullTolerant
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+
71
+ describe("nullTolerant", () => {
72
+ const schema = z.object({
73
+ name: z.string().optional(),
74
+ count: z.number().optional(),
75
+ tags: z.array(z.string()).optional(),
76
+ nested: z
77
+ .object({
78
+ value: z.string().optional(),
79
+ })
80
+ .optional(),
81
+ });
82
+
83
+ test("original schema rejects null", () => {
84
+ expect(() => schema.parse({ name: null })).toThrow();
85
+ expect(() => schema.parse({ tags: null })).toThrow();
86
+ expect(() => schema.parse({ count: null })).toThrow();
87
+ });
88
+
89
+ test("tolerant schema accepts null for optional string", () => {
90
+ const tolerant = nullTolerant(schema);
91
+ const result = tolerant.parse({ name: null });
92
+ expect(result).toEqual({});
93
+ });
94
+
95
+ test("tolerant schema accepts null for optional array", () => {
96
+ const tolerant = nullTolerant(schema);
97
+ const result = tolerant.parse({ tags: null });
98
+ expect(result).toEqual({});
99
+ });
100
+
101
+ test("tolerant schema accepts null for optional number", () => {
102
+ const tolerant = nullTolerant(schema);
103
+ const result = tolerant.parse({ count: null });
104
+ expect(result).toEqual({});
105
+ });
106
+
107
+ test("tolerant schema accepts null for nested optional", () => {
108
+ const tolerant = nullTolerant(schema);
109
+ const result = tolerant.parse({ nested: { value: null } });
110
+ expect(result).toEqual({ nested: {} });
111
+ });
112
+
113
+ test("tolerant schema still validates real values", () => {
114
+ const tolerant = nullTolerant(schema);
115
+ const result = tolerant.parse({
116
+ name: "test",
117
+ count: 5,
118
+ tags: ["a", "b"],
119
+ });
120
+ expect(result).toEqual({ name: "test", count: 5, tags: ["a", "b"] });
121
+ });
122
+
123
+ test("tolerant schema still rejects invalid types", () => {
124
+ const tolerant = nullTolerant(schema);
125
+ expect(() => tolerant.parse({ name: 123 })).toThrow();
126
+ expect(() => tolerant.parse({ count: "not a number" })).toThrow();
127
+ expect(() => tolerant.parse({ tags: "not an array" })).toThrow();
128
+ });
129
+
130
+ test("all nulls at once", () => {
131
+ const tolerant = nullTolerant(schema);
132
+ const result = tolerant.parse({
133
+ name: null,
134
+ count: null,
135
+ tags: null,
136
+ nested: null,
137
+ });
138
+ expect(result).toEqual({});
139
+ });
140
+ });
141
+
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+ // callAgentValidationSchema — real-world scenarios
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+
146
+ describe("callAgentValidationSchema", () => {
147
+ test("accepts execute_tool with null optional fields (the original bug)", () => {
148
+ const input = {
149
+ request: {
150
+ action: "execute_tool",
151
+ path: "/agents/@librarian",
152
+ tool: "search_skill",
153
+ params: { query: "agent registry" },
154
+ callerId: null,
155
+ callerType: null,
156
+ metadata: null,
157
+ },
158
+ };
159
+ const result = callAgentValidationSchema.safeParse(input);
160
+ expect(result.success).toBe(true);
161
+ });
162
+
163
+ test("accepts describe_tools with tools: null (the original bug)", () => {
164
+ const input = {
165
+ request: {
166
+ action: "describe_tools",
167
+ path: "/agents/@librarian",
168
+ tools: null,
169
+ callerId: null,
170
+ callerType: null,
171
+ metadata: null,
172
+ },
173
+ };
174
+ const result = callAgentValidationSchema.safeParse(input);
175
+ expect(result.success).toBe(true);
176
+ });
177
+
178
+ test("accepts invoke with null sessionId and branchAttributes", () => {
179
+ const input = {
180
+ request: {
181
+ action: "invoke",
182
+ path: "/agents/@compactor",
183
+ prompt: "Hello",
184
+ sessionId: null,
185
+ branchAttributes: null,
186
+ callerId: null,
187
+ callerType: null,
188
+ metadata: null,
189
+ },
190
+ };
191
+ const result = callAgentValidationSchema.safeParse(input);
192
+ expect(result.success).toBe(true);
193
+ });
194
+
195
+ test("accepts ask with null optional fields", () => {
196
+ const input = {
197
+ request: {
198
+ action: "ask",
199
+ path: "/agents/@worker",
200
+ prompt: "What time is it?",
201
+ sessionId: null,
202
+ branchAttributes: null,
203
+ callerId: null,
204
+ callerType: null,
205
+ metadata: null,
206
+ },
207
+ };
208
+ const result = callAgentValidationSchema.safeParse(input);
209
+ expect(result.success).toBe(true);
210
+ });
211
+
212
+ test("accepts list_resources with null optional fields", () => {
213
+ const input = {
214
+ request: {
215
+ action: "list_resources",
216
+ path: "/agents/@agent-fs",
217
+ callerId: null,
218
+ callerType: null,
219
+ metadata: null,
220
+ },
221
+ };
222
+ const result = callAgentValidationSchema.safeParse(input);
223
+ expect(result.success).toBe(true);
224
+ });
225
+
226
+ test("accepts read_resources with null optional fields", () => {
227
+ const input = {
228
+ request: {
229
+ action: "read_resources",
230
+ path: "/agents/@agent-fs",
231
+ uris: ["AUTH.md"],
232
+ callerId: null,
233
+ callerType: null,
234
+ metadata: null,
235
+ },
236
+ };
237
+ const result = callAgentValidationSchema.safeParse(input);
238
+ expect(result.success).toBe(true);
239
+ });
240
+
241
+ test("still rejects truly invalid input", () => {
242
+ const result = callAgentValidationSchema.safeParse({
243
+ request: {
244
+ action: "execute_tool",
245
+ // missing required 'path' and 'tool'
246
+ },
247
+ });
248
+ expect(result.success).toBe(false);
249
+ });
250
+
251
+ test("still rejects unknown action", () => {
252
+ const result = callAgentValidationSchema.safeParse({
253
+ request: {
254
+ action: "unknown_action",
255
+ path: "/agents/@test",
256
+ },
257
+ });
258
+ expect(result.success).toBe(false);
259
+ });
260
+ });
261
+
262
+ // ─────────────────────────────────────────────────────────────────────────────
263
+ // listAgentsValidationSchema
264
+ // ─────────────────────────────────────────────────────────────────────────────
265
+
266
+ describe("listAgentsValidationSchema", () => {
267
+ test("accepts all-null optional fields", () => {
268
+ const result = listAgentsValidationSchema.parse({
269
+ query: null,
270
+ limit: null,
271
+ cursor: null,
272
+ });
273
+ expect(result).toEqual({});
274
+ });
275
+
276
+ test("accepts mix of null and real values", () => {
277
+ const result = listAgentsValidationSchema.parse({
278
+ query: "notion",
279
+ limit: null,
280
+ cursor: null,
281
+ });
282
+ expect(result).toEqual({ query: "notion" });
283
+ });
284
+ });
285
+
286
+ // ─────────────────────────────────────────────────────────────────────────────
287
+ // zodToOpenAiJsonSchema
288
+ // ─────────────────────────────────────────────────────────────────────────────
289
+
290
+ describe("zodToOpenAiJsonSchema", () => {
291
+ test("produces JSON schema with nullable optional fields", () => {
292
+ const schema = z.object({
293
+ required: z.string(),
294
+ optional: z.string().optional(),
295
+ });
296
+ const jsonSchema = zodToOpenAiJsonSchema(schema) as any;
297
+
298
+ // Required field should be in required array
299
+ expect(jsonSchema.required).toContain("required");
300
+ // Optional field should also be required (openAi convention)
301
+ expect(jsonSchema.required).toContain("optional");
302
+ // Optional field should allow null
303
+ const optProp = jsonSchema.properties.optional;
304
+ expect(optProp.anyOf || optProp.type).toBeTruthy();
305
+ });
306
+ });
@@ -12,7 +12,69 @@
12
12
  * Response types live in types.ts (they're output shapes, not validated input).
13
13
  */
14
14
 
15
- import { z } from "zod";
15
+ import { z, type ZodTypeAny } from "zod";
16
+ import { zodToJsonSchema } from "zod-to-json-schema";
17
+
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ // OpenAI null-tolerance transform
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Recursively strip null values from an object, converting them to undefined.
24
+ * This is the inverse of zod-to-json-schema's openAi target behavior, which
25
+ * converts .optional() fields to nullable+required in JSON Schema.
26
+ *
27
+ * Used as a z.preprocess() step so that Zod's .optional() (which accepts
28
+ * undefined but not null) works correctly with LLM outputs that send null
29
+ * for "no value" per the OpenAI function calling convention.
30
+ */
31
+ export function stripNulls(obj: unknown): unknown {
32
+ if (obj === null) return undefined;
33
+ if (Array.isArray(obj)) return obj.map(stripNulls);
34
+ if (typeof obj === "object") {
35
+ return Object.fromEntries(
36
+ Object.entries(obj as Record<string, unknown>).map(([k, v]) => [
37
+ k,
38
+ stripNulls(v),
39
+ ]),
40
+ );
41
+ }
42
+ return obj;
43
+ }
44
+
45
+ /**
46
+ * Wrap a Zod schema with a preprocess step that converts null → undefined.
47
+ * This makes the schema "null-tolerant" — matching what the OpenAI JSON Schema
48
+ * target promises to LLMs (nullable fields) while keeping Zod's .optional()
49
+ * semantics internally.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const schema = z.object({ name: z.string().optional() });
54
+ * const tolerant = nullTolerant(schema);
55
+ * tolerant.parse({ name: null }); // { name: undefined } — no error
56
+ * ```
57
+ */
58
+ export function nullTolerant<T extends ZodTypeAny>(schema: T) {
59
+ return z.preprocess(stripNulls, schema) as unknown as T;
60
+ }
61
+
62
+ /**
63
+ * Convert a Zod schema to JSON Schema using the OpenAI target,
64
+ * which makes all optional fields nullable+required.
65
+ *
66
+ * This is the standard way to generate input schemas for MCP tools
67
+ * that will be called by LLMs.
68
+ */
69
+ export function zodToOpenAiJsonSchema(
70
+ schema: ZodTypeAny,
71
+ ): Record<string, unknown> {
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ return zodToJsonSchema(schema as any, { target: "openAi" }) as Record<
74
+ string,
75
+ unknown
76
+ >;
77
+ }
16
78
 
17
79
  // ─────────────────────────────────────────────────────────────────────────────
18
80
  // Base schemas
@@ -85,12 +147,20 @@ export const loadActionSchema = callAgentBaseSchema.extend({
85
147
 
86
148
  /** List resources: discover available resources on an agent */
87
149
  export const listResourcesActionSchema = callAgentBaseSchema.extend({
88
- action: z.literal("list_resources").describe("List all resources available on an agent — docs, auth instructions, config schemas, etc."),
150
+ action: z
151
+ .literal("list_resources")
152
+ .describe(
153
+ "List all resources available on an agent — docs, auth instructions, config schemas, etc.",
154
+ ),
89
155
  });
90
156
 
91
157
  /** Read resources: fetch one or more resources by URI */
92
158
  export const readResourcesActionSchema = callAgentBaseSchema.extend({
93
- action: z.literal("read_resources").describe("Fetch one or more resources by URI. Use list_resources first to discover available URIs."),
159
+ action: z
160
+ .literal("read_resources")
161
+ .describe(
162
+ "Fetch one or more resources by URI. Use list_resources first to discover available URIs.",
163
+ ),
94
164
  uris: z
95
165
  .array(z.string())
96
166
  .describe("Resource URIs to read (e.g., ['AUTH.md'])"),
@@ -140,15 +210,13 @@ export type CallerType = z.infer<typeof callerTypeSchema>;
140
210
  /** All supported action strings as a const array. */
141
211
  export const CALL_AGENT_ACTIONS: AgentAction[] =
142
212
  callAgentRequestSchema.options.map(
143
- (s) => (s.shape as { action: z.ZodLiteral<AgentAction> }).action.value
213
+ (s) => (s.shape as { action: z.ZodLiteral<AgentAction> }).action.value,
144
214
  );
145
215
 
146
216
  // ─────────────────────────────────────────────────────────────────────────────
147
217
  // JSON Schema for MCP (derived from zod)
148
218
  // ─────────────────────────────────────────────────────────────────────────────
149
219
 
150
- import { zodToJsonSchema } from "zod-to-json-schema";
151
-
152
220
  /**
153
221
  * Zod schema for the full MCP tool input (wraps request in an outer object).
154
222
  * This is the schema that gets converted to JSON Schema for the LLM.
@@ -163,10 +231,17 @@ export const callAgentToolInputSchema = z.object({
163
231
  *
164
232
  * Fully derived from the zod schemas — no hand-written JSON Schema.
165
233
  */
166
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
- export const callAgentInputSchema = zodToJsonSchema(
168
- callAgentToolInputSchema as any,
169
- { target: "openAi" }
234
+ export const callAgentInputSchema = zodToOpenAiJsonSchema(
235
+ callAgentToolInputSchema,
236
+ );
237
+
238
+ /**
239
+ * Null-tolerant validation schema for `call_agent`.
240
+ * Accepts null values where the JSON Schema promises nullable,
241
+ * converting them to undefined before Zod validation.
242
+ */
243
+ export const callAgentValidationSchema = nullTolerant(
244
+ callAgentToolInputSchema,
170
245
  );
171
246
 
172
247
  // ─────────────────────────────────────────────────────────────────────────────
@@ -183,9 +258,7 @@ export const listAgentsToolInputSchema = z.object({
183
258
  limit: z
184
259
  .number()
185
260
  .optional()
186
- .describe(
187
- "Maximum number of results per page (default: 20)",
188
- ),
261
+ .describe("Maximum number of results per page (default: 20)"),
189
262
  cursor: z
190
263
  .string()
191
264
  .optional()
@@ -196,8 +269,10 @@ export const listAgentsToolInputSchema = z.object({
196
269
 
197
270
  export type ListAgentsInput = z.infer<typeof listAgentsToolInputSchema>;
198
271
 
199
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
- export const listAgentsInputSchema = zodToJsonSchema(
201
- listAgentsToolInputSchema as any,
202
- { target: "openAi" }
272
+ export const listAgentsInputSchema = zodToOpenAiJsonSchema(
273
+ listAgentsToolInputSchema,
274
+ );
275
+
276
+ export const listAgentsValidationSchema = nullTolerant(
277
+ listAgentsToolInputSchema,
203
278
  );
package/src/index.ts CHANGED
@@ -384,6 +384,8 @@ export type { IntrospectOptions } from "./introspect.js";
384
384
  export {
385
385
  callAgentInputSchema,
386
386
  callAgentRequestSchema,
387
+ callAgentValidationSchema,
388
+ callAgentToolInputSchema,
387
389
  invokeActionSchema,
388
390
  askActionSchema,
389
391
  executeToolActionSchema,
@@ -393,6 +395,10 @@ export {
393
395
  readResourcesActionSchema,
394
396
  callerTypeSchema,
395
397
  CALL_AGENT_ACTIONS,
398
+ nullTolerant,
399
+ stripNulls,
400
+ zodToOpenAiJsonSchema,
401
+ listAgentsValidationSchema,
396
402
  } from "./call-agent-schema.js";
397
403
 
398
404
  // ============================================
@@ -229,8 +229,13 @@ type ListAgentsEntry = Omit<AgentListing, "publisher" | "tools"> & {
229
229
  tools?: Array<{ name: string; description?: string } | string>;
230
230
  };
231
231
 
232
- /** Response shape from list_agents — an array of agent entries. */
233
- type ListAgentsResponse = ListAgentsEntry[];
232
+ /** Response shape from list_agents — wrapped object with agents array. */
233
+ type ListAgentsResponse = {
234
+ agents: ListAgentsEntry[];
235
+ total?: number;
236
+ nextCursor?: string;
237
+ success?: boolean;
238
+ };
234
239
 
235
240
  // ============================================
236
241
  // Secret Resolver
@@ -590,7 +595,7 @@ export async function createRegistryConsumer(
590
595
  const mcpUrl =
591
596
  configuration.call_endpoint ?? registry.url.replace(/\/$/, "");
592
597
 
593
- const agents = await callMcpTool(
598
+ const response = await callMcpTool(
594
599
  mcpUrl,
595
600
  "list_agents",
596
601
  query ? { query } : {},
@@ -601,6 +606,8 @@ export async function createRegistryConsumer(
601
606
  fetchFn,
602
607
  ) as ListAgentsResponse;
603
608
 
609
+ const agents = response.agents ?? [];
610
+
604
611
  return agents.map((agent) => ({
605
612
  ...agent,
606
613
  ...agent,
package/src/server.ts CHANGED
@@ -48,8 +48,11 @@ import type { AgentDefinition, CallAgentRequest } from "./types.js";
48
48
 
49
49
  import {
50
50
  callAgentInputSchema,
51
+ callAgentValidationSchema,
51
52
  listAgentsInputSchema,
52
- listAgentsToolInputSchema,
53
+ listAgentsValidationSchema,
54
+ nullTolerant,
55
+ zodToOpenAiJsonSchema,
53
56
  } from "./call-agent-schema.js";
54
57
 
55
58
  // ============================================
@@ -146,6 +149,24 @@ export interface AgentServerOptions {
146
149
  * ```
147
150
  */
148
151
  resolveAuth?: (req: Request) => Promise<ResolvedAuth | null>;
152
+ /**
153
+ * Schema overrides for built-in MCP tools.
154
+ * When provided, these replace the default schemas for both
155
+ * JSON Schema generation (what LLMs see) and runtime validation.
156
+ *
157
+ * Schemas must be Zod schemas that are supersets of the defaults
158
+ * (e.g., extending the base action schemas with additional fields).
159
+ *
160
+ * The server automatically:
161
+ * - Converts to JSON Schema (openAi target) for tools/list
162
+ * - Wraps with nullTolerant() for validation
163
+ */
164
+ schemas?: {
165
+ /** Override call_agent tool input schema (wraps callAgentRequestSchema) */
166
+ callAgent?: import("zod").ZodTypeAny;
167
+ /** Override list_agents tool input schema */
168
+ listAgents?: import("zod").ZodTypeAny;
169
+ };
149
170
  /**
150
171
  * Registry capabilities — advertised in MCP initialize response.
151
172
  * When set, this server identifies as an agent registry (superset of MCP).
@@ -437,19 +458,23 @@ function resolveAgent(
437
458
  // MCP Tool Definitions
438
459
  // ============================================
439
460
 
440
- function getToolDefinitions() {
461
+ function getToolDefinitions(schemas?: AgentServerOptions["schemas"]) {
441
462
  return [
442
463
  {
443
464
  name: "call_agent",
444
465
  description:
445
466
  "Execute a tool on a registered agent. Provide the agent path and tool name.\n\nSupported actions:\n- invoke: Fire-and-forget agent invocation\n- ask: Invoke and wait for response\n- execute_tool: Call a specific tool on an agent\n- describe_tools: Get tool schemas for an agent\n- load: Get agent definition/system prompt\n- list_resources: List all resources available on an agent (docs, auth instructions, config schemas, etc.)\n- read_resources: Fetch one or more resources by URI",
446
- inputSchema: callAgentInputSchema,
467
+ inputSchema: schemas?.callAgent
468
+ ? zodToOpenAiJsonSchema(schemas.callAgent)
469
+ : callAgentInputSchema,
447
470
  },
448
471
  {
449
472
  name: "list_agents",
450
473
  description:
451
474
  "List all registered agents and their available tools. Optionally search/filter by query using BM25 ranking.",
452
- inputSchema: listAgentsInputSchema,
475
+ inputSchema: schemas?.listAgents
476
+ ? zodToOpenAiJsonSchema(schemas.listAgents)
477
+ : listAgentsInputSchema,
453
478
  },
454
479
  ];
455
480
  }
@@ -473,6 +498,15 @@ export function createAgentServer(
473
498
  oauthIdentityProvider,
474
499
  } = options;
475
500
 
501
+ // Build tool definitions and validation schemas from overrides
502
+ const toolDefs = getToolDefinitions(options.schemas);
503
+ const callAgentValidate = options.schemas?.callAgent
504
+ ? nullTolerant(options.schemas.callAgent)
505
+ : callAgentValidationSchema;
506
+ const listAgentsValidate = options.schemas?.listAgents
507
+ ? nullTolerant(options.schemas.listAgents)
508
+ : listAgentsValidationSchema;
509
+
476
510
  // OIDC sign-in handler (if configured)
477
511
  const oidcSignIn = options.oidcProvider
478
512
  ? createOIDCSignIn(options.oidcProvider)
@@ -518,7 +552,7 @@ export function createAgentServer(
518
552
 
519
553
  case "tools/list":
520
554
  return jsonRpcSuccess(request.id, {
521
- tools: getToolDefinitions(),
555
+ tools: toolDefs,
522
556
  });
523
557
 
524
558
  case "tools/call": {
@@ -562,7 +596,11 @@ export function createAgentServer(
562
596
  ) {
563
597
  switch (toolName) {
564
598
  case "call_agent": {
565
- const req = (args.request ?? args) as CallAgentRequest;
599
+ // Validate + strip nulls (OpenAI convention: null = absent)
600
+ const parsed = callAgentValidate.safeParse(args);
601
+ const req = (parsed.success
602
+ ? (parsed.data as Record<string, unknown>).request ?? parsed.data
603
+ : (args.request ?? args)) as CallAgentRequest;
566
604
 
567
605
  // Inject auth context
568
606
  if (auth) {
@@ -597,7 +635,7 @@ export function createAgentServer(
597
635
 
598
636
  case "list_agents": {
599
637
  const { query: listQuery, limit: listLimit, cursor: listCursor } =
600
- listAgentsToolInputSchema.parse(args);
638
+ listAgentsValidate.parse(args);
601
639
  const agents = registry.list();
602
640
  let visible = agents.filter((agent) => canSeeAgent(agent, auth));
603
641