Skip to content

Python: MCP Tool Schema Information Loss Bug #2747

@irarainey

Description

@irarainey

Environment

  • Agent Framework Version: 1.0.0b251209
  • MCP Version: 1.22.0
  • File: agent_framework/_mcp.py
  • Function: _get_input_model_from_mcp_tool() (lines 292-371)

Description

The _get_input_model_from_mcp_tool() function loses critical JSON Schema information when converting MCP tool schemas to Pydantic models. This results in the LLM receiving incomplete schema information that doesn't match what the MCP server expects, causing tool invocation failures.

Root Cause

Problem 1: Nested Objects Lose All Property Information (Line 332)

When an MCP tool parameter has type "object" with nested properties, the function returns just dict instead of recursively processing the nested schema:

case "object":
    return dict  # ❌ BUG: Discards all nested properties, required fields, and descriptions

What gets lost:

  • Nested object properties
  • Nested required fields
  • Nested field descriptions
  • Type information for nested fields

Problem 2: Arrays Lose Item Type Information (Line 330)

When an MCP tool parameter has type "array", the function returns just list without preserving the item type:

case "array":
    return list  # ❌ BUG: Discards item type (e.g., array of strings vs array of objects)

Partial mitigation (lines 350-354): The code attempts to preserve array schemas via json_schema_extra, but this doesn't create properly typed Pydantic fields (e.g., list[str]):

# Preserve array items schema if present
if prop_details.get("type") == "array" and "items" in prop_details:
    items_schema = prop_details["items"]
    if items_schema and items_schema != {}:
        field_kwargs["json_schema_extra"] = {"items": items_schema}

This stores the schema as metadata but doesn't affect the Python type annotation, so the field remains just list instead of list[str], list[int], etc.

Impact

Example MCP Tool Schema

@mcp.tool()
async def get_news(news_request: NewsRequest) -> NewsResponse:
    """Fetch news for companies."""
    pass
 
class NewsRequest(BaseModel):
    identifiers: list[str] = Field(description="List of company identifiers")

Original MCP Tool JSON Schema:

{
  "type": "object",
  "properties": {
    "news_request": {
      "type": "object",
      "properties": {
        "identifiers": {
          "type": "array",
          "items": {"type": "string"},
          "description": "List of company identifiers"
        }
      },
      "required": ["identifiers"]
    }
  },
  "required": ["news_request"]
}

What Agent Framework Creates

Pydantic Model (incorrect):

class get_news_input(BaseModel):
    news_request: dict  # ❌ Should be NewsRequestModel with typed fields

Regenerated Schema Sent to LLM:

{
  "type": "object",
  "properties": {
    "news_request": {
      "type": "object"
      // ❌ Missing: nested "properties", "required", item types
    }
  },
  "required": ["news_request"]
}

What Should Be Created

Correct Pydantic Model:

class NewsRequestModel(BaseModel):
    identifiers: list[str] = Field(description="List of company identifiers")
 
class get_news_input(BaseModel):
    news_request: NewsRequestModel

Correct Schema:

{
  "type": "object",
  "properties": {
    "news_request": {
      "type": "object",
      "properties": {
        "identifiers": {
          "type": "array",
          "items": {"type": "string"},
          "description": "List of company identifiers"
        }
      },
      "required": ["identifiers"]
    }
  },
  "required": ["news_request"]
}

Observed Failures

1. LLM Cannot Construct Valid Tool Calls

Without nested property information, the LLM doesn't know:

  • What fields exist inside nested objects
  • Which nested fields are required
  • What types nested arrays should contain

Result: LLM makes incorrect tool calls or omits required nested fields

2. MCP Server Validation Errors

When the LLM attempts to call the tool (even if structured correctly), the MCP server receives arguments that may be missing required nested fields:

1 validation error for get_news_input
news_request.identifiers
  Field required [type=missing, input_value={'news_request': {}}, ...]

3. Inconsistent Tool Behavior

Different MCP clients (Claude Desktop, VS Code Copilot, etc.) may interpret the incomplete schema differently, leading to inconsistent tool behavior across platforms.

Steps to Reproduce

Example 1: News Request Tool (Real-World Case)

  1. Create an MCP server with a tool that takes a Pydantic model parameter:

    import fastmcp
    from pydantic import BaseModel, Field
    mcp = fastmcp.FastMCP(name="News MCP Server")
    class NewsRequest(BaseModel):
        identifiers: list[str] = Field(description="List of company identifiers")
    @mcp.tool(description="Fetch news for companies")
    async def get_news(news_request: NewsRequest) -> dict:
        """Fetch news for the given company identifiers."""
        return {"news": f"News for {news_request.identifiers}"}
    # Run server
    mcp.run(transport="streamable-http", port=8003)
  2. Connect to this MCP server using Agent Framework:

    from agent_framework import MCPStreamableHTTPTool
    tool = MCPStreamableHTTPTool(
        name="news_mcp",
        url="http://localhost:8003/mcp"
    )
    await tool.connect()
    # Inspect the generated schema
    func = tool.functions[0]
    print("Function name:", func.name)
    print("Input model schema:", func.input_model.model_json_schema())
  3. Expected Schema:

    {
      "type": "object",
      "properties": {
        "news_request": {
          "type": "object",
          "properties": {
            "identifiers": {
              "type": "array",
              "items": {"type": "string"},
              "description": "List of company identifiers"
            }
          },
          "required": ["identifiers"]
        }
      },
      "required": ["news_request"]
    }
  4. Actual Schema (Broken):

    {
      "type": "object",
      "properties": {
        "news_request": {
          "type": "object"
          // ❌ Missing nested "properties", "required", and "items" schema
        }
      },
      "required": ["news_request"]
    }
  5. Observed Failure:
    When the LLM attempts to call this tool, it receives the incomplete schema and either:

    • Cannot determine what fields to include in news_request
    • Makes a call with an empty news_request: {}
    • Receives validation error from the MCP server:
      1 validation error for get_news_input
      news_request.identifiers
        Field required [type=missing, input_value={'news_request': {}}, ...]
      

Example 2: Generic Nested Structure

  1. Create an MCP server with nested object parameters:

    @mcp.tool()
    async def complex_tool(request: ComplexRequest) -> str:
        pass
    class ComplexRequest(BaseModel):
        nested: NestedModel
    class NestedModel(BaseModel):
        items: list[str]
  2. Connect and inspect as shown in Example 1

  3. Expected: Full nested schema with request.nested.items as array of string

  4. Actual: request.nested is just dict with no nested properties

Code Location

File: agent_framework/_mcp.py
Function: _get_input_model_from_mcp_tool(tool: types.Tool) -> type[BaseModel]
Lines: 292-371

Problematic code:

def resolve_type(prop_details: dict[str, Any]) -> type:
    json_type = prop_details.get("type", "string")
    match json_type:
        case "array":
            return list  # Line 330: Loses item type
        case "object":
            return dict  # Line 332: Loses nested properties

Expected Behavior

The function should:

  1. Recursively process nested objects - Create nested Pydantic models with create_model() for object types
  2. Preserve array item types - Use proper typing like list[str], list[int], list[NestedModel]
  3. Maintain all schema metadata - Descriptions, constraints, default values at all nesting levels

This would ensure the schema sent to the LLM matches the original MCP tool schema exactly.

Related Issues

  • Similar to how FastAPI/Pydantic handles nested models in request bodies
  • MCP protocol specification doesn't mandate schema flattening
  • This affects interoperability between different MCP client implementations

System Information

  • Python: 3.12
  • OS: Debian GNU/Linux 12 (bookworm) in DevContainer
  • agent-framework: 1.0.0b251209
  • agent-framework-core: 1.0.0b251209
  • mcp: 1.22.0
  • pydantic: 2.12.5

Metadata

Metadata

Labels

bugSomething isn't workingmodel context protocolIssue related to Model Context Protocolpatch requiredOnce this fix is merged a patch release is required to unblock a team or customerpythonv1.0Features being tracked for the version 1.0 GA

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions