Title: [BUG] MCP tool parameters using $ref enum types are serialized as null
Preflight Checklist
What's Wrong?
When an MCP tool parameter's JSON Schema uses a $ref pointer to an enum definition in $defs, Claude serializes the parameter value as null regardless of what the model intends to send. The model correctly identifies the value to send (visible in its reasoning), but the serialized tool call payload contains null.
Inline enum values (without $ref) work correctly.
What Should Happen?
Claude should resolve $ref pointers in $defs and serialize the intended enum value. Both schema shapes represent the same constraint and should produce the same behavior.
Steps to Reproduce
- Create an MCP server with two tools. One uses an inline enum, the other uses a
$ref enum:
from fastmcp import FastMCP
from enum import Enum
from typing import Optional, Literal
from pydantic import Field
mcp = FastMCP("test-server")
class Priority(str, Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
# Tool A: inline Literal enum (WORKS)
@mcp.tool()
def create_task_inline(
title: str = Field(description="Task title"),
priority: Optional[Literal["high", "medium", "low"]] = Field(
default=None, description="Task priority"
),
) -> str:
return f"Created task: title={title}, priority={priority}"
# Tool B: $ref enum via Pydantic class (BROKEN)
@mcp.tool()
def create_task_ref(
title: str = Field(description="Task title"),
priority: Optional[Priority] = Field(
default=None, description="Task priority"
),
) -> str:
return f"Created task: title={title}, priority={priority}"
- The two tools produce different JSON Schema shapes:
Tool A (inline, works):
{
"priority": {
"anyOf": [
{"enum": ["high", "medium", "low"], "type": "string"},
{"type": "null"}
],
"default": null
}
}
Tool B ($ref, broken):
{
"$defs": {
"Priority": {"enum": ["high", "medium", "low"], "type": "string"}
},
"priority": {
"anyOf": [
{"$ref": "#/$defs/Priority"},
{"type": "null"}
],
"default": null
}
}
-
Ask Claude to call both tools with priority="high".
-
Tool A receives priority="high". Tool B receives priority=null.
Additional patterns tested
The bug also affects non-nullable $ref enums with defaults:
{
"$defs": {
"OutputFormat": {"enum": ["mp4", "webm"], "type": "string"}
},
"output_format": {
"$ref": "#/$defs/OutputFormat",
"default": "mp4"
}
}
When the model sets output_format="webm", the server receives null. Depending on server validation/normalization, this either fails validation or is treated as omitted and falls back to the default "mp4".
Test matrix
| Parameter type |
Schema shape |
Value sent |
Value received |
Optional[Literal["high", "medium", "low"]] |
inline enum |
"high" |
"high" |
Optional[Priority] (str Enum class) |
$ref to $defs |
"high" |
null |
str |
{"type": "string"} |
"test" |
"test" |
Optional[str] |
anyOf[string, null] |
"test" |
"test" |
bool |
{"type": "boolean"} |
true |
true |
int |
{"type": "integer"} |
3 |
3 |
Only $ref enum parameters are affected. All primitive types and inline enums work.
Error Messages/Logs
No error is raised. The value is silently sent as null, making the bug difficult to detect. If the server has null-stripping logic or default fallbacks, the user gets wrong output with no indication of failure.
Is this a regression?
Unknown
Claude Code Version
1.0.33
Platform
Anthropic API
Operating System
Linux
Terminal/Shell
zsh
Additional Information
Title:
[BUG] MCP tool parameters using $ref enum types are serialized as nullPreflight Checklist
What's Wrong?
When an MCP tool parameter's JSON Schema uses a
$refpointer to an enum definition in$defs, Claude serializes the parameter value asnullregardless of what the model intends to send. The model correctly identifies the value to send (visible in its reasoning), but the serialized tool call payload containsnull.Inline enum values (without
$ref) work correctly.What Should Happen?
Claude should resolve
$refpointers in$defsand serialize the intended enum value. Both schema shapes represent the same constraint and should produce the same behavior.Steps to Reproduce
$refenum:Tool A (inline, works):
{ "priority": { "anyOf": [ {"enum": ["high", "medium", "low"], "type": "string"}, {"type": "null"} ], "default": null } }Tool B (
$ref, broken):{ "$defs": { "Priority": {"enum": ["high", "medium", "low"], "type": "string"} }, "priority": { "anyOf": [ {"$ref": "#/$defs/Priority"}, {"type": "null"} ], "default": null } }Ask Claude to call both tools with
priority="high".Tool A receives
priority="high". Tool B receivespriority=null.Additional patterns tested
The bug also affects non-nullable
$refenums with defaults:{ "$defs": { "OutputFormat": {"enum": ["mp4", "webm"], "type": "string"} }, "output_format": { "$ref": "#/$defs/OutputFormat", "default": "mp4" } }When the model sets
output_format="webm", the server receivesnull. Depending on server validation/normalization, this either fails validation or is treated as omitted and falls back to the default"mp4".Test matrix
Optional[Literal["high", "medium", "low"]]enum"high""high"Optional[Priority](str Enum class)$refto$defs"high"nullstr{"type": "string"}"test""test"Optional[str]anyOf[string, null]"test""test"bool{"type": "boolean"}truetrueint{"type": "integer"}33Only
$refenum parameters are affected. All primitive types and inline enums work.Error Messages/Logs
No error is raised. The value is silently sent as
null, making the bug difficult to detect. If the server has null-stripping logic or default fallbacks, the user gets wrong output with no indication of failure.Is this a regression?
Unknown
Claude Code Version
1.0.33
Platform
Anthropic API
Operating System
Linux
Terminal/Shell
zsh
Additional Information
$refpattern is standard JSON Schema. MCP servers using Pydantic v2str, Enumtypes may hit this when their tool schema exposes those enums through$refdefinitions.fastmcpframework (used by many MCP servers) produces$refschemas by default for enum types.$refenum types with inlineLiteral[...]in tool signatures at codegen time. This produces the working schema shape without changing runtime behavior.