Skip to content

Commit 656143f

Browse files
Merge branch 'main' into generate-api-docs
2 parents b8eeb5d + 54303b4 commit 656143f

File tree

5 files changed

+84
-13
lines changed

5 files changed

+84
-13
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
/**

src/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,16 @@ export const ImplementationSchema = BaseMetadataSchema.extend({
329329
/**
330330
* An optional URL of the website for this implementation.
331331
*/
332-
websiteUrl: z.string().optional()
332+
websiteUrl: z.string().optional(),
333+
334+
/**
335+
* An optional human-readable description of what this implementation does.
336+
*
337+
* This can be used by clients or servers to provide context about their purpose
338+
* and capabilities. For example, a server might describe the types of resources
339+
* or tools it provides, while a client might describe its intended use case.
340+
*/
341+
description: z.string().optional()
333342
});
334343

335344
const FormElicitationCapabilitySchema = z.intersection(

src/validation/ajv-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* AJV-based JSON Schema validator provider
33
*/
44

5-
import { Ajv } from 'ajv';
5+
import Ajv from 'ajv';
66
import _addFormats from 'ajv-formats';
77
import type { JsonSchemaType, JsonSchemaValidator, JsonSchemaValidatorResult, jsonSchemaValidator } from './types.js';
88

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+
});

tsconfig.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"compilerOptions": {
3-
"target": "es2018",
4-
"module": "Node16",
5-
"moduleResolution": "Node16",
3+
"target": "es2020",
4+
"module": "es2022",
5+
"moduleResolution": "bundler",
66
"declaration": true,
77
"declarationMap": true,
88
"sourceMap": true,
99
"outDir": "./dist",
1010
"strict": true,
1111
"esModuleInterop": true,
12+
"allowSyntheticDefaultImports": true,
1213
"forceConsistentCasingInFileNames": true,
1314
"resolveJsonModule": true,
1415
"isolatedModules": true,

0 commit comments

Comments
 (0)