Skip to content

Commit 5aaf6ee

Browse files
Fix optional argument handling in prompts for Zod V4 (#1199)
Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
1 parent ae0eaf4 commit 5aaf6ee

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

src/server/mcp.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3879,6 +3879,51 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
38793879
expect(result.resources[0].description).toBe('Overridden description');
38803880
expect(result.resources[0].mimeType).toBe('text/markdown');
38813881
});
3882+
3883+
test('should support optional prompt arguments', async () => {
3884+
const mcpServer = new McpServer({
3885+
name: 'test server',
3886+
version: '1.0'
3887+
});
3888+
3889+
const client = new Client({
3890+
name: 'test client',
3891+
version: '1.0'
3892+
});
3893+
3894+
mcpServer.registerPrompt(
3895+
'test-prompt',
3896+
{
3897+
argsSchema: {
3898+
name: z.string().optional()
3899+
}
3900+
},
3901+
() => ({
3902+
messages: []
3903+
})
3904+
);
3905+
3906+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
3907+
3908+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
3909+
3910+
const result = await client.request(
3911+
{
3912+
method: 'prompts/list'
3913+
},
3914+
ListPromptsResultSchema
3915+
);
3916+
3917+
expect(result.prompts).toHaveLength(1);
3918+
expect(result.prompts[0].name).toBe('test-prompt');
3919+
expect(result.prompts[0].arguments).toEqual([
3920+
{
3921+
name: 'name',
3922+
description: undefined,
3923+
required: false
3924+
}
3925+
]);
3926+
});
38823927
});
38833928

38843929
describe('Tool title precedence', () => {

src/server/zod-compat.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface ZodV3Internal {
3131
export interface ZodV4Internal {
3232
_zod?: {
3333
def?: {
34-
typeName?: string;
34+
type?: string;
3535
value?: unknown;
3636
values?: unknown[];
3737
shape?: Record<string, AnySchema> | (() => Record<string, AnySchema>);
@@ -175,7 +175,7 @@ export function normalizeObjectSchema(schema: AnySchema | ZodRawShapeCompat | un
175175
// Check if it's a v4 object
176176
const v4Schema = schema as unknown as ZodV4Internal;
177177
const def = v4Schema._zod?.def;
178-
if (def && (def.typeName === 'object' || def.shape !== undefined)) {
178+
if (def && (def.type === 'object' || def.shape !== undefined)) {
179179
return schema as AnyObjectSchema;
180180
}
181181
} else {
@@ -238,7 +238,7 @@ export function getSchemaDescription(schema: AnySchema): string | undefined {
238238
export function isSchemaOptional(schema: AnySchema): boolean {
239239
if (isZ4Schema(schema)) {
240240
const v4Schema = schema as unknown as ZodV4Internal;
241-
return v4Schema._zod?.def?.typeName === 'ZodOptional';
241+
return v4Schema._zod?.def?.type === 'optional';
242242
}
243243
const v3Schema = schema as unknown as ZodV3Internal;
244244
// v3 has isOptional() method

0 commit comments

Comments
 (0)