[BUG] MCP tool parameters using $ref enum types are serialized as null

Resolved 💬 5 comments Opened May 13, 2026 by somanshreddy Closed Jun 14, 2026

Title: [BUG] MCP tool parameters using $ref enum types are serialized as null

---

Preflight Checklist

  • [x] I have searched for existing issues
  • [x] This is a single bug report (not multiple issues)
  • [x] I am on the latest version of Claude Code

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

  1. 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}"
  1. 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
  }
}
  1. Ask Claude to call both tools with priority="high".
  1. 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

  • Reproducible on both Claude Code (CLI) and Claude.ai (web) with connected MCP servers.
  • The Pydantic $ref pattern is standard JSON Schema. MCP servers using Pydantic v2 str, Enum types may hit this when their tool schema exposes those enums through $ref definitions.
  • Python's fastmcp framework (used by many MCP servers) produces $ref schemas by default for enum types.
  • Related Cursor forum report with the same root cause: https://forum.cursor.com/t/mcp-server-cant-handle-enums-from-my-python-fastmcp-server/99092
  • Related MCP Inspector issue: https://github.com/modelcontextprotocol/inspector/issues/672
  • Server-side workaround: replace $ref enum types with inline Literal[...] in tool signatures at codegen time. This produces the working schema shape without changing runtime behavior.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗