From f1c3238e296a635e2417c4c62fe3129b0ab66247 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Thu, 20 Nov 2025 17:54:21 +0000 Subject: [PATCH] fix: restore extensibility for ToolAnnotations schema The ToolAnnotations schema should allow additional properties per the MCP specification. The JSON Schema at: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json does NOT include "additionalProperties": false for ToolAnnotations, which means additional properties are allowed by default per JSON Schema semantics. This was accidentally removed in commit f59996a (SEP-1319) when .passthrough() was dropped during schema cleanup. This broke consumers like Cloudflare's x402 integration that rely on extending annotations with custom fields (e.g., paymentHint, paymentPriceUSD). Using .catchall(z.unknown()) rather than .passthrough() for consistency with other schemas per commit 913ca2a. --- src/types.ts | 80 +++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/types.ts b/src/types.ts index 64153094d..9828ff039 100644 --- a/src/types.ts +++ b/src/types.ts @@ -944,49 +944,51 @@ 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.string().optional(), +export const ToolAnnotationsSchema = z + .object({ + /** + * A human-readable title for the tool. + */ + title: z.string().optional(), - /** - * If true, the tool does not modify its environment. - * - * Default: false - */ - readOnlyHint: z.boolean().optional(), + /** + * If true, the tool does not modify its environment. + * + * Default: false + */ + readOnlyHint: z.boolean().optional(), - /** - * 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.boolean().optional(), + /** + * 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.boolean().optional(), - /** - * 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.boolean().optional(), + /** + * 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.boolean().optional(), - /** - * 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.boolean().optional() -}); + /** + * 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.boolean().optional() + }) + .catchall(z.unknown()); /** * Definition for a tool the client can call.