server: skip duplicate response on CancelledError by lukacf · Pull Request #1153 · modelcontextprotocol/python-sdk

calreynolds pushed a commit to databricks-solutions/ai-dev-kit that referenced this pull request

@QuentinAmbard @claude

#411)

* Fix MCP server crash on request cancellation

When a client cancels a long-running MCP request, there's a race condition
between the cancellation and normal response paths:

1. Client cancels request → RequestResponder.cancel() sends error response
   and sets _completed = True
2. Middleware catches CancelledError and returns a ToolResult
3. MCP SDK tries to call message.respond(response)
4. Crash: assert not self._completed fails

Fix: Re-raise CancelledError instead of returning a result, allowing the
MCP SDK's cancellation handler to properly manage the response lifecycle.

See: modelcontextprotocol/python-sdk#1153

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add structured_content to error responses for MCP SDK validation

When tools have an outputSchema (auto-generated from return type like
Dict[str, Any]), MCP SDK requires structured_content in all responses.
The middleware was returning ToolResult without structured_content for
error cases (timeout, exceptions), causing validation errors:

  "Output validation error: outputSchema defined but no structured output returned"

Fix: Include structured_content with the same error data in all error responses.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix structured_content not populated for tools with return type annotations

FastMCP auto-generates outputSchema from return type annotations (e.g.,
-> Dict[str, Any]) but doesn't populate structured_content in ToolResult.
MCP SDK validation then fails: "outputSchema defined but no structured output"

Fix: Intercept successful results and populate structured_content from
JSON text content when missing. Only modifies results when:
1. structured_content is missing
2. There's exactly one TextContent item
3. The text is valid JSON that parses to a dict

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(mcp): apply async wrapper on all platforms to prevent cancellation crashes

The asyncio.to_thread() wrapper was only applied on Windows, but it's
needed on ALL platforms to enable proper cancellation handling.

Without this fix, when a sync tool runs longer than the client timeout:
1. Client sends cancellation
2. Sync tool blocks event loop, can't receive CancelledError
3. Tool eventually returns, but MCP SDK already responded to cancel
4. AssertionError: "Request already responded to" → server crashes

This was discovered when uploading 7,375 files triggered a timeout,
crashing the MCP server on macOS.

Extends the fix from PR #411 which added CancelledError handling in
middleware - that fix only works when cancellation can propagate,
which requires async execution via to_thread().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix: don't set structured_content on error responses

Setting structured_content causes MCP SDK to validate it against the
tool's outputSchema. For error responses, the error dict {"error": True, ...}
doesn't match the expected return type (e.g., Union[str, List[Dict]]),
causing "Output validation error: 'result' is a required property".

Fix: Only set structured_content for successful responses, not errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Quentin Ambard <quentin.ambard@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>