MCP JSON Schema: additionalProperties `{}` normalized to `false`, breaking z.record() fields
Description
When Claude Code loads tools from an MCP server, it normalizes JSON Schema "additionalProperties": {} (empty schema = accept everything) to "additionalProperties": false (reject everything). These mean opposite things in JSON Schema.
Reproduction
- Register an MCP tool with a
z.record(z.string(), z.any())field:
server.tool("my_tool", "description", {
goal: z.string(),
input: z.record(z.string(), z.any()).describe("Key-value pairs"),
}, handler);
- The MCP SDK correctly generates JSON Schema:
"input": { "type": "object", "additionalProperties": {} }
- But Claude Code presents this to the LLM as:
"input": { "type": "object", "additionalProperties": false, "properties": {} }
- The LLM sees "no properties, no additional properties" and never populates the field — it sends
{}or omits it entirely.
Expected behavior
"additionalProperties": {} should be preserved as-is (or normalized to "additionalProperties": true, which is semantically equivalent). It should NOT be converted to false.
JSON Schema spec reference
Per JSON Schema draft-07:
"additionalProperties": {}— empty schema, matches any value (accept all additional properties)"additionalProperties": true— equivalent to empty schema"additionalProperties": false— reject all additional properties
Impact
Any MCP server using z.record() for dynamic key-value inputs (common for form data, configuration objects, metadata) has those fields silently broken. The LLM can see the field description but the schema tells it the object accepts nothing.
Workaround
Using z.object({}).passthrough() instead of z.record() produces "additionalProperties": true (explicit boolean) which Claude Code preserves correctly.
Environment
- Claude Code version: latest as of 2026-07-17
- MCP SDK:
@modelcontextprotocol/sdk@1.29.0 zod-to-json-schema@3.25.1
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗