diff --git a/package-lock.json b/package-lock.json index 4a2dc15d2..7a92ef4af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,7 @@ "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^4.1.9" }, "devDependencies": { "@eslint/js": "^9.8.0", @@ -6673,22 +6672,13 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", - "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } } } } diff --git a/package.json b/package.json index ad0db7075..360dfb151 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,7 @@ "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^4.1.9" }, "devDependencies": { "@eslint/js": "^9.8.0", diff --git a/src/examples/server/simpleSseServer.ts b/src/examples/server/simpleSseServer.ts index b99334369..7b57844bf 100644 --- a/src/examples/server/simpleSseServer.ts +++ b/src/examples/server/simpleSseServer.ts @@ -28,8 +28,8 @@ const getServer = () => { 'start-notification-stream', 'Starts sending periodic notifications', { - interval: z.number().describe('Interval in milliseconds between notifications').default(1000), - count: z.number().describe('Number of notifications to send').default(10) + interval: z.number().describe('Interval in milliseconds between notifications').prefault(1000), + count: z.number().describe('Number of notifications to send').prefault(10) }, async ({ interval, count }, extra): Promise => { const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/src/examples/server/simpleStatelessStreamableHttp.ts b/src/examples/server/simpleStatelessStreamableHttp.ts index f71e5db6c..b3d136ed9 100644 --- a/src/examples/server/simpleStatelessStreamableHttp.ts +++ b/src/examples/server/simpleStatelessStreamableHttp.ts @@ -42,8 +42,8 @@ const getServer = () => { 'start-notification-stream', 'Starts sending periodic notifications for testing resumability', { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(10) + interval: z.number().describe('Interval in milliseconds between notifications').prefault(100), + count: z.number().describe('Number of notifications to send (0 for 100)').prefault(10) }, async ({ interval, count }, extra): Promise => { const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/src/examples/server/simpleStreamableHttp.ts b/src/examples/server/simpleStreamableHttp.ts index 5872cb4ac..eff9ef7cb 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -291,8 +291,8 @@ const getServer = () => { 'start-notification-stream', 'Starts sending periodic notifications for testing resumability', { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(50) + interval: z.number().describe('Interval in milliseconds between notifications').prefault(100), + count: z.number().describe('Number of notifications to send (0 for 100)').prefault(50) }, async ({ interval, count }, extra): Promise => { const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts index 50e2e5125..e66b3ab53 100644 --- a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts +++ b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts @@ -33,8 +33,8 @@ const getServer = () => { 'start-notification-stream', 'Starts sending periodic notifications for testing resumability', { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(50) + interval: z.number().describe('Interval in milliseconds between notifications').prefault(100), + count: z.number().describe('Number of notifications to send (0 for 100)').prefault(50) }, async ({ interval, count }, extra): Promise => { const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/src/integration-tests/stateManagementStreamableHttp.test.ts b/src/integration-tests/stateManagementStreamableHttp.test.ts index 629b01519..82d1187ee 100644 --- a/src/integration-tests/stateManagementStreamableHttp.test.ts +++ b/src/integration-tests/stateManagementStreamableHttp.test.ts @@ -56,7 +56,7 @@ describe('Streamable HTTP Transport Session Management', () => { 'greet', 'A simple greeting tool', { - name: z.string().describe('Name to greet').default('World') + name: z.string().describe('Name to greet').prefault('World') }, async ({ name }) => { return { diff --git a/src/integration-tests/taskResumability.test.ts b/src/integration-tests/taskResumability.test.ts index d397ffab3..227c1d0e6 100644 --- a/src/integration-tests/taskResumability.test.ts +++ b/src/integration-tests/taskResumability.test.ts @@ -28,7 +28,7 @@ describe('Transport resumability', () => { 'send-notification', 'Sends a single notification', { - message: z.string().describe('Message to send').default('Test notification') + message: z.string().describe('Message to send').prefault('Test notification') }, async ({ message }, { sendNotification }) => { // Send notification immediately @@ -51,8 +51,8 @@ describe('Transport resumability', () => { 'run-notifications', 'Sends multiple notifications over time', { - count: z.number().describe('Number of notifications to send').default(10), - interval: z.number().describe('Interval between notifications in ms').default(50) + count: z.number().describe('Number of notifications to send').prefault(10), + interval: z.number().describe('Interval between notifications in ms').prefault(50) }, async ({ count, interval }, { sendNotification }) => { // Send notifications at specified intervals diff --git a/src/server/auth/handlers/authorize.ts b/src/server/auth/handlers/authorize.ts index 14c7121ae..0e9386313 100644 --- a/src/server/auth/handlers/authorize.ts +++ b/src/server/auth/handlers/authorize.ts @@ -21,7 +21,9 @@ const ClientAuthorizationParamsSchema = z.object({ redirect_uri: z .string() .optional() - .refine(value => value === undefined || URL.canParse(value), { message: 'redirect_uri must be a valid URL' }) + .refine(value => value === undefined || URL.canParse(value), { + error: 'redirect_uri must be a valid URL' + }) }); // Parameters that must be validated for a successful authorization request. Failure can be reported to the redirect URI. @@ -31,7 +33,7 @@ const RequestAuthorizationParamsSchema = z.object({ code_challenge_method: z.literal('S256'), scope: z.string().optional(), state: z.string().optional(), - resource: z.string().url().optional() + resource: z.url().optional() }); export function authorizationHandler({ provider, rateLimit: rateLimitConfig }: AuthorizationHandlerOptions): RequestHandler { diff --git a/src/server/auth/handlers/token.ts b/src/server/auth/handlers/token.ts index c387ff7bf..a7480a66b 100644 --- a/src/server/auth/handlers/token.ts +++ b/src/server/auth/handlers/token.ts @@ -32,13 +32,13 @@ const AuthorizationCodeGrantSchema = z.object({ code: z.string(), code_verifier: z.string(), redirect_uri: z.string().optional(), - resource: z.string().url().optional() + resource: z.url().optional() }); const RefreshTokenGrantSchema = z.object({ refresh_token: z.string(), scope: z.string().optional(), - resource: z.string().url().optional() + resource: z.url().optional() }); export function tokenHandler({ provider, rateLimit: rateLimitConfig }: TokenHandlerOptions): RequestHandler { diff --git a/src/server/completable.ts b/src/server/completable.ts index 67d91c383..9a136ded4 100644 --- a/src/server/completable.ts +++ b/src/server/completable.ts @@ -1,4 +1,4 @@ -import { ZodTypeAny, ZodTypeDef, ZodType, ParseInput, ParseReturnType, RawCreateParams, ZodErrorMap, ProcessedCreateParams } from 'zod'; +import { ZodTypeAny } from 'zod'; export enum McpZodTypeKind { Completable = 'McpCompletable' @@ -11,69 +11,58 @@ export type CompleteCallback = ( } ) => T['_input'][] | Promise; -export interface CompletableDef extends ZodTypeDef { +export interface CompletableDef { type: T; complete: CompleteCallback; typeName: McpZodTypeKind.Completable; } -export class Completable extends ZodType, T['_input']> { - _parse(input: ParseInput): ParseReturnType { - const { ctx } = this._processInputParams(input); - const data = ctx.data; - return this._def.type._parse({ - data, - path: ctx.path, - parent: ctx - }); - } - - unwrap() { - return this._def.type; - } - - static create = ( - type: T, - params: RawCreateParams & { - complete: CompleteCallback; - } - ): Completable => { - return new Completable({ - type, - typeName: McpZodTypeKind.Completable, - complete: params.complete, - ...processCreateParams(params) - }); - }; -} +/** + * A Zod schema that has been wrapped with completion capabilities. + */ +export type CompletableSchema = T & { _def: T['_def'] & CompletableDef }; /** * Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP. + * + * Uses an immutable wrapper approach that creates a new schema object with completion metadata + * while preserving all validation behavior of the underlying schema. */ -export function completable(schema: T, complete: CompleteCallback): Completable { - return Completable.create(schema, { ...schema._def, complete }); -} - -// Not sure why this isn't exported from Zod: -// https://github.com/colinhacks/zod/blob/f7ad26147ba291cb3fb257545972a8e00e767470/src/types.ts#L130 -function processCreateParams(params: RawCreateParams): ProcessedCreateParams { - if (!params) return {}; - const { errorMap, invalid_type_error, required_error, description } = params; - if (errorMap && (invalid_type_error || required_error)) { - throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); - } - if (errorMap) return { errorMap: errorMap, description }; - const customMap: ZodErrorMap = (iss, ctx) => { - const { message } = params; +export function completable(schema: T, complete: CompleteCallback): CompletableSchema { + // Create new schema object inheriting from original + const wrapped = Object.create(Object.getPrototypeOf(schema)); - if (iss.code === 'invalid_enum_value') { - return { message: message ?? ctx.defaultError }; + // Copy all properties including getters/setters (except _def and _zod which we'll redefine) + Object.getOwnPropertyNames(schema).forEach(key => { + if (key !== '_def' && key !== '_zod') { + const descriptor = Object.getOwnPropertyDescriptor(schema, key); + if (descriptor) { + Object.defineProperty(wrapped, key, descriptor); + } } - if (typeof ctx.data === 'undefined') { - return { message: message ?? required_error ?? ctx.defaultError }; - } - if (iss.code !== 'invalid_type') return { message: ctx.defaultError }; - return { message: message ?? invalid_type_error ?? ctx.defaultError }; + }); + + // Create new def with added completion metadata + const newDef = { + ...schema._def, + typeName: McpZodTypeKind.Completable, + type: schema, + complete }; - return { errorMap: customMap, description }; + + // Set _def as read-only property (matching Zod's design) + Object.defineProperty(wrapped, '_def', { + value: newDef, + writable: false, + enumerable: false, + configurable: false + }); + + // Update _zod to maintain _def === _zod.def invariant + wrapped._zod = { + ...schema._zod, + def: newDef + }; + + return wrapped as CompletableSchema; } diff --git a/src/server/mcp.ts b/src/server/mcp.ts index cef1722d6..f2442962d 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -1,6 +1,5 @@ import { Server, ServerOptions } from './index.js'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { z, ZodRawShape, ZodObject, ZodString, AnyZodObject, ZodTypeAny, ZodType, ZodTypeDef, ZodOptional } from 'zod'; +import { z, ZodRawShape, ZodObject, ZodString, ZodType, ZodOptional } from 'zod'; import { Implementation, Tool, @@ -33,7 +32,7 @@ import { ToolAnnotations, LoggingMessageNotification } from '../types.js'; -import { Completable, CompletableDef } from './completable.js'; +import { CompletableDef, McpZodTypeKind } from './completable.js'; import { UriTemplate, Variables } from '../shared/uriTemplate.js'; import { RequestHandlerExtra } from '../shared/protocol.js'; import { Transport } from '../shared/transport.js'; @@ -103,18 +102,14 @@ export class McpServer { title: tool.title, description: tool.description, inputSchema: tool.inputSchema - ? (zodToJsonSchema(tool.inputSchema, { - strictUnions: true - }) as Tool['inputSchema']) + ? (z.toJSONSchema(tool.inputSchema) as Tool['inputSchema']) : EMPTY_OBJECT_JSON_SCHEMA, annotations: tool.annotations, _meta: tool._meta }; if (tool.outputSchema) { - toolDefinition.outputSchema = zodToJsonSchema(tool.outputSchema, { - strictUnions: true - }) as Tool['outputSchema']; + toolDefinition.outputSchema = z.toJSONSchema(tool.outputSchema) as Tool['outputSchema']; } return toolDefinition; @@ -243,11 +238,12 @@ export class McpServer { } const field = prompt.argsSchema.shape[request.params.argument.name]; - if (!(field instanceof Completable)) { + const defLike = (field as unknown as { _def?: { typeName?: unknown } })._def; + if (!defLike || defLike.typeName !== McpZodTypeKind.Completable) { return EMPTY_COMPLETION_RESULT; } - const def: CompletableDef = field._def; + const def: CompletableDef = (field as unknown as { _def: CompletableDef })._def; const suggestions = await def.complete(request.params.argument.value, request.params.context); return createCompletionResult(suggestions); } @@ -1013,7 +1009,7 @@ export class ResourceTemplate { */ export type ToolCallback = Args extends ZodRawShape ? ( - args: z.objectOutputType, + args: z.infer>, extra: RequestHandlerExtra ) => CallToolResult | Promise : (extra: RequestHandlerExtra) => CallToolResult | Promise; @@ -1021,8 +1017,8 @@ export type ToolCallback = Arg export type RegisteredTool = { title?: string; description?: string; - inputSchema?: AnyZodObject; - outputSchema?: AnyZodObject; + inputSchema?: ZodObject; + outputSchema?: ZodObject; annotations?: ToolAnnotations; _meta?: Record; callback: ToolCallback; @@ -1138,12 +1134,12 @@ export type RegisteredResourceTemplate = { }; type PromptArgsRawShape = { - [k: string]: ZodType | ZodOptional>; + [k: string]: ZodString | ZodOptional; }; export type PromptCallback = Args extends PromptArgsRawShape ? ( - args: z.objectOutputType, + args: z.infer>, extra: RequestHandlerExtra ) => GetPromptResult | Promise : (extra: RequestHandlerExtra) => GetPromptResult | Promise; diff --git a/src/shared/auth.ts b/src/shared/auth.ts index c5ddbda16..1c484f637 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -4,12 +4,11 @@ import { z } from 'zod'; * Reusable URL validation that disallows javascript: scheme */ export const SafeUrlSchema = z - .string() .url() .superRefine((val, ctx) => { if (!URL.canParse(val)) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: 'custom', message: 'URL must be parseable', fatal: true }); @@ -22,100 +21,96 @@ export const SafeUrlSchema = z const u = new URL(url); return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:'; }, - { message: 'URL cannot use javascript:, data:, or vbscript: scheme' } + { + error: 'URL cannot use javascript:, data:, or vbscript: scheme' + } ); /** * RFC 9728 OAuth Protected Resource Metadata */ -export const OAuthProtectedResourceMetadataSchema = z - .object({ - resource: z.string().url(), - authorization_servers: z.array(SafeUrlSchema).optional(), - jwks_uri: z.string().url().optional(), - scopes_supported: z.array(z.string()).optional(), - bearer_methods_supported: z.array(z.string()).optional(), - resource_signing_alg_values_supported: z.array(z.string()).optional(), - resource_name: z.string().optional(), - resource_documentation: z.string().optional(), - resource_policy_uri: z.string().url().optional(), - resource_tos_uri: z.string().url().optional(), - tls_client_certificate_bound_access_tokens: z.boolean().optional(), - authorization_details_types_supported: z.array(z.string()).optional(), - dpop_signing_alg_values_supported: z.array(z.string()).optional(), - dpop_bound_access_tokens_required: z.boolean().optional() - }) - .passthrough(); +export const OAuthProtectedResourceMetadataSchema = z.looseObject({ + resource: z.url(), + authorization_servers: z.array(SafeUrlSchema).optional(), + jwks_uri: z.url().optional(), + scopes_supported: z.array(z.string()).optional(), + bearer_methods_supported: z.array(z.string()).optional(), + resource_signing_alg_values_supported: z.array(z.string()).optional(), + resource_name: z.string().optional(), + resource_documentation: z.string().optional(), + resource_policy_uri: z.url().optional(), + resource_tos_uri: z.url().optional(), + tls_client_certificate_bound_access_tokens: z.boolean().optional(), + authorization_details_types_supported: z.array(z.string()).optional(), + dpop_signing_alg_values_supported: z.array(z.string()).optional(), + dpop_bound_access_tokens_required: z.boolean().optional() +}); /** * RFC 8414 OAuth 2.0 Authorization Server Metadata */ -export const OAuthMetadataSchema = z - .object({ - issuer: z.string(), - authorization_endpoint: SafeUrlSchema, - token_endpoint: SafeUrlSchema, - registration_endpoint: SafeUrlSchema.optional(), - scopes_supported: z.array(z.string()).optional(), - response_types_supported: z.array(z.string()), - response_modes_supported: z.array(z.string()).optional(), - grant_types_supported: z.array(z.string()).optional(), - token_endpoint_auth_methods_supported: z.array(z.string()).optional(), - token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), - service_documentation: SafeUrlSchema.optional(), - revocation_endpoint: SafeUrlSchema.optional(), - revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(), - revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), - introspection_endpoint: z.string().optional(), - introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(), - introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), - code_challenge_methods_supported: z.array(z.string()).optional() - }) - .passthrough(); +export const OAuthMetadataSchema = z.looseObject({ + issuer: z.string(), + authorization_endpoint: SafeUrlSchema, + token_endpoint: SafeUrlSchema, + registration_endpoint: SafeUrlSchema.optional(), + scopes_supported: z.array(z.string()).optional(), + response_types_supported: z.array(z.string()), + response_modes_supported: z.array(z.string()).optional(), + grant_types_supported: z.array(z.string()).optional(), + token_endpoint_auth_methods_supported: z.array(z.string()).optional(), + token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), + service_documentation: SafeUrlSchema.optional(), + revocation_endpoint: SafeUrlSchema.optional(), + revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(), + revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), + introspection_endpoint: z.string().optional(), + introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(), + introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), + code_challenge_methods_supported: z.array(z.string()).optional() +}); /** * OpenID Connect Discovery 1.0 Provider Metadata * see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata */ -export const OpenIdProviderMetadataSchema = z - .object({ - issuer: z.string(), - authorization_endpoint: SafeUrlSchema, - token_endpoint: SafeUrlSchema, - userinfo_endpoint: SafeUrlSchema.optional(), - jwks_uri: SafeUrlSchema, - registration_endpoint: SafeUrlSchema.optional(), - scopes_supported: z.array(z.string()).optional(), - response_types_supported: z.array(z.string()), - response_modes_supported: z.array(z.string()).optional(), - grant_types_supported: z.array(z.string()).optional(), - acr_values_supported: z.array(z.string()).optional(), - subject_types_supported: z.array(z.string()), - id_token_signing_alg_values_supported: z.array(z.string()), - id_token_encryption_alg_values_supported: z.array(z.string()).optional(), - id_token_encryption_enc_values_supported: z.array(z.string()).optional(), - userinfo_signing_alg_values_supported: z.array(z.string()).optional(), - userinfo_encryption_alg_values_supported: z.array(z.string()).optional(), - userinfo_encryption_enc_values_supported: z.array(z.string()).optional(), - request_object_signing_alg_values_supported: z.array(z.string()).optional(), - request_object_encryption_alg_values_supported: z.array(z.string()).optional(), - request_object_encryption_enc_values_supported: z.array(z.string()).optional(), - token_endpoint_auth_methods_supported: z.array(z.string()).optional(), - token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), - display_values_supported: z.array(z.string()).optional(), - claim_types_supported: z.array(z.string()).optional(), - claims_supported: z.array(z.string()).optional(), - service_documentation: z.string().optional(), - claims_locales_supported: z.array(z.string()).optional(), - ui_locales_supported: z.array(z.string()).optional(), - claims_parameter_supported: z.boolean().optional(), - request_parameter_supported: z.boolean().optional(), - request_uri_parameter_supported: z.boolean().optional(), - require_request_uri_registration: z.boolean().optional(), - op_policy_uri: SafeUrlSchema.optional(), - op_tos_uri: SafeUrlSchema.optional() - }) - .passthrough(); +export const OpenIdProviderMetadataSchema = z.looseObject({ + issuer: z.string(), + authorization_endpoint: SafeUrlSchema, + token_endpoint: SafeUrlSchema, + userinfo_endpoint: SafeUrlSchema.optional(), + jwks_uri: SafeUrlSchema, + registration_endpoint: SafeUrlSchema.optional(), + scopes_supported: z.array(z.string()).optional(), + response_types_supported: z.array(z.string()), + response_modes_supported: z.array(z.string()).optional(), + grant_types_supported: z.array(z.string()).optional(), + acr_values_supported: z.array(z.string()).optional(), + subject_types_supported: z.array(z.string()), + id_token_signing_alg_values_supported: z.array(z.string()), + id_token_encryption_alg_values_supported: z.array(z.string()).optional(), + id_token_encryption_enc_values_supported: z.array(z.string()).optional(), + userinfo_signing_alg_values_supported: z.array(z.string()).optional(), + userinfo_encryption_alg_values_supported: z.array(z.string()).optional(), + userinfo_encryption_enc_values_supported: z.array(z.string()).optional(), + request_object_signing_alg_values_supported: z.array(z.string()).optional(), + request_object_encryption_alg_values_supported: z.array(z.string()).optional(), + request_object_encryption_enc_values_supported: z.array(z.string()).optional(), + token_endpoint_auth_methods_supported: z.array(z.string()).optional(), + token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(), + display_values_supported: z.array(z.string()).optional(), + claim_types_supported: z.array(z.string()).optional(), + claims_supported: z.array(z.string()).optional(), + service_documentation: z.string().optional(), + claims_locales_supported: z.array(z.string()).optional(), + ui_locales_supported: z.array(z.string()).optional(), + claims_parameter_supported: z.boolean().optional(), + request_parameter_supported: z.boolean().optional(), + request_uri_parameter_supported: z.boolean().optional(), + require_request_uri_registration: z.boolean().optional(), + op_policy_uri: SafeUrlSchema.optional(), + op_tos_uri: SafeUrlSchema.optional() +}); /** * OpenID Connect Discovery metadata that may include OAuth 2.0 fields @@ -131,16 +126,14 @@ export const OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchem /** * OAuth 2.1 token response */ -export const OAuthTokensSchema = z - .object({ - access_token: z.string(), - id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect - token_type: z.string(), - expires_in: z.number().optional(), - scope: z.string().optional(), - refresh_token: z.string().optional() - }) - .strip(); +export const OAuthTokensSchema = z.object({ + access_token: z.string(), + id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect + token_type: z.string(), + expires_in: z.number().optional(), + scope: z.string().optional(), + refresh_token: z.string().optional() +}); /** * OAuth 2.1 error response @@ -159,38 +152,34 @@ export const OptionalSafeUrlSchema = SafeUrlSchema.optional().or(z.literal('').t /** * RFC 7591 OAuth 2.0 Dynamic Client Registration metadata */ -export const OAuthClientMetadataSchema = z - .object({ - redirect_uris: z.array(SafeUrlSchema), - token_endpoint_auth_method: z.string().optional(), - grant_types: z.array(z.string()).optional(), - response_types: z.array(z.string()).optional(), - client_name: z.string().optional(), - client_uri: SafeUrlSchema.optional(), - logo_uri: OptionalSafeUrlSchema, - scope: z.string().optional(), - contacts: z.array(z.string()).optional(), - tos_uri: OptionalSafeUrlSchema, - policy_uri: z.string().optional(), - jwks_uri: SafeUrlSchema.optional(), - jwks: z.any().optional(), - software_id: z.string().optional(), - software_version: z.string().optional(), - software_statement: z.string().optional() - }) - .strip(); +export const OAuthClientMetadataSchema = z.object({ + redirect_uris: z.array(SafeUrlSchema), + token_endpoint_auth_method: z.string().optional(), + grant_types: z.array(z.string()).optional(), + response_types: z.array(z.string()).optional(), + client_name: z.string().optional(), + client_uri: SafeUrlSchema.optional(), + logo_uri: OptionalSafeUrlSchema, + scope: z.string().optional(), + contacts: z.array(z.string()).optional(), + tos_uri: OptionalSafeUrlSchema, + policy_uri: z.string().optional(), + jwks_uri: SafeUrlSchema.optional(), + jwks: z.any().optional(), + software_id: z.string().optional(), + software_version: z.string().optional(), + software_statement: z.string().optional() +}); /** * RFC 7591 OAuth 2.0 Dynamic Client Registration client information */ -export const OAuthClientInformationSchema = z - .object({ - client_id: z.string(), - client_secret: z.string().optional(), - client_id_issued_at: z.number().optional(), - client_secret_expires_at: z.number().optional() - }) - .strip(); +export const OAuthClientInformationSchema = z.object({ + client_id: z.string(), + client_secret: z.string().optional(), + client_id_issued_at: z.number().optional(), + client_secret_expires_at: z.number().optional() +}); /** * RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata) @@ -200,22 +189,18 @@ export const OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge( /** * RFC 7591 OAuth 2.0 Dynamic Client Registration error response */ -export const OAuthClientRegistrationErrorSchema = z - .object({ - error: z.string(), - error_description: z.string().optional() - }) - .strip(); +export const OAuthClientRegistrationErrorSchema = z.object({ + error: z.string(), + error_description: z.string().optional() +}); /** * RFC 7009 OAuth 2.0 Token Revocation request */ -export const OAuthTokenRevocationRequestSchema = z - .object({ - token: z.string(), - token_type_hint: z.string().optional() - }) - .strip(); +export const OAuthTokenRevocationRequestSchema = z.object({ + token: z.string(), + token_type_hint: z.string().optional() +}); export type OAuthMetadata = z.infer; export type OpenIdProviderMetadata = z.infer; diff --git a/src/types.ts b/src/types.ts index e6d3fe46e..26ed661ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,73 +11,64 @@ export const JSONRPC_VERSION = '2.0'; /** * A progress token, used to associate progress notifications with the original request. */ -export const ProgressTokenSchema = z.union([z.string(), z.number().int()]); +export const ProgressTokenSchema = z.union([z.string(), z.int()]); /** * An opaque token used to represent a cursor for pagination. */ export const CursorSchema = z.string(); -const RequestMetaSchema = z - .object({ - /** - * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. - */ - progressToken: z.optional(ProgressTokenSchema) - }) - .passthrough(); +const RequestMetaSchema = z.looseObject({ + /** + * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + */ + progressToken: z.optional(ProgressTokenSchema) +}); -const BaseRequestParamsSchema = z - .object({ - _meta: z.optional(RequestMetaSchema) - }) - .passthrough(); +const BaseRequestParamsSchema = z.looseObject({ + _meta: z.optional(RequestMetaSchema) +}); export const RequestSchema = z.object({ method: z.string(), params: z.optional(BaseRequestParamsSchema) }); -const BaseNotificationParamsSchema = z - .object({ - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); +const BaseNotificationParamsSchema = z.looseObject({ + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); export const NotificationSchema = z.object({ method: z.string(), params: z.optional(BaseNotificationParamsSchema) }); -export const ResultSchema = z - .object({ - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); +export const ResultSchema = z.looseObject({ + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * A uniquely identifying ID for a request in JSON-RPC. */ -export const RequestIdSchema = z.union([z.string(), z.number().int()]); +export const RequestIdSchema = z.union([z.string(), z.int()]); /** * A request that expects a response. */ export const JSONRPCRequestSchema = z - .object({ + .strictObject({ jsonrpc: z.literal(JSONRPC_VERSION), id: RequestIdSchema }) - .merge(RequestSchema) - .strict(); + .extend(RequestSchema.shape); export const isJSONRPCRequest = (value: unknown): value is JSONRPCRequest => JSONRPCRequestSchema.safeParse(value).success; @@ -85,24 +76,21 @@ export const isJSONRPCRequest = (value: unknown): value is JSONRPCRequest => JSO * A notification which does not expect a response. */ export const JSONRPCNotificationSchema = z - .object({ + .strictObject({ jsonrpc: z.literal(JSONRPC_VERSION) }) - .merge(NotificationSchema) - .strict(); + .extend(NotificationSchema.shape); export const isJSONRPCNotification = (value: unknown): value is JSONRPCNotification => JSONRPCNotificationSchema.safeParse(value).success; /** * A successful (non-error) response to a request. */ -export const JSONRPCResponseSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - id: RequestIdSchema, - result: ResultSchema - }) - .strict(); +export const JSONRPCResponseSchema = z.strictObject({ + jsonrpc: z.literal(JSONRPC_VERSION), + id: RequestIdSchema, + result: ResultSchema +}); export const isJSONRPCResponse = (value: unknown): value is JSONRPCResponse => JSONRPCResponseSchema.safeParse(value).success; @@ -125,26 +113,24 @@ export enum ErrorCode { /** * A response to a request that indicates an error occurred. */ -export const JSONRPCErrorSchema = z - .object({ - jsonrpc: z.literal(JSONRPC_VERSION), - id: RequestIdSchema, - error: z.object({ - /** - * The error type that occurred. - */ - code: z.number().int(), - /** - * A short description of the error. The message SHOULD be limited to a concise single sentence. - */ - message: z.string(), - /** - * Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). - */ - data: z.optional(z.unknown()) - }) +export const JSONRPCErrorSchema = z.strictObject({ + jsonrpc: z.literal(JSONRPC_VERSION), + id: RequestIdSchema, + error: z.strictObject({ + /** + * The error type that occurred. + */ + code: z.int(), + /** + * A short description of the error. The message SHOULD be limited to a concise single sentence. + */ + message: z.string(), + /** + * Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + */ + data: z.optional(z.unknown()) }) - .strict(); +}); export const isJSONRPCError = (value: unknown): value is JSONRPCError => JSONRPCErrorSchema.safeParse(value).success; @@ -187,65 +173,59 @@ export const CancelledNotificationSchema = NotificationSchema.extend({ /** * Icon schema for use in tools, prompts, resources, and implementations. */ -export const IconSchema = z - .object({ - /** - * URL or data URI for the icon. - */ - src: z.string(), - /** - * Optional MIME type for the icon. - */ - mimeType: z.optional(z.string()), - /** - * Optional array of strings that specify sizes at which the icon can be used. - * Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. - * - * If not provided, the client should assume that the icon can be used at any size. - */ - sizes: z.optional(z.array(z.string())) - }) - .passthrough(); +export const IconSchema = z.looseObject({ + /** + * URL or data URI for the icon. + */ + src: z.string(), + /** + * Optional MIME type for the icon. + */ + mimeType: z.optional(z.string()), + /** + * Optional array of strings that specify sizes at which the icon can be used. + * Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + * + * If not provided, the client should assume that the icon can be used at any size. + */ + sizes: z.optional(z.array(z.string())) +}); /** * Base schema to add `icons` property. * */ -export const IconsSchema = z - .object({ - /** - * Optional set of sized icons that the client can display in a user interface. - * - * Clients that support rendering icons MUST support at least the following MIME types: - * - `image/png` - PNG images (safe, universal compatibility) - * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) - * - * Clients that support rendering icons SHOULD also support: - * - `image/svg+xml` - SVG images (scalable but requires security precautions) - * - `image/webp` - WebP images (modern, efficient format) - */ - icons: z.array(IconSchema).optional() - }) - .passthrough(); +export const IconsSchema = z.looseObject({ + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following MIME types: + * - `image/png` - PNG images (safe, universal compatibility) + * - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + * + * Clients that support rendering icons SHOULD also support: + * - `image/svg+xml` - SVG images (scalable but requires security precautions) + * - `image/webp` - WebP images (modern, efficient format) + */ + icons: z.array(IconSchema).optional() +}); /** * Base metadata interface for common properties across resources, tools, prompts, and implementations. */ -export const BaseMetadataSchema = z - .object({ - /** Intended for programmatic or logical use, but used as a display name in past specs or fallback */ - name: z.string(), - /** - * Intended for UI and end-user contexts — optimized to be human-readable and easily understood, - * even by those unfamiliar with domain-specific terminology. - * - * If not provided, the name should be used for display (except for Tool, - * where `annotations.title` should be given precedence over using `name`, - * if present). - */ - title: z.optional(z.string()) - }) - .passthrough(); +export const BaseMetadataSchema = z.looseObject({ + /** Intended for programmatic or logical use, but used as a display name in past specs or fallback */ + name: z.string(), + /** + * Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + * even by those unfamiliar with domain-specific terminology. + * + * If not provided, the name should be used for display (except for Tool, + * where `annotations.title` should be given precedence over using `name`, + * if present). + */ + title: z.optional(z.string()) +}); /* Initialization */ /** @@ -262,35 +242,31 @@ export const ImplementationSchema = BaseMetadataSchema.extend({ /** * Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. */ -export const ClientCapabilitiesSchema = z - .object({ - /** - * Experimental, non-standard capabilities that the client supports. - */ - experimental: z.optional(z.object({}).passthrough()), - /** - * Present if the client supports sampling from an LLM. - */ - sampling: z.optional(z.object({}).passthrough()), - /** - * Present if the client supports eliciting user input. - */ - elicitation: z.optional(z.object({}).passthrough()), - /** - * Present if the client supports listing roots. - */ - roots: z.optional( - z - .object({ - /** - * Whether the client supports issuing notifications for changes to the roots list. - */ - listChanged: z.optional(z.boolean()) - }) - .passthrough() - ) - }) - .passthrough(); +export const ClientCapabilitiesSchema = z.looseObject({ + /** + * Experimental, non-standard capabilities that the client supports. + */ + experimental: z.optional(z.looseObject({})), + /** + * Present if the client supports sampling from an LLM. + */ + sampling: z.optional(z.looseObject({})), + /** + * Present if the client supports eliciting user input. + */ + elicitation: z.optional(z.looseObject({})), + /** + * Present if the client supports listing roots. + */ + roots: z.optional( + z.looseObject({ + /** + * Whether the client supports issuing notifications for changes to the roots list. + */ + listChanged: z.optional(z.boolean()) + }) + ) +}); /** * This request is sent from the client to the server when it first connects, asking it to begin initialization. @@ -312,66 +288,58 @@ export const isInitializeRequest = (value: unknown): value is InitializeRequest /** * Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. */ -export const ServerCapabilitiesSchema = z - .object({ - /** - * Experimental, non-standard capabilities that the server supports. - */ - experimental: z.optional(z.object({}).passthrough()), - /** - * Present if the server supports sending log messages to the client. - */ - logging: z.optional(z.object({}).passthrough()), - /** - * Present if the server supports sending completions to the client. - */ - completions: z.optional(z.object({}).passthrough()), - /** - * Present if the server offers any prompt templates. - */ - prompts: z.optional( - z - .object({ - /** - * Whether this server supports issuing notifications for changes to the prompt list. - */ - listChanged: z.optional(z.boolean()) - }) - .passthrough() - ), - /** - * Present if the server offers any resources to read. - */ - resources: z.optional( - z - .object({ - /** - * Whether this server supports clients subscribing to resource updates. - */ - subscribe: z.optional(z.boolean()), - - /** - * Whether this server supports issuing notifications for changes to the resource list. - */ - listChanged: z.optional(z.boolean()) - }) - .passthrough() - ), - /** - * Present if the server offers any tools to call. - */ - tools: z.optional( - z - .object({ - /** - * Whether this server supports issuing notifications for changes to the tool list. - */ - listChanged: z.optional(z.boolean()) - }) - .passthrough() - ) - }) - .passthrough(); +export const ServerCapabilitiesSchema = z.looseObject({ + /** + * Experimental, non-standard capabilities that the server supports. + */ + experimental: z.optional(z.looseObject({})), + /** + * Present if the server supports sending log messages to the client. + */ + logging: z.optional(z.looseObject({})), + /** + * Present if the server supports sending completions to the client. + */ + completions: z.optional(z.looseObject({})), + /** + * Present if the server offers any prompt templates. + */ + prompts: z.optional( + z.looseObject({ + /** + * Whether this server supports issuing notifications for changes to the prompt list. + */ + listChanged: z.optional(z.boolean()) + }) + ), + /** + * Present if the server offers any resources to read. + */ + resources: z.optional( + z.looseObject({ + /** + * Whether this server supports clients subscribing to resource updates. + */ + subscribe: z.optional(z.boolean()), + + /** + * Whether this server supports issuing notifications for changes to the resource list. + */ + listChanged: z.optional(z.boolean()) + }) + ), + /** + * Present if the server offers any tools to call. + */ + tools: z.optional( + z.looseObject({ + /** + * Whether this server supports issuing notifications for changes to the tool list. + */ + listChanged: z.optional(z.boolean()) + }) + ) +}); /** * After receiving an initialize request from the client, the server sends this response. @@ -410,22 +378,20 @@ export const PingRequestSchema = RequestSchema.extend({ }); /* Progress notifications */ -export const ProgressSchema = z - .object({ - /** - * The progress thus far. This should increase every time progress is made, even if the total is unknown. - */ - progress: z.number(), - /** - * Total number of items to process (or total progress required), if known. - */ - total: z.optional(z.number()), - /** - * An optional message describing the current progress. - */ - message: z.optional(z.string()) - }) - .passthrough(); +export const ProgressSchema = z.looseObject({ + /** + * The progress thus far. This should increase every time progress is made, even if the total is unknown. + */ + progress: z.number(), + /** + * Total number of items to process (or total progress required), if known. + */ + total: z.optional(z.number()), + /** + * An optional message describing the current progress. + */ + message: z.optional(z.string()) +}); /** * An out-of-band notification used to inform the receiver of a progress update for a long-running request. @@ -463,23 +429,21 @@ export const PaginatedResultSchema = ResultSchema.extend({ /** * The contents of a specific resource or sub-resource. */ -export const ResourceContentsSchema = z - .object({ - /** - * The URI of this resource. - */ - uri: z.string(), - /** - * The MIME type of this resource, if known. - */ - mimeType: z.optional(z.string()), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); +export const ResourceContentsSchema = z.looseObject({ + /** + * The URI of this resource. + */ + uri: z.string(), + /** + * The MIME type of this resource, if known. + */ + mimeType: z.optional(z.string()), + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); export const TextResourceContentsSchema = ResourceContentsSchema.extend({ /** @@ -504,7 +468,9 @@ const Base64Schema = z.string().refine( return false; } }, - { message: 'Invalid Base64 string' } + { + error: 'Invalid Base64 string' + } ); export const BlobResourceContentsSchema = ResourceContentsSchema.extend({ @@ -539,7 +505,7 @@ export const ResourceSchema = BaseMetadataSchema.extend({ * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. */ - _meta: z.optional(z.object({}).passthrough()) + _meta: z.optional(z.looseObject({})) }).merge(IconsSchema); /** @@ -567,7 +533,7 @@ export const ResourceTemplateSchema = BaseMetadataSchema.extend({ * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. */ - _meta: z.optional(z.object({}).passthrough()) + _meta: z.optional(z.looseObject({})) }).merge(IconsSchema); /** @@ -668,22 +634,20 @@ export const ResourceUpdatedNotificationSchema = NotificationSchema.extend({ /** * Describes an argument that a prompt can accept. */ -export const PromptArgumentSchema = z - .object({ - /** - * The name of the argument. - */ - name: z.string(), - /** - * A human-readable description of the argument. - */ - description: z.optional(z.string()), - /** - * Whether this argument must be provided. - */ - required: z.optional(z.boolean()) - }) - .passthrough(); +export const PromptArgumentSchema = z.looseObject({ + /** + * The name of the argument. + */ + name: z.string(), + /** + * A human-readable description of the argument. + */ + description: z.optional(z.string()), + /** + * Whether this argument must be provided. + */ + required: z.optional(z.boolean()) +}); /** * A prompt or prompt template that the server offers. @@ -701,7 +665,7 @@ export const PromptSchema = BaseMetadataSchema.extend({ * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. */ - _meta: z.optional(z.object({}).passthrough()) + _meta: z.optional(z.looseObject({})) }).merge(IconsSchema); /** @@ -731,89 +695,81 @@ export const GetPromptRequestSchema = RequestSchema.extend({ /** * Arguments to use for templating the prompt. */ - arguments: z.optional(z.record(z.string())) + arguments: z.optional(z.record(z.string(), z.string())) }) }); /** * Text provided to or from an LLM. */ -export const TextContentSchema = z - .object({ - type: z.literal('text'), - /** - * The text content of the message. - */ - text: z.string(), +export const TextContentSchema = z.looseObject({ + type: z.literal('text'), + /** + * The text content of the message. + */ + text: z.string(), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * An image provided to or from an LLM. */ -export const ImageContentSchema = z - .object({ - type: z.literal('image'), - /** - * The base64-encoded image data. - */ - data: Base64Schema, - /** - * The MIME type of the image. Different providers may support different image types. - */ - mimeType: z.string(), +export const ImageContentSchema = z.looseObject({ + type: z.literal('image'), + /** + * The base64-encoded image data. + */ + data: Base64Schema, + /** + * The MIME type of the image. Different providers may support different image types. + */ + mimeType: z.string(), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * An Audio provided to or from an LLM. */ -export const AudioContentSchema = z - .object({ - type: z.literal('audio'), - /** - * The base64-encoded audio data. - */ - data: Base64Schema, - /** - * The MIME type of the audio. Different providers may support different audio types. - */ - mimeType: z.string(), +export const AudioContentSchema = z.looseObject({ + type: z.literal('audio'), + /** + * The base64-encoded audio data. + */ + data: Base64Schema, + /** + * The MIME type of the audio. Different providers may support different audio types. + */ + mimeType: z.string(), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * The contents of a resource, embedded into a prompt or tool call result. */ -export const EmbeddedResourceSchema = z - .object({ - type: z.literal('resource'), - resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), - /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. - */ - _meta: z.optional(z.object({}).passthrough()) - }) - .passthrough(); +export const EmbeddedResourceSchema = z.looseObject({ + type: z.literal('resource'), + resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * A resource that the server is capable of reading, included in a prompt or tool call result. @@ -838,12 +794,10 @@ export const ContentBlockSchema = z.union([ /** * Describes a message returned as part of a prompt. */ -export const PromptMessageSchema = z - .object({ - role: z.enum(['user', 'assistant']), - content: ContentBlockSchema - }) - .passthrough(); +export const PromptMessageSchema = z.looseObject({ + role: z.enum(['user', 'assistant']), + content: ContentBlockSchema +}); /** * The server's response to a prompts/get request from the client. @@ -874,51 +828,49 @@ export const PromptListChangedNotificationSchema = NotificationSchema.extend({ * Clients should never make tool use decisions based on ToolAnnotations * received from untrusted servers. */ -export const ToolAnnotationsSchema = z - .object({ - /** - * A human-readable title for the tool. - */ - title: z.optional(z.string()), +export const ToolAnnotationsSchema = z.looseObject({ + /** + * A human-readable title for the tool. + */ + title: z.optional(z.string()), - /** - * If true, the tool does not modify its environment. - * - * Default: false - */ - readOnlyHint: z.optional(z.boolean()), + /** + * If true, the tool does not modify its environment. + * + * Default: false + */ + readOnlyHint: z.optional(z.boolean()), - /** - * If true, the tool may perform destructive updates to its environment. - * If false, the tool performs only additive updates. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: true - */ - destructiveHint: z.optional(z.boolean()), + /** + * If true, the tool may perform destructive updates to its environment. + * If false, the tool performs only additive updates. + * + * (This property is meaningful only when `readOnlyHint == false`) + * + * Default: true + */ + destructiveHint: z.optional(z.boolean()), - /** - * If true, calling the tool repeatedly with the same arguments - * will have no additional effect on the its environment. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: false - */ - idempotentHint: z.optional(z.boolean()), + /** + * If true, calling the tool repeatedly with the same arguments + * will have no additional effect on the its environment. + * + * (This property is meaningful only when `readOnlyHint == false`) + * + * Default: false + */ + idempotentHint: z.optional(z.boolean()), - /** - * If true, this tool may interact with an "open world" of external - * entities. If false, the tool's domain of interaction is closed. - * For example, the world of a web search tool is open, whereas that - * of a memory tool is not. - * - * Default: true - */ - openWorldHint: z.optional(z.boolean()) - }) - .passthrough(); + /** + * If true, this tool may interact with an "open world" of external + * entities. If false, the tool's domain of interaction is closed. + * For example, the world of a web search tool is open, whereas that + * of a memory tool is not. + * + * Default: true + */ + openWorldHint: z.optional(z.boolean()) +}); /** * Definition for a tool the client can call. @@ -931,25 +883,21 @@ export const ToolSchema = BaseMetadataSchema.extend({ /** * A JSON Schema object defining the expected parameters for the tool. */ - inputSchema: z - .object({ - type: z.literal('object'), - properties: z.optional(z.object({}).passthrough()), - required: z.optional(z.array(z.string())) - }) - .passthrough(), + inputSchema: z.looseObject({ + type: z.literal('object'), + properties: z.optional(z.looseObject({})), + required: z.optional(z.array(z.string())) + }), /** * An optional JSON Schema object defining the structure of the tool's output returned in * the structuredContent field of a CallToolResult. */ outputSchema: z.optional( - z - .object({ - type: z.literal('object'), - properties: z.optional(z.object({}).passthrough()), - required: z.optional(z.array(z.string())) - }) - .passthrough() + z.looseObject({ + type: z.literal('object'), + properties: z.optional(z.looseObject({})), + required: z.optional(z.array(z.string())) + }) ), /** * Optional additional tool information. @@ -960,7 +908,7 @@ export const ToolSchema = BaseMetadataSchema.extend({ * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) * for notes on _meta usage. */ - _meta: z.optional(z.object({}).passthrough()) + _meta: z.optional(z.looseObject({})) }).merge(IconsSchema); /** @@ -987,14 +935,14 @@ export const CallToolResultSchema = ResultSchema.extend({ * If the Tool does not define an outputSchema, this field MUST be present in the result. * For backwards compatibility, this field is always present, but it may be empty. */ - content: z.array(ContentBlockSchema).default([]), + content: z.array(ContentBlockSchema).prefault([]), /** * An object containing structured tool output. * * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema. */ - structuredContent: z.object({}).passthrough().optional(), + structuredContent: z.looseObject({}).optional(), /** * Whether the tool call ended in an error. @@ -1029,7 +977,7 @@ export const CallToolRequestSchema = RequestSchema.extend({ method: z.literal('tools/call'), params: BaseRequestParamsSchema.extend({ name: z.string(), - arguments: z.optional(z.record(z.unknown())) + arguments: z.optional(z.record(z.string(), z.unknown())) }) }); @@ -1084,48 +1032,42 @@ export const LoggingMessageNotificationSchema = NotificationSchema.extend({ /** * Hints to use for model selection. */ -export const ModelHintSchema = z - .object({ - /** - * A hint for a model name. - */ - name: z.string().optional() - }) - .passthrough(); +export const ModelHintSchema = z.looseObject({ + /** + * A hint for a model name. + */ + name: z.string().optional() +}); /** * The server's preferences for model selection, requested of the client during sampling. */ -export const ModelPreferencesSchema = z - .object({ - /** - * Optional hints to use for model selection. - */ - hints: z.optional(z.array(ModelHintSchema)), - /** - * How much to prioritize cost when selecting a model. - */ - costPriority: z.optional(z.number().min(0).max(1)), - /** - * How much to prioritize sampling speed (latency) when selecting a model. - */ - speedPriority: z.optional(z.number().min(0).max(1)), - /** - * How much to prioritize intelligence and capabilities when selecting a model. - */ - intelligencePriority: z.optional(z.number().min(0).max(1)) - }) - .passthrough(); +export const ModelPreferencesSchema = z.looseObject({ + /** + * Optional hints to use for model selection. + */ + hints: z.optional(z.array(ModelHintSchema)), + /** + * How much to prioritize cost when selecting a model. + */ + costPriority: z.optional(z.number().min(0).max(1)), + /** + * How much to prioritize sampling speed (latency) when selecting a model. + */ + speedPriority: z.optional(z.number().min(0).max(1)), + /** + * How much to prioritize intelligence and capabilities when selecting a model. + */ + intelligencePriority: z.optional(z.number().min(0).max(1)) +}); /** * Describes a message issued to or received from an LLM API. */ -export const SamplingMessageSchema = z - .object({ - role: z.enum(['user', 'assistant']), - content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema]) - }) - .passthrough(); +export const SamplingMessageSchema = z.looseObject({ + role: z.enum(['user', 'assistant']), + content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema]) +}); /** * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. @@ -1146,12 +1088,12 @@ export const CreateMessageRequestSchema = RequestSchema.extend({ /** * The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. */ - maxTokens: z.number().int(), + maxTokens: z.int(), stopSequences: z.optional(z.array(z.string())), /** * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. */ - metadata: z.optional(z.object({}).passthrough()), + metadata: z.optional(z.looseObject({})), /** * The server's preferences for which model to select. */ @@ -1179,54 +1121,46 @@ export const CreateMessageResultSchema = ResultSchema.extend({ /** * Primitive schema definition for boolean fields. */ -export const BooleanSchemaSchema = z - .object({ - type: z.literal('boolean'), - title: z.optional(z.string()), - description: z.optional(z.string()), - default: z.optional(z.boolean()) - }) - .passthrough(); +export const BooleanSchemaSchema = z.looseObject({ + type: z.literal('boolean'), + title: z.optional(z.string()), + description: z.optional(z.string()), + default: z.optional(z.boolean()) +}); /** * Primitive schema definition for string fields. */ -export const StringSchemaSchema = z - .object({ - type: z.literal('string'), - title: z.optional(z.string()), - description: z.optional(z.string()), - minLength: z.optional(z.number()), - maxLength: z.optional(z.number()), - format: z.optional(z.enum(['email', 'uri', 'date', 'date-time'])) - }) - .passthrough(); +export const StringSchemaSchema = z.looseObject({ + type: z.literal('string'), + title: z.optional(z.string()), + description: z.optional(z.string()), + minLength: z.optional(z.number()), + maxLength: z.optional(z.number()), + format: z.optional(z.enum(['email', 'uri', 'date', 'date-time'])) +}); /** * Primitive schema definition for number fields. */ -export const NumberSchemaSchema = z - .object({ - type: z.enum(['number', 'integer']), - title: z.optional(z.string()), - description: z.optional(z.string()), - minimum: z.optional(z.number()), - maximum: z.optional(z.number()) - }) - .passthrough(); +export const NumberSchemaSchema = z.looseObject({ + type: z.enum(['number', 'integer']), + title: z.optional(z.string()), + description: z.optional(z.string()), + minimum: z.optional(z.number()), + maximum: z.optional(z.number()) +}); /** * Primitive schema definition for enum fields. */ -export const EnumSchemaSchema = z - .object({ - type: z.literal('string'), - title: z.optional(z.string()), - description: z.optional(z.string()), - enum: z.array(z.string()), - enumNames: z.optional(z.array(z.string())) - }) - .passthrough(); +export const EnumSchemaSchema = z.looseObject({ + type: z.literal('string'), + title: z.optional(z.string()), + description: z.optional(z.string()), + enum: z.array(z.string()), + enumNames: z.optional(z.array(z.string())) +}); /** * Union of all primitive schema definitions. @@ -1247,13 +1181,11 @@ export const ElicitRequestSchema = RequestSchema.extend({ /** * The schema for the requested user input. */ - requestedSchema: z - .object({ - type: z.literal('object'), - properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), - required: z.optional(z.array(z.string())) - }) - .passthrough() + requestedSchema: z.looseObject({ + type: z.literal('object'), + properties: z.record(z.string(), PrimitiveSchemaDefinitionSchema), + required: z.optional(z.array(z.string())) + }) }) }); @@ -1275,15 +1207,13 @@ export const ElicitResultSchema = ResultSchema.extend({ /** * A reference to a resource or resource template definition. */ -export const ResourceTemplateReferenceSchema = z - .object({ - type: z.literal('ref/resource'), - /** - * The URI or URI template of the resource. - */ - uri: z.string() - }) - .passthrough(); +export const ResourceTemplateReferenceSchema = z.looseObject({ + type: z.literal('ref/resource'), + /** + * The URI or URI template of the resource. + */ + uri: z.string() +}); /** * @deprecated Use ResourceTemplateReferenceSchema instead @@ -1293,15 +1223,13 @@ export const ResourceReferenceSchema = ResourceTemplateReferenceSchema; /** * Identifies a prompt. */ -export const PromptReferenceSchema = z - .object({ - type: z.literal('ref/prompt'), - /** - * The name of the prompt or prompt template - */ - name: z.string() - }) - .passthrough(); +export const PromptReferenceSchema = z.looseObject({ + type: z.literal('ref/prompt'), + /** + * The name of the prompt or prompt template + */ + name: z.string() +}); /** * A request from the client to the server, to ask for completion options. @@ -1313,18 +1241,16 @@ export const CompleteRequestSchema = RequestSchema.extend({ /** * The argument's information */ - argument: z - .object({ - /** - * The name of the argument - */ - name: z.string(), - /** - * The value of the argument to use for completion matching. - */ - value: z.string() - }) - .passthrough(), + argument: z.looseObject({ + /** + * The name of the argument + */ + name: z.string(), + /** + * The value of the argument to use for completion matching. + */ + value: z.string() + }), context: z.optional( z.object({ /** @@ -1340,46 +1266,42 @@ export const CompleteRequestSchema = RequestSchema.extend({ * The server's response to a completion/complete request */ export const CompleteResultSchema = ResultSchema.extend({ - completion: z - .object({ - /** - * An array of completion values. Must not exceed 100 items. - */ - values: z.array(z.string()).max(100), - /** - * The total number of completion options available. This can exceed the number of values actually sent in the response. - */ - total: z.optional(z.number().int()), - /** - * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. - */ - hasMore: z.optional(z.boolean()) - }) - .passthrough() -}); - -/* Roots */ -/** - * Represents a root directory or file that the server can operate on. - */ -export const RootSchema = z - .object({ + completion: z.looseObject({ /** - * The URI identifying the root. This *must* start with file:// for now. + * An array of completion values. Must not exceed 100 items. */ - uri: z.string().startsWith('file://'), + values: z.array(z.string()).max(100), /** - * An optional name for the root. + * The total number of completion options available. This can exceed the number of values actually sent in the response. */ - name: z.optional(z.string()), - + total: z.optional(z.int()), /** - * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - * for notes on _meta usage. + * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. */ - _meta: z.optional(z.object({}).passthrough()) + hasMore: z.optional(z.boolean()) }) - .passthrough(); +}); + +/* Roots */ +/** + * Represents a root directory or file that the server can operate on. + */ +export const RootSchema = z.looseObject({ + /** + * The URI identifying the root. This *must* start with file:// for now. + */ + uri: z.string().startsWith('file://'), + /** + * An optional name for the root. + */ + name: z.optional(z.string()), + + /** + * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) + * for notes on _meta usage. + */ + _meta: z.optional(z.looseObject({})) +}); /** * Sent from the server to request a list of root URIs from the client.