Bug: Optional params emit {"not":{}} in tool schemas, breaking strict LLM validators
Severity: High — renders the MCP server unusable with affected models
Summary
The workers-bindings MCP server returns tool input schemas in which every optional parameter is encoded as {"not":{}} inside an anyOf:
"primary_location_hint": {
"anyOf": [
{ "not": {} },
{ "type": "string", "enum": ["wnam","enam","weur","eeur","apac","oc"] }
]
}
Several LLM providers reject the not keyword outright and fail the entire request:
- Kimi / Moonshot:
JSON Schema not supported: could not understand the instance {'not': {}}
- Google Gemini: rejects
anyOf/not tool schemas
- OpenAI strict mode / Fireworks: same class of rejection
Because the failure aborts the whole request, enabling this MCP server makes the agent unusable with these models.
Affected tools: d1_database_create, d1_database_query, hyperdrive_configs_list, hyperdrive_config_edit, and 14 others. A tools/list against the live server shows 18 distinct {"not":{}} instances.
Root Cause
Tool params use Zod .optional() / .optional().nullable(). The MCP SDK (@modelcontextprotocol/sdk@1.20.2) converts schemas with zod-to-json-schema, which renders optionals as:
return { anyOf: [ { not: parseAnyDef(refs) }, innerSchema ] }
// parseAnyDef(refs) === {} -> { not: {} }
strictUnions: true does not remove this — it only filters catch-all union members, not the not: {} arm.
Reference: zod-to-json-schema parseOptionalDef (https://github.com/StefanTerdell/zod-to-json-schema/blob/master/src/parsers/optional.ts)
Proposed Fix
Post-process each tool's generated JSON Schema before registration — recursively, for any anyOf containing a {"not":{}} member, drop that member; if only one branch remains, inline it. The field is already absent from required, so semantics are preserved.
function stripNotEmpty(node: unknown): unknown {
if (Array.isArray(node)) return node.map(stripNotEmpty)
if (node && typeof node === 'object') {
const obj = node as Record<string, unknown>
if (Array.isArray(obj.anyOf)) {
const kept = obj.anyOf.filter(
(m) => !(m && typeof m === 'object'
&& 'not' in m
&& Object.keys((m as any).not ?? {}).length === 0
&& Object.keys(m).length === 1),
)
if (kept.length === 1) return stripNotEmpty(kept[0])
obj.anyOf = kept.map(stripNotEmpty)
}
for (const k of Object.keys(obj)) if (k !== 'anyOf') obj[k] = stripNotEmpty(obj[k])
}
return node
}
This is a minimal, low-risk change that unblocks affected users immediately. Long-term, migrating to Zod v4 native z.toJSONSchema() would avoid the issue entirely.
References
Bug: Optional params emit {"not":{}} in tool schemas, breaking strict LLM validators
Severity: High — renders the MCP server unusable with affected models
Summary
The
workers-bindingsMCP server returns tool input schemas in which every optional parameter is encoded as{"not":{}}inside ananyOf:Several LLM providers reject the
notkeyword outright and fail the entire request:JSON Schema not supported: could not understand the instance {'not': {}}anyOf/nottool schemasBecause the failure aborts the whole request, enabling this MCP server makes the agent unusable with these models.
Affected tools:
d1_database_create,d1_database_query,hyperdrive_configs_list,hyperdrive_config_edit, and 14 others. Atools/listagainst the live server shows 18 distinct{"not":{}}instances.Root Cause
Tool params use Zod
.optional()/.optional().nullable(). The MCP SDK (@modelcontextprotocol/sdk@1.20.2) converts schemas withzod-to-json-schema, which renders optionals as:strictUnions: truedoes not remove this — it only filters catch-all union members, not thenot: {}arm.Reference:
zod-to-json-schemaparseOptionalDef(https://github.com/StefanTerdell/zod-to-json-schema/blob/master/src/parsers/optional.ts)Proposed Fix
Post-process each tool's generated JSON Schema before registration — recursively, for any
anyOfcontaining a{"not":{}}member, drop that member; if only one branch remains, inline it. The field is already absent fromrequired, so semantics are preserved.This is a minimal, low-risk change that unblocks affected users immediately. Long-term, migrating to Zod v4 native
z.toJSONSchema()would avoid the issue entirely.References