-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
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 descriptionsWhat 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 fieldsRegenerated 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: NewsRequestModelCorrect 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)
-
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)
-
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())
-
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"] } -
Actual Schema (Broken):
{ "type": "object", "properties": { "news_request": { "type": "object" // ❌ Missing nested "properties", "required", and "items" schema } }, "required": ["news_request"] } -
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': {}}, ...]
- Cannot determine what fields to include in
Example 2: Generic Nested Structure
-
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]
-
Connect and inspect as shown in Example 1
-
Expected: Full nested schema with
request.nested.itemsasarrayofstring -
Actual:
request.nestedis justdictwith 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 propertiesExpected Behavior
The function should:
- Recursively process nested objects - Create nested Pydantic models with
create_model()for object types - Preserve array item types - Use proper typing like
list[str],list[int],list[NestedModel] - 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
Assignees
Labels
Type
Projects
Status