Skip to content

Response Transformation Hook/Middleware in call_tool() #593

@avior-qc

Description

@avior-qc

Is your feature request related to a problem? Please describe.
When adapting existing REST APIs to MCP endpoints (particularly fastmcp, fastapi_mcp), the response structure from the original API often doesn't align perfectly with what's ideal for MCP tool responses. Currently, there's no clean way to transform these responses before they're converted to types.ServerResult.

For example, an existing REST API might return a Pydantic model with fields like request_id or metadata that make sense in REST but are irrelevant or redundant in an MCP context.

Use Case Examples

  1. Removing irrelevant fields: Stripping out specific fields like that don't add value in an MCP context
  2. Restructuring data: Changing the shape of response data to better fit MCP's expected format
  3. Adding MCP-specific metadata: Enriching responses with additional information useful for MCP clients

Benefits

  • Cleaner separation of concerns between API logic and MCP integration
  • More flexibility when adapting existing APIs to MCP
  • Follows established middleware/hook patterns

Describe the solution you'd like
Add a response transformation hook or middleware-like support to the MCP SDK that allows post-processing of endpoint results before they're converted to types.ServerResult.

Something like this as a response hook:

Expose response processor registration on Server class

# In mcp.server.lowlevel.server
class Server:
    def __init__(self, *args, **kwargs):
        self.response_processors = {}
        super().__init__(*args, **kwargs)
    
    def register_response_processor(self, tool_name, processor_func):
        """Register a function that will transform responses for a specific tool"""
        self.response_processors[tool_name] = processor_func
    
    async def call_tool(self, req: types.CallToolRequest) -> types.ServerResult:
        # ... existing code ...
        
        results = await func(req.params.name, (req.params.arguments or {}))
        
        # Apply transformation if registered
        if req.params.name in self.response_processors:
            results = self.response_processors[req.params.name](results)
            
        return types.ServerResult(
            types.CallToolResult(content=list(results), isError=False)
        )

Middleware-style approach for FastMCP (kind of a usage example)

# Example usage
mcp_app = FastMCP.from_fastapi(app)
mcp_app.add_response_middleware(lambda tool_name, result: {
    k: v for k, v in result.items() if k != "request_id"
})

Decorator for individual endpoints (another usage example)

@mcp_transform_response(lambda response: {k: v for k, v in response.dict().items() if k != "request_id"})
@app.post("/my-endpoint")
async def my_endpoint():
    # ...

Describe alternatives you've considered
currently implemented it on my pydantic response class, override model_dump() to exclude certain fields if endpoint was called as mcp.

Additional context
None

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Significant bug affecting many users, highly requested featurefeature requestRequest for a new feature that's not currently supportedready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions