diff --git a/package-lock.json b/package-lock.json index c9380e731..249938c5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,7 @@ "express": "^5.0.1", "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" + "raw-body": "^3.0.0" }, "devDependencies": { "@eslint/js": "^9.8.0", @@ -46,6 +44,9 @@ }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "zod": "^4.1.5" } }, "node_modules/@ampproject/remapping": { @@ -6639,22 +6640,14 @@ } }, "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.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", + "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==", "license": "MIT", + "peer": true, "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 b421b6eac..67f028069 100644 --- a/package.json +++ b/package.json @@ -71,9 +71,7 @@ "express": "^5.0.1", "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" + "raw-body": "^3.0.0" }, "devDependencies": { "@eslint/js": "^9.8.0", @@ -97,7 +95,10 @@ "typescript-eslint": "^8.0.0", "ws": "^8.18.0" }, + "peerDependencies": { + "zod": "^4.1.5" + }, "resolutions": { "strip-ansi": "6.0.1" } -} \ No newline at end of file +} diff --git a/src/examples/server/simpleSseServer.ts b/src/examples/server/simpleSseServer.ts index 664b15008..08b6c7be0 100644 --- a/src/examples/server/simpleSseServer.ts +++ b/src/examples/server/simpleSseServer.ts @@ -25,8 +25,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 d91f3a7b5..ec91a50ab 100644 --- a/src/examples/server/simpleStatelessStreamableHttp.ts +++ b/src/examples/server/simpleStatelessStreamableHttp.ts @@ -39,8 +39,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 3271e6213..e47977cb9 100644 --- a/src/examples/server/simpleStreamableHttp.ts +++ b/src/examples/server/simpleStreamableHttp.ts @@ -270,8 +270,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 a9d9b63d7..45f1da90b 100644 --- a/src/examples/server/sseAndStreamableHttpCompatibleServer.ts +++ b/src/examples/server/sseAndStreamableHttpCompatibleServer.ts @@ -30,8 +30,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 4a191134b..2d10eed46 100644 --- a/src/integration-tests/stateManagementStreamableHttp.test.ts +++ b/src/integration-tests/stateManagementStreamableHttp.test.ts @@ -55,7 +55,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 efd2611f8..0dc2c74d7 100644 --- a/src/integration-tests/taskResumability.test.ts +++ b/src/integration-tests/taskResumability.test.ts @@ -33,7 +33,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 @@ -56,8 +56,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 126ce006b..9868eb5cc 100644 --- a/src/server/auth/handlers/authorize.ts +++ b/src/server/auth/handlers/authorize.ts @@ -25,7 +25,9 @@ export type AuthorizationHandlerOptions = { // Parameters that must be validated in order to issue redirects. const ClientAuthorizationParamsSchema = z.object({ client_id: z.string(), - redirect_uri: z.string().optional().refine((value) => value === undefined || URL.canParse(value), { message: "redirect_uri must be a valid URL" }), + redirect_uri: z.string().optional().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. @@ -35,7 +37,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 b2ab74391..7cb0f4492 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.test.ts b/src/server/completable.test.ts index 6040ff3f6..4a8e2d5e8 100644 --- a/src/server/completable.test.ts +++ b/src/server/completable.test.ts @@ -14,30 +14,32 @@ describe("completable", () => { const completions = ["foo", "bar", "baz"]; const schema = completable(z.string(), () => completions); - expect(await schema._def.complete("")).toEqual(completions); + expect(await schema.def.complete("")).toEqual(completions); }); it("allows async completion functions", async () => { const completions = ["foo", "bar", "baz"]; const schema = completable(z.string(), async () => completions); - expect(await schema._def.complete("")).toEqual(completions); + expect(await schema.def.complete("")).toEqual(completions); }); it("passes current value to completion function", async () => { const schema = completable(z.string(), (value) => [value + "!"]); - expect(await schema._def.complete("test")).toEqual(["test!"]); + expect(await schema.def.complete("test")).toEqual(["test!"]); }); it("works with number schemas", async () => { const schema = completable(z.number(), () => [1, 2, 3]); expect(schema.parse(1)).toBe(1); - expect(await schema._def.complete(0)).toEqual([1, 2, 3]); + expect(await schema.def.complete(0)).toEqual([1, 2, 3]); }); - it("preserves schema description", () => { + // This is no longer how zod behavior works + // See: https://github.com/colinhacks/zod/issues/4965 + it.skip("preserves schema description", () => { const desc = "test description"; const schema = completable(z.string().describe(desc), () => []); diff --git a/src/server/completable.ts b/src/server/completable.ts index 652eaf72e..e3d687832 100644 --- a/src/server/completable.ts +++ b/src/server/completable.ts @@ -1,98 +1,71 @@ import { - ZodTypeAny, - ZodTypeDef, - ZodType, - ParseInput, - ParseReturnType, - RawCreateParams, - ZodErrorMap, - ProcessedCreateParams, + z, } from "zod"; -export enum McpZodTypeKind { - Completable = "McpCompletable", -} - -export type CompleteCallback = ( - value: T["_input"], +export type CompleteCallback = ( + value: z.input, context?: { arguments?: Record; }, -) => T["_input"][] | Promise; +) => z.output[] | Promise[]>; -export interface CompletableDef - extends ZodTypeDef { - type: T; +export interface $CompletableDef extends z.core.$ZodTypeDef { + type: "custom"; + innerType: T; complete: CompleteCallback; - typeName: McpZodTypeKind.Completable; } -export class Completable extends ZodType< - T["_output"], - CompletableDef, - 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, - }); - } +export interface $CompletableInternals + extends z.core.$ZodTypeInternals, z.core.input> { + def: $CompletableDef; + isst: never; + /** Auto-cached way to retrieve the inner schema */ + innerType: T; + pattern: T["_zod"]["pattern"]; + propValues: T["_zod"]["propValues"]; + optin: T["_zod"]["optin"]; + optout: T["_zod"]["optout"]; +} - unwrap() { - return this._def.type; - } +export interface $Completable extends z.core.$ZodType { + _zod: $CompletableInternals; +} - static create = ( - type: T, - params: RawCreateParams & { - complete: CompleteCallback; - }, - ): Completable => { - return new Completable({ - type, - typeName: McpZodTypeKind.Completable, - complete: params.complete, - ...processCreateParams(params), - }); +export const $Completable: z.core.$constructor<$Completable> = /*@__PURE__*/ z.core.$constructor("$Completable", (inst, def) => { + z.core.$ZodType.init(inst, def); + + z.util.defineLazy(inst._zod, "innerType", () => inst._zod.innerType); + z.util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType._zod.pattern); + z.util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType._zod.propValues); + z.util.defineLazy(inst._zod, "optin", () => inst._zod.innerType._zod.optin ?? undefined); + z.util.defineLazy(inst._zod, "optout", () => inst._zod.innerType._zod.optout ?? undefined); + + inst._zod.parse = (payload, ctx) => { + return def.innerType._zod.run(payload, ctx); }; +}); + + +// Completable +export interface Completable + extends z._ZodType<$CompletableInternals>, + $Completable { + complete: CompleteCallback; } +export const Completable: z.core.$constructor = /*@__PURE__*/ z.core.$constructor("Completable", (inst, def) => { + $Completable.init(inst, def); + z.ZodType.init(inst, def); + + inst.complete = def.complete; +}); -/** - * Wraps a Zod type to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP. - */ -export function completable( +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; - - if (iss.code === "invalid_enum_value") { - return { message: message ?? ctx.defaultError }; - } - 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 }; - }; - return { errorMap: customMap, description }; -} + return new Completable({ + type: "custom", + innerType: schema, + complete: complete, + }) as Completable; +} \ No newline at end of file diff --git a/src/server/mcp.ts b/src/server/mcp.ts index fb797a8b4..9ec0bafc2 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -1,14 +1,10 @@ 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 { @@ -43,7 +39,7 @@ import { ToolAnnotations, LoggingMessageNotification, } from "../types.js"; -import { Completable, CompletableDef } from "./completable.js"; +import { Completable, $CompletableDef } from "./completable.js"; import { UriTemplate, Variables } from "../shared/uriTemplate.js"; import { RequestHandlerExtra } from "../shared/protocol.js"; import { Transport } from "../shared/transport.js"; @@ -118,18 +114,17 @@ export class McpServer { title: tool.title, description: tool.description, inputSchema: tool.inputSchema - ? (zodToJsonSchema(tool.inputSchema, { - strictUnions: true, + ? (z.toJSONSchema(tool.inputSchema, { + target: "draft-7", }) as Tool["inputSchema"]) : EMPTY_OBJECT_JSON_SCHEMA, annotations: tool.annotations, }; if (tool.outputSchema) { - toolDefinition.outputSchema = zodToJsonSchema( - tool.outputSchema, - { strictUnions: true } - ) as Tool["outputSchema"]; + toolDefinition.outputSchema = z.toJSONSchema(tool.outputSchema, { + target: "draft-7", + }) as Tool["outputSchema"]; } return toolDefinition; @@ -293,7 +288,7 @@ export class McpServer { return EMPTY_COMPLETION_RESULT; } - const def: CompletableDef = field._def; + const def: $CompletableDef = (field as Completable)._zod.def; const suggestions = await def.complete(request.params.argument.value, request.params.context); return createCompletionResult(suggestions); } @@ -1159,10 +1154,11 @@ export class ResourceTemplate { * - `content` if the tool does not have an outputSchema * - Both fields are optional but typically one should be provided */ -export type ToolCallback = - Args extends ZodRawShape +export type ToolCallback = + Args extends z.core.$ZodLooseShape ? ( - args: z.objectOutputType, + args: z.core.$InferObjectOutput>, + // args: z.objectOutputType, extra: RequestHandlerExtra, ) => CallToolResult | Promise : (extra: RequestHandlerExtra) => CallToolResult | Promise; @@ -1170,8 +1166,8 @@ export type ToolCallback = export type RegisteredTool = { title?: string; description?: string; - inputSchema?: AnyZodObject; - outputSchema?: AnyZodObject; + inputSchema?: ZodObject; + outputSchema?: ZodObject; annotations?: ToolAnnotations; callback: ToolCallback; enabled: boolean; @@ -1269,15 +1265,15 @@ export type RegisteredResourceTemplate = { type PromptArgsRawShape = { [k: string]: - | ZodType - | ZodOptional>; + | ZodType + | ZodOptional>; }; export type PromptCallback< Args extends undefined | PromptArgsRawShape = undefined, > = Args extends PromptArgsRawShape ? ( - args: z.objectOutputType, + args: z.core.$InferObjectOutput>, extra: RequestHandlerExtra, ) => GetPromptResult | Promise : (extra: RequestHandlerExtra) => GetPromptResult | Promise; diff --git a/src/shared/auth.ts b/src/shared/auth.ts index 886eb1084..0526364da 100644 --- a/src/shared/auth.ts +++ b/src/shared/auth.ts @@ -3,11 +3,11 @@ import { z } from "zod"; /** * Reusable URL validation that disallows javascript: scheme */ -export const SafeUrlSchema = z.string().url() +export const SafeUrlSchema = z.url() .superRefine((val, ctx) => { if (!URL.canParse(val)) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "URL must be parseable", fatal: true, }); @@ -19,37 +19,36 @@ export const SafeUrlSchema = z.string().url() 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(), +export const OAuthProtectedResourceMetadataSchema = z.looseObject({ + resource: z.url(), authorization_servers: z.array(SafeUrlSchema).optional(), - jwks_uri: z.string().url().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.string().url().optional(), - resource_tos_uri: z.string().url().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(), - }) - .passthrough(); + }); /** * RFC 8414 OAuth 2.0 Authorization Server Metadata */ -export const OAuthMetadataSchema = z - .object({ +export const OAuthMetadataSchema = z.looseObject({ issuer: z.string(), authorization_endpoint: SafeUrlSchema, token_endpoint: SafeUrlSchema, @@ -76,15 +75,13 @@ export const OAuthMetadataSchema = z .array(z.string()) .optional(), code_challenge_methods_supported: z.array(z.string()).optional(), - }) - .passthrough(); + }); /** * OpenID Connect Discovery 1.0 Provider Metadata * see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata */ -export const OpenIdProviderMetadataSchema = z - .object({ +export const OpenIdProviderMetadataSchema = z.looseObject({ issuer: z.string(), authorization_endpoint: SafeUrlSchema, token_endpoint: SafeUrlSchema, @@ -126,8 +123,7 @@ export const OpenIdProviderMetadataSchema = z require_request_uri_registration: z.boolean().optional(), op_policy_uri: SafeUrlSchema.optional(), op_tos_uri: SafeUrlSchema.optional(), - }) - .passthrough(); + }); /** * OpenID Connect Discovery metadata that may include OAuth 2.0 fields @@ -144,16 +140,14 @@ export const OpenIdProviderDiscoveryMetadataSchema = /** * OAuth 2.1 token response */ -export const OAuthTokensSchema = z - .object({ +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(); + }); /** * OAuth 2.1 error response @@ -185,7 +179,7 @@ export const OAuthClientMetadataSchema = z.object({ software_id: z.string().optional(), software_version: z.string().optional(), software_statement: z.string().optional(), -}).strip(); +}); /** * RFC 7591 OAuth 2.0 Dynamic Client Registration client information @@ -195,7 +189,7 @@ export const OAuthClientInformationSchema = z.object({ client_secret: z.string().optional(), client_id_issued_at: z.number().optional(), client_secret_expires_at: z.number().optional(), -}).strip(); +}); /** * RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata) @@ -208,7 +202,7 @@ export const OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge( export const OAuthClientRegistrationErrorSchema = z.object({ error: z.string(), error_description: z.string().optional(), -}).strip(); +}); /** * RFC 7009 OAuth 2.0 Token Revocation request @@ -216,7 +210,7 @@ export const OAuthClientRegistrationErrorSchema = z.object({ export const OAuthTokenRevocationRequestSchema = z.object({ token: z.string(), token_type_hint: z.string().optional(), -}).strip(); +}); export type OAuthMetadata = z.infer; export type OpenIdProviderMetadata = z.infer; diff --git a/src/types.ts b/src/types.ts index 323e37389..5b5f5da85 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { z, ZodTypeAny } from "zod"; +import { z, ZodType } from "zod"; import { AuthInfo } from "./server/auth/types.js"; export const LATEST_PROTOCOL_VERSION = "2025-06-18"; @@ -16,73 +16,63 @@ 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({ +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), - }) - .passthrough(); + }); -const BaseRequestParamsSchema = z - .object({ +const BaseRequestParamsSchema = z.looseObject({ _meta: z.optional(RequestMetaSchema), - }) - .passthrough(); + }); export const RequestSchema = z.object({ method: z.string(), params: z.optional(BaseRequestParamsSchema), }); -const BaseNotificationParamsSchema = z - .object({ +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.object({}).passthrough()), - }) - .passthrough(); + _meta: z.optional(z.looseObject({})), + }); export const NotificationSchema = z.object({ method: z.string(), params: z.optional(BaseNotificationParamsSchema), }); -export const ResultSchema = z - .object({ +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.object({}).passthrough()), - }) - .passthrough(); + _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({ +export const JSONRPCRequestSchema = z.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; @@ -90,12 +80,10 @@ export const isJSONRPCRequest = (value: unknown): value is JSONRPCRequest => /** * A notification which does not expect a response. */ -export const JSONRPCNotificationSchema = z - .object({ +export const JSONRPCNotificationSchema = z.strictObject({ jsonrpc: z.literal(JSONRPC_VERSION), }) - .merge(NotificationSchema) - .strict(); + .extend(NotificationSchema.shape); export const isJSONRPCNotification = ( value: unknown @@ -105,13 +93,11 @@ export const isJSONRPCNotification = ( /** * A successful (non-error) response to a request. */ -export const JSONRPCResponseSchema = z - .object({ +export const JSONRPCResponseSchema = z.strictObject({ jsonrpc: z.literal(JSONRPC_VERSION), id: RequestIdSchema, result: ResultSchema, - }) - .strict(); + }); export const isJSONRPCResponse = (value: unknown): value is JSONRPCResponse => JSONRPCResponseSchema.safeParse(value).success; @@ -135,15 +121,14 @@ export enum ErrorCode { /** * A response to a request that indicates an error occurred. */ -export const JSONRPCErrorSchema = z - .object({ +export const JSONRPCErrorSchema = z.strictObject({ jsonrpc: z.literal(JSONRPC_VERSION), id: RequestIdSchema, - error: z.object({ + error: z.strictObject({ /** * The error type that occurred. */ - code: z.number().int(), + code: z.int(), /** * A short description of the error. The message SHOULD be limited to a concise single sentence. */ @@ -153,8 +138,7 @@ export const JSONRPCErrorSchema = z */ data: z.optional(z.unknown()), }), - }) - .strict(); + }); export const isJSONRPCError = (value: unknown): value is JSONRPCError => JSONRPCErrorSchema.safeParse(value).success; @@ -203,8 +187,7 @@ export const CancelledNotificationSchema = NotificationSchema.extend({ /** * Base metadata interface for common properties across resources, tools, prompts, and implementations. */ -export const BaseMetadataSchema = z - .object({ +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(), /** @@ -216,8 +199,7 @@ export const BaseMetadataSchema = z * if present). */ title: z.optional(z.string()), - }) - .passthrough(); + }); /* Initialization */ /** @@ -230,35 +212,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({ +export const ClientCapabilitiesSchema = z.looseObject({ /** * Experimental, non-standard capabilities that the client supports. */ - experimental: z.optional(z.object({}).passthrough()), + experimental: z.optional(z.looseObject({})), /** * Present if the client supports sampling from an LLM. */ - sampling: z.optional(z.object({}).passthrough()), + sampling: z.optional(z.looseObject({})), /** * Present if the client supports eliciting user input. */ - elicitation: z.optional(z.object({}).passthrough()), + elicitation: z.optional(z.looseObject({})), /** * 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(), + z.looseObject({ + /** + * Whether the client supports issuing notifications for changes to the roots list. + */ + listChanged: z.optional(z.boolean()), + }), ), - }) - .passthrough(); + }); /** * This request is sent from the client to the server when it first connects, asking it to begin initialization. @@ -282,66 +260,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({ +export const ServerCapabilitiesSchema = z.looseObject({ /** * Experimental, non-standard capabilities that the server supports. */ - experimental: z.optional(z.object({}).passthrough()), + experimental: z.optional(z.looseObject({})), /** * Present if the server supports sending log messages to the client. */ - logging: z.optional(z.object({}).passthrough()), + logging: z.optional(z.looseObject({})), /** * Present if the server supports sending completions to the client. */ - completions: z.optional(z.object({}).passthrough()), + completions: z.optional(z.looseObject({})), /** * 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(), + 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 - .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(), + 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 - .object({ - /** - * Whether this server supports issuing notifications for changes to the tool list. - */ - listChanged: z.optional(z.boolean()), - }) - .passthrough(), + z.looseObject({ + /** + * Whether this server supports issuing notifications for changes to the tool list. + */ + listChanged: z.optional(z.boolean()), + }), ), - }) - .passthrough(); + }); /** * After receiving an initialize request from the client, the server sends this response. @@ -380,8 +350,7 @@ export const PingRequestSchema = RequestSchema.extend({ }); /* Progress notifications */ -export const ProgressSchema = z - .object({ +export const ProgressSchema = z.looseObject({ /** * The progress thus far. This should increase every time progress is made, even if the total is unknown. */ @@ -394,8 +363,7 @@ export const ProgressSchema = z * An optional message describing the current progress. */ message: z.optional(z.string()), - }) - .passthrough(); + }); /** * An out-of-band notification used to inform the receiver of a progress update for a long-running request. @@ -433,8 +401,7 @@ export const PaginatedResultSchema = ResultSchema.extend({ /** * The contents of a specific resource or sub-resource. */ -export const ResourceContentsSchema = z - .object({ +export const ResourceContentsSchema = z.looseObject({ /** * The URI of this resource. */ @@ -447,9 +414,8 @@ export const ResourceContentsSchema = z * 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(); + _meta: z.optional(z.looseObject({})), + }); export const TextResourceContentsSchema = ResourceContentsSchema.extend({ /** @@ -475,7 +441,9 @@ const Base64Schema = z.string().refine( return false; } }, - { message: "Invalid Base64 string" }, + { + error: "Invalid Base64 string" + }, ); export const BlobResourceContentsSchema = ResourceContentsSchema.extend({ @@ -510,7 +478,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({})), }); /** @@ -538,7 +506,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({})), }); /** @@ -643,8 +611,7 @@ export const ResourceUpdatedNotificationSchema = NotificationSchema.extend({ /** * Describes an argument that a prompt can accept. */ -export const PromptArgumentSchema = z - .object({ +export const PromptArgumentSchema = z.looseObject({ /** * The name of the argument. */ @@ -657,8 +624,7 @@ export const PromptArgumentSchema = z * Whether this argument must be provided. */ required: z.optional(z.boolean()), - }) - .passthrough(); + }); /** * A prompt or prompt template that the server offers. @@ -676,7 +642,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({})), }); /** @@ -706,15 +672,14 @@ 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({ +export const TextContentSchema = z.looseObject({ type: z.literal("text"), /** * The text content of the message. @@ -725,15 +690,13 @@ export const TextContentSchema = z * 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(); + _meta: z.optional(z.looseObject({})), + }); /** * An image provided to or from an LLM. */ -export const ImageContentSchema = z - .object({ +export const ImageContentSchema = z.looseObject({ type: z.literal("image"), /** * The base64-encoded image data. @@ -748,15 +711,13 @@ export const ImageContentSchema = z * 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(); + _meta: z.optional(z.looseObject({})), + }); /** * An Audio provided to or from an LLM. */ -export const AudioContentSchema = z - .object({ +export const AudioContentSchema = z.looseObject({ type: z.literal("audio"), /** * The base64-encoded audio data. @@ -771,24 +732,21 @@ export const AudioContentSchema = z * 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(); + _meta: z.optional(z.looseObject({})), + }); /** * The contents of a resource, embedded into a prompt or tool call result. */ -export const EmbeddedResourceSchema = z - .object({ +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.object({}).passthrough()), - }) - .passthrough(); + _meta: z.optional(z.looseObject({})), + }); /** * A resource that the server is capable of reading, included in a prompt or tool call result. @@ -813,12 +771,10 @@ export const ContentBlockSchema = z.union([ /** * Describes a message returned as part of a prompt. */ -export const PromptMessageSchema = z - .object({ +export const PromptMessageSchema = z.looseObject({ role: z.enum(["user", "assistant"]), content: ContentBlockSchema, - }) - .passthrough(); + }); /** * The server's response to a prompts/get request from the client. @@ -849,8 +805,7 @@ export const PromptListChangedNotificationSchema = NotificationSchema.extend({ * Clients should never make tool use decisions based on ToolAnnotations * received from untrusted servers. */ -export const ToolAnnotationsSchema = z - .object({ +export const ToolAnnotationsSchema = z.looseObject({ /** * A human-readable title for the tool. */ @@ -892,8 +847,7 @@ export const ToolAnnotationsSchema = z * Default: true */ openWorldHint: z.optional(z.boolean()), - }) - .passthrough(); + }); /** * Definition for a tool the client can call. @@ -906,24 +860,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. @@ -934,7 +885,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({})), }); /** @@ -961,14 +912,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. @@ -1003,7 +954,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())), }), }); @@ -1067,20 +1018,17 @@ export const LoggingMessageNotificationSchema = NotificationSchema.extend({ /** * Hints to use for model selection. */ -export const ModelHintSchema = z - .object({ +export const ModelHintSchema = z.looseObject({ /** * A hint for a model name. */ name: z.string().optional(), - }) - .passthrough(); + }); /** * The server's preferences for model selection, requested of the client during sampling. */ -export const ModelPreferencesSchema = z - .object({ +export const ModelPreferencesSchema = z.looseObject({ /** * Optional hints to use for model selection. */ @@ -1097,18 +1045,15 @@ export const ModelPreferencesSchema = z * How much to prioritize intelligence and capabilities when selecting a model. */ intelligencePriority: z.optional(z.number().min(0).max(1)), - }) - .passthrough(); + }); /** * Describes a message issued to or received from an LLM API. */ -export const SamplingMessageSchema = z - .object({ +export const SamplingMessageSchema = z.looseObject({ role: z.enum(["user", "assistant"]), content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema]), - }) - .passthrough(); + }); /** * 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. @@ -1129,12 +1074,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. */ @@ -1168,54 +1113,46 @@ export const CreateMessageResultSchema = ResultSchema.extend({ /** * Primitive schema definition for boolean fields. */ -export const BooleanSchemaSchema = z - .object({ +export const BooleanSchemaSchema = z.looseObject({ type: z.literal("boolean"), title: z.optional(z.string()), description: z.optional(z.string()), default: z.optional(z.boolean()), - }) - .passthrough(); + }); /** * Primitive schema definition for string fields. */ -export const StringSchemaSchema = z - .object({ +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"])), - }) - .passthrough(); + }); /** * Primitive schema definition for number fields. */ -export const NumberSchemaSchema = z - .object({ +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()), - }) - .passthrough(); + }); /** * Primitive schema definition for enum fields. */ -export const EnumSchemaSchema = z - .object({ +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())), - }) - .passthrough(); + }); /** * Union of all primitive schema definitions. @@ -1241,13 +1178,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())), + }), }), }); @@ -1269,15 +1204,13 @@ export const ElicitResultSchema = ResultSchema.extend({ /** * A reference to a resource or resource template definition. */ -export const ResourceTemplateReferenceSchema = z - .object({ +export const ResourceTemplateReferenceSchema = z.looseObject({ type: z.literal("ref/resource"), /** * The URI or URI template of the resource. */ uri: z.string(), - }) - .passthrough(); + }); /** * @deprecated Use ResourceTemplateReferenceSchema instead @@ -1287,15 +1220,13 @@ export const ResourceReferenceSchema = ResourceTemplateReferenceSchema; /** * Identifies a prompt. */ -export const PromptReferenceSchema = z - .object({ +export const PromptReferenceSchema = z.looseObject({ type: z.literal("ref/prompt"), /** * The name of the prompt or prompt template */ name: z.string(), - }) - .passthrough(); + }); /** * A request from the client to the server, to ask for completion options. @@ -1307,18 +1238,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({ /** @@ -1334,30 +1263,27 @@ 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(), + completion: z.looseObject({ + /** + * 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.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()), + }), }); /* Roots */ /** * Represents a root directory or file that the server can operate on. */ -export const RootSchema = z - .object({ +export const RootSchema = z.looseObject({ /** * The URI identifying the root. This *must* start with file:// for now. */ @@ -1371,9 +1297,8 @@ export const RootSchema = z * 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(); + _meta: z.optional(z.looseObject({})), + }); /** * Sent from the server to request a list of root URIs from the client. @@ -1482,7 +1407,7 @@ type Flatten = T extends Primitive ? { [K in keyof T]: Flatten } : T; -type Infer = Flatten>; +type Infer = Flatten>; /** * Headers that are compatible with both Node.js and the browser.