Skip to content

Recursive tool return type publishes an outputSchema with no root type, failing tools/list on 2025-11-25 sessions #3337

Description

@maxisbey

Release line

2.x (main), observed at b2025ab and on #3331.

Bug description

When an MCPServer tool's return type is self-referential, pydantic emits the output schema as {"$defs": {...}, "$ref": "#/$defs/Node"} with no root "type": "object". That's fine on 2026-07-28 sessions, but the 2025-11-25 Tool.outputSchema shape requires type: "object" at the root, so serializing the tools/list result for a legacy-negotiated client fails validation and the client gets an error for the entire listing, not just that tool.

On main this affects recursive BaseModel return types. #3331 hands TypedDict returns to pydantic natively, so recursive TypedDicts join them (previously the hand-built mirror model happened to inline the root).

Steps to reproduce

import anyio
from pydantic import BaseModel
from mcp import Client
from mcp.server.mcpserver import MCPServer

class Node(BaseModel):
    name: str
    children: list["Node"] = []

mcp = MCPServer("rec")

@mcp.tool()
def tree() -> Node:
    return Node(name="root")

@mcp.tool()
def other() -> int:
    return 1

async def main():
    async with Client(mcp) as c:                 # 2026-07-28
        print(sorted((await c.list_tools()).tools[1].output_schema))   # ['$defs', '$ref']
    async with Client(mcp, mode="legacy") as c:  # 2025-11-25
        await c.list_tools()                     # raises; server logs "handler for 'tools/list' returned an invalid result"

anyio.run(main)

Server log on the legacy session:

ValidationError: 1 validation error for ListToolsResult
tools.0.outputSchema.type
  Field required [type=missing, input_value={'$defs': {'Node': {...}}, '$ref': '#/$defs/Node'}, input_type=dict]

Expected behaviour

tools/list succeeds on both protocol versions; the recursive tool's schema has an object root (e.g. inline the root $ref, or wrap it as {"type": "object", "allOf": [{"$ref": ...}], "$defs": ...}).

Separately, it seems worth deciding whether one tool's unrepresentable schema should fail the whole listing on a legacy session or just drop/degrade that tool.

AI Disclaimer

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingspec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionv1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions