Skip to content

[Bug]: MCP Tool outputSchema Field is Stripped During Discovery #1258

@crivetimihai

Description

@crivetimihai

Bug Description

ContextForge strips the outputSchema field from MCP tools during gateway discovery, breaking structured content support and MCP spec compliance.

MCP Specification Context

Per the MCP Tools Specification (2025-06-18):

Tools may also provide an output schema for validation of structured results. If an output schema is provided:

  • Servers MUST provide structured results that conform to this schema.
  • Clients SHOULD validate structured results against this schema.

The outputSchema field is part of the official Tool definition:

interface Tool {
  name: string;
  description?: string;
  inputSchema: JSONSchema;
  outputSchema?: JSONSchema;  // ← This is being stripped
  annotations?: ToolAnnotations;
}

How FastMCP Uses outputSchema

Modern MCP servers built with FastMCP automatically generate outputSchema from function return type annotations:

@server.tool()
def get_weather_data(location: str) -> WeatherData:
    """Returns structured weather data."""
    return WeatherData(temperature=22.5, conditions="Partly cloudy", humidity=65)

FastMCP generates:

{
  "name": "get_weather_data",
  "inputSchema": { "type": "object", "properties": { "location": { "type": "string" } } },
  "outputSchema": {
    "type": "object",
    "properties": {
      "temperature": { "type": "number" },
      "conditions": { "type": "string" },
      "humidity": { "type": "number" }
    },
    "required": ["temperature", "conditions", "humidity"]
  }
}

The tool then returns BOTH unstructured and structured content:

{
  "content": [{ "type": "text", "text": "{\"temperature\": 22.5, ...}" }],
  "structuredContent": { "temperature": 22.5, "conditions": "Partly cloudy", "humidity": 65 }
}

The Bug in ContextForge

Location: mcpgateway/services/gateway_service.py:2866-2868

response = await session.list_tools()
tools = response.tools
tools = [tool.model_dump(by_alias=True, exclude_none=True) for tool in tools]  # Has outputSchema ✅
tools = [ToolCreate.model_validate(tool) for tool in tools]  # Strips outputSchema ❌

Root Cause

The ToolCreate, ToolUpdate, and ToolRead schemas in mcpgateway/schemas.py do NOT include an output_schema field:

class ToolCreate(BaseModel):
    name: str
    description: Optional[str]
    input_schema: Optional[Dict[str, Any]] = Field(..., alias="inputSchema")
    # ❌ NO output_schema field
    annotations: Optional[Dict[str, Any]]

When ToolCreate.model_validate(tool) is called, Pydantic silently drops the outputSchema field because it's not defined in the schema.

Similarly, the database Tool model has no output_schema column to store this data.

Impact

  1. Loss of Type Information: Clients and LLMs cannot understand what structured data format tools return
  2. No Response Validation: Cannot validate tool responses against expected schemas
  3. Breaks MCP Spec Compliance: Tools advertising outputSchema lose that metadata when proxied through ContextForge
  4. Poor Developer Experience: No autocomplete, type hints, or documentation for tool outputs
  5. Breaks Structured Workflows: Modern AI agents rely on typed tool outputs for reliable automation

Evidence from MCP SDK

The official MCP Python SDK types show outputSchema is a standard field:

# From mcp/types.py
class Tool(BaseMetadata):
    """Definition for a tool the client can call."""
    description: str | None = None
    inputSchema: dict[str, Any]
    outputSchema: dict[str, Any] | None = None  # ← Official field
    icons: list[Icon] | None = None
    annotations: ToolAnnotations | None = None

Expected Behavior

ContextForge should:

  1. Preserve outputSchema when discovering tools from MCP servers
  2. Store outputSchema in the database alongside input_schema
  3. Return outputSchema in API responses (/tools, /servers/{id}/tools, etc.)
  4. Pass through structuredContent from tool invocations
  5. Optionally validate structuredContent against outputSchema (per spec: "Clients SHOULD validate")

Proposed Solution

Add output_schema field (with alias outputSchema) to:

  1. Database Model (mcpgateway/db.py):

    class Tool(Base):
        # ...
        input_schema = Column(JSON, nullable=False)
        output_schema = Column(JSON, nullable=True)  # ← Add this
  2. Pydantic Schemas (mcpgateway/schemas.py):

    class ToolCreate(BaseModel):
        input_schema: Dict[str, Any] = Field(..., alias="inputSchema")
        output_schema: Optional[Dict[str, Any]] = Field(None, alias="outputSchema")  # ← Add
    
    class ToolRead(BaseModel):
        input_schema: Dict[str, Any]
        output_schema: Optional[Dict[str, Any]]  # ← Add
    
    class ToolUpdate(BaseModel):
        input_schema: Optional[Dict[str, Any]]
        output_schema: Optional[Dict[str, Any]]  # ← Add
  3. Database Migration (Alembic):

    ALTER TABLE tools ADD COLUMN output_schema JSON NULL;

Reproduction Steps

  1. Create an MCP server with FastMCP that uses typed return annotations:

    @server.tool()
    def structured_tool(x: int) -> dict[str, int]:
        return {"result": x * 2}
  2. Register the server as a gateway in ContextForge

  3. Query GET /tools or GET /servers/{id}/tools

  4. Observe: The returned tool definition has inputSchema but NOT outputSchema

Additional Context

  • This affects all modern FastMCP servers (FastMCP automatically generates outputSchema)
  • The MCP 2025-06-18 specification explicitly includes outputSchema
  • Structured outputs are becoming standard in AI workflows for reliability
  • ContextForge already stores input_schema in the database, so adding output_schema is a natural extension

Labels

  • bug: Core functionality (MCP spec compliance) is broken
  • enhancement: Adds missing feature support
  • mcp-protocol: Related to MCP specification adherence
  • python: Python codebase changes required

Priority

High - This breaks a core MCP specification feature that modern servers rely on.

Metadata

Metadata

Labels

bugSomething isn't workingenhancementNew feature or requestmcp-protocolAlignment with MCP protocol or specificationpythonPython / backend development (FastAPI)

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions