Skip to content

Commit 961b39c

Browse files
Allow extra JSON Schema keys in elicit primitive schemas
Add .passthrough() to all primitive schema Zod definitions (BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, and all enum schema variants) so that standard JSON Schema keywords like pattern, format, exclusiveMinimum, const, default, etc. are not rejected during validation. Also add index signatures to the corresponding TypeScript interfaces in spec.types.ts, and remove now-unnecessary @ts-expect-error comments in elicitation tests. Fixes #1844
1 parent 7ba58da commit 961b39c

File tree

3 files changed

+105
-79
lines changed

3 files changed

+105
-79
lines changed

packages/core/src/types/schemas.ts

Lines changed: 97 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,116 +1717,136 @@ export const CreateMessageResultWithToolsSchema = ResultSchema.extend({
17171717
/**
17181718
* Primitive schema definition for boolean fields.
17191719
*/
1720-
export const BooleanSchemaSchema = z.object({
1721-
type: z.literal('boolean'),
1722-
title: z.string().optional(),
1723-
description: z.string().optional(),
1724-
default: z.boolean().optional()
1725-
});
1720+
export const BooleanSchemaSchema = z
1721+
.object({
1722+
type: z.literal('boolean'),
1723+
title: z.string().optional(),
1724+
description: z.string().optional(),
1725+
default: z.boolean().optional()
1726+
})
1727+
.passthrough();
17261728

17271729
/**
17281730
* Primitive schema definition for string fields.
17291731
*/
1730-
export const StringSchemaSchema = z.object({
1731-
type: z.literal('string'),
1732-
title: z.string().optional(),
1733-
description: z.string().optional(),
1734-
minLength: z.number().optional(),
1735-
maxLength: z.number().optional(),
1736-
format: z.enum(['email', 'uri', 'date', 'date-time']).optional(),
1737-
default: z.string().optional()
1738-
});
1732+
export const StringSchemaSchema = z
1733+
.object({
1734+
type: z.literal('string'),
1735+
title: z.string().optional(),
1736+
description: z.string().optional(),
1737+
minLength: z.number().optional(),
1738+
maxLength: z.number().optional(),
1739+
format: z.enum(['email', 'uri', 'date', 'date-time']).optional(),
1740+
default: z.string().optional()
1741+
})
1742+
.passthrough();
17391743

17401744
/**
17411745
* Primitive schema definition for number fields.
17421746
*/
1743-
export const NumberSchemaSchema = z.object({
1744-
type: z.enum(['number', 'integer']),
1745-
title: z.string().optional(),
1746-
description: z.string().optional(),
1747-
minimum: z.number().optional(),
1748-
maximum: z.number().optional(),
1749-
default: z.number().optional()
1750-
});
1747+
export const NumberSchemaSchema = z
1748+
.object({
1749+
type: z.enum(['number', 'integer']),
1750+
title: z.string().optional(),
1751+
description: z.string().optional(),
1752+
minimum: z.number().optional(),
1753+
maximum: z.number().optional(),
1754+
default: z.number().optional()
1755+
})
1756+
.passthrough();
17511757

17521758
/**
17531759
* Schema for single-selection enumeration without display titles for options.
17541760
*/
1755-
export const UntitledSingleSelectEnumSchemaSchema = z.object({
1756-
type: z.literal('string'),
1757-
title: z.string().optional(),
1758-
description: z.string().optional(),
1759-
enum: z.array(z.string()),
1760-
default: z.string().optional()
1761-
});
1761+
export const UntitledSingleSelectEnumSchemaSchema = z
1762+
.object({
1763+
type: z.literal('string'),
1764+
title: z.string().optional(),
1765+
description: z.string().optional(),
1766+
enum: z.array(z.string()),
1767+
default: z.string().optional()
1768+
})
1769+
.passthrough();
17621770

17631771
/**
17641772
* Schema for single-selection enumeration with display titles for each option.
17651773
*/
1766-
export const TitledSingleSelectEnumSchemaSchema = z.object({
1767-
type: z.literal('string'),
1768-
title: z.string().optional(),
1769-
description: z.string().optional(),
1770-
oneOf: z.array(
1771-
z.object({
1772-
const: z.string(),
1773-
title: z.string()
1774-
})
1775-
),
1776-
default: z.string().optional()
1777-
});
1774+
export const TitledSingleSelectEnumSchemaSchema = z
1775+
.object({
1776+
type: z.literal('string'),
1777+
title: z.string().optional(),
1778+
description: z.string().optional(),
1779+
oneOf: z.array(
1780+
z.object({
1781+
const: z.string(),
1782+
title: z.string()
1783+
})
1784+
),
1785+
default: z.string().optional()
1786+
})
1787+
.passthrough();
17781788

17791789
/**
17801790
* Use {@linkcode TitledSingleSelectEnumSchema} instead.
17811791
* This interface will be removed in a future version.
17821792
*/
1783-
export const LegacyTitledEnumSchemaSchema = z.object({
1784-
type: z.literal('string'),
1785-
title: z.string().optional(),
1786-
description: z.string().optional(),
1787-
enum: z.array(z.string()),
1788-
enumNames: z.array(z.string()).optional(),
1789-
default: z.string().optional()
1790-
});
1793+
export const LegacyTitledEnumSchemaSchema = z
1794+
.object({
1795+
type: z.literal('string'),
1796+
title: z.string().optional(),
1797+
description: z.string().optional(),
1798+
enum: z.array(z.string()),
1799+
enumNames: z.array(z.string()).optional(),
1800+
default: z.string().optional()
1801+
})
1802+
.passthrough();
17911803

17921804
// Combined single selection enumeration
17931805
export const SingleSelectEnumSchemaSchema = z.union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]);
17941806

17951807
/**
17961808
* Schema for multiple-selection enumeration without display titles for options.
17971809
*/
1798-
export const UntitledMultiSelectEnumSchemaSchema = z.object({
1799-
type: z.literal('array'),
1800-
title: z.string().optional(),
1801-
description: z.string().optional(),
1802-
minItems: z.number().optional(),
1803-
maxItems: z.number().optional(),
1804-
items: z.object({
1805-
type: z.literal('string'),
1806-
enum: z.array(z.string())
1807-
}),
1808-
default: z.array(z.string()).optional()
1809-
});
1810+
export const UntitledMultiSelectEnumSchemaSchema = z
1811+
.object({
1812+
type: z.literal('array'),
1813+
title: z.string().optional(),
1814+
description: z.string().optional(),
1815+
minItems: z.number().optional(),
1816+
maxItems: z.number().optional(),
1817+
items: z
1818+
.object({
1819+
type: z.literal('string'),
1820+
enum: z.array(z.string())
1821+
})
1822+
.passthrough(),
1823+
default: z.array(z.string()).optional()
1824+
})
1825+
.passthrough();
18101826

18111827
/**
18121828
* Schema for multiple-selection enumeration with display titles for each option.
18131829
*/
1814-
export const TitledMultiSelectEnumSchemaSchema = z.object({
1815-
type: z.literal('array'),
1816-
title: z.string().optional(),
1817-
description: z.string().optional(),
1818-
minItems: z.number().optional(),
1819-
maxItems: z.number().optional(),
1820-
items: z.object({
1821-
anyOf: z.array(
1822-
z.object({
1823-
const: z.string(),
1824-
title: z.string()
1830+
export const TitledMultiSelectEnumSchemaSchema = z
1831+
.object({
1832+
type: z.literal('array'),
1833+
title: z.string().optional(),
1834+
description: z.string().optional(),
1835+
minItems: z.number().optional(),
1836+
maxItems: z.number().optional(),
1837+
items: z
1838+
.object({
1839+
anyOf: z.array(
1840+
z.object({
1841+
const: z.string(),
1842+
title: z.string()
1843+
})
1844+
)
18251845
})
1826-
)
1827-
}),
1828-
default: z.array(z.string()).optional()
1829-
});
1846+
.passthrough(),
1847+
default: z.array(z.string()).optional()
1848+
})
1849+
.passthrough();
18301850

18311851
/**
18321852
* Combined schema for multiple-selection enumeration

packages/core/src/types/spec.types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,7 @@ export interface StringSchema {
28792879
maxLength?: number;
28802880
format?: 'email' | 'uri' | 'date' | 'date-time';
28812881
default?: string;
2882+
[key: string]: unknown;
28822883
}
28832884

28842885
/**
@@ -2894,6 +2895,7 @@ export interface NumberSchema {
28942895
minimum?: number;
28952896
maximum?: number;
28962897
default?: number;
2898+
[key: string]: unknown;
28972899
}
28982900

28992901
/**
@@ -2907,6 +2909,7 @@ export interface BooleanSchema {
29072909
title?: string;
29082910
description?: string;
29092911
default?: boolean;
2912+
[key: string]: unknown;
29102913
}
29112914

29122915
/**
@@ -2935,6 +2938,7 @@ export interface UntitledSingleSelectEnumSchema {
29352938
* Optional default value.
29362939
*/
29372940
default?: string;
2941+
[key: string]: unknown;
29382942
}
29392943

29402944
/**
@@ -2972,6 +2976,7 @@ export interface TitledSingleSelectEnumSchema {
29722976
* Optional default value.
29732977
*/
29742978
default?: string;
2979+
[key: string]: unknown;
29752980
}
29762981

29772982
/**
@@ -3020,6 +3025,7 @@ export interface UntitledMultiSelectEnumSchema {
30203025
* Optional default value.
30213026
*/
30223027
default?: string[];
3028+
[key: string]: unknown;
30233029
}
30243030

30253031
/**
@@ -3070,6 +3076,7 @@ export interface TitledMultiSelectEnumSchema {
30703076
* Optional default value.
30713077
*/
30723078
default?: string[];
3079+
[key: string]: unknown;
30733080
}
30743081

30753082
/**
@@ -3095,6 +3102,7 @@ export interface LegacyTitledEnumSchema {
30953102
*/
30963103
enumNames?: string[];
30973104
default?: string;
3105+
[key: string]: unknown;
30983106
}
30993107

31003108
/**

test/integration/test/server/elicitation.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
161161
age: { type: 'integer', minimum: 0, maximum: 150 },
162162
street: { type: 'string' },
163163
city: { type: 'string' },
164-
// @ts-expect-error - pattern is not a valid property by MCP spec, however it is making use of the Ajv validator
165164
zipCode: { type: 'string', pattern: '^[0-9]{5}$' },
166165
newsletter: { type: 'boolean' },
167166
notifications: { type: 'boolean' }
@@ -280,7 +279,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
280279
requestedSchema: {
281280
type: 'object',
282281
properties: {
283-
// @ts-expect-error - pattern is not a valid property by MCP spec, however it is making use of the Ajv validator
284282
zipCode: { type: 'string', pattern: '^[0-9]{5}$' }
285283
},
286284
required: ['zipCode']

0 commit comments

Comments
 (0)