Skip to content

Commit 9941294

Browse files
Fix Zod v4 schema description extraction (#1296)
1 parent 34c9e16 commit 9941294

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

src/server/zod-compat.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ export interface ZodV4Internal {
3535
value?: unknown;
3636
values?: unknown[];
3737
shape?: Record<string, AnySchema> | (() => Record<string, AnySchema>);
38-
description?: string;
3938
};
4039
};
4140
value?: unknown;
@@ -220,15 +219,12 @@ export function getParseErrorMessage(error: unknown): string {
220219
/**
221220
* Gets the description from a schema, if available.
222221
* Works with both Zod v3 and v4.
222+
*
223+
* Both versions expose a `.description` getter that returns the description
224+
* from their respective internal storage (v3: _def, v4: globalRegistry).
223225
*/
224226
export function getSchemaDescription(schema: AnySchema): string | undefined {
225-
if (isZ4Schema(schema)) {
226-
const v4Schema = schema as unknown as ZodV4Internal;
227-
return v4Schema._zod?.def?.description;
228-
}
229-
const v3Schema = schema as unknown as ZodV3Internal;
230-
// v3 may have description on the schema itself or in _def
231-
return (schema as { description?: string }).description ?? v3Schema._def?.description;
227+
return (schema as { description?: string }).description;
232228
}
233229

234230
/**
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/1277
3+
*
4+
* Zod v4 stores `.describe()` descriptions directly on the schema object,
5+
* not in `._zod.def.description`. This test verifies that descriptions are
6+
* correctly extracted for prompt arguments.
7+
*/
8+
9+
import { Client } from '../../src/client/index.js';
10+
import { InMemoryTransport } from '../../src/inMemory.js';
11+
import { ListPromptsResultSchema } from '../../src/types.js';
12+
import { McpServer } from '../../src/server/mcp.js';
13+
import { zodTestMatrix, type ZodMatrixEntry } from '../../src/__fixtures__/zodTestMatrix.js';
14+
15+
describe.each(zodTestMatrix)('Issue #1277: $zodVersionLabel', (entry: ZodMatrixEntry) => {
16+
const { z } = entry;
17+
18+
test('should preserve argument descriptions from .describe()', async () => {
19+
const mcpServer = new McpServer({
20+
name: 'test server',
21+
version: '1.0'
22+
});
23+
const client = new Client({
24+
name: 'test client',
25+
version: '1.0'
26+
});
27+
28+
mcpServer.prompt(
29+
'test',
30+
{
31+
name: z.string().describe('The user name'),
32+
value: z.string().describe('The value to set')
33+
},
34+
async ({ name, value }) => ({
35+
messages: [
36+
{
37+
role: 'assistant',
38+
content: {
39+
type: 'text',
40+
text: `${name}: ${value}`
41+
}
42+
}
43+
]
44+
})
45+
);
46+
47+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
48+
49+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
50+
51+
const result = await client.request(
52+
{
53+
method: 'prompts/list'
54+
},
55+
ListPromptsResultSchema
56+
);
57+
58+
expect(result.prompts).toHaveLength(1);
59+
expect(result.prompts[0].name).toBe('test');
60+
expect(result.prompts[0].arguments).toEqual([
61+
{ name: 'name', required: true, description: 'The user name' },
62+
{ name: 'value', required: true, description: 'The value to set' }
63+
]);
64+
});
65+
});

0 commit comments

Comments
 (0)