Use a POST-only transport for external MCP connections (fix list_tools hang)#441
Draft
goelise wants to merge 1 commit into
Draft
Use a POST-only transport for external MCP connections (fix list_tools hang)#441goelise wants to merge 1 commit into
goelise wants to merge 1 commit into
Conversation
External/managed MCP connections (/api/2.0/mcp/external/<connection>) are fronted by a POST-only JSON-REST bridge: it rejects non-POST methods with 405 and replies with application/json, never text/event-stream. The Streamable HTTP client used for managed MCP servers opens a background GET SSE stream; against this bridge that GET is rejected and a later request can block until timeout (modelcontextprotocol/python-sdk#1941), so connecting to an external connection hangs on list_tools. Route EXTERNAL_MCP URLs through a new PostOnlyMCPClient that speaks plain request/response JSON-RPC over POST (initialize -> notifications/initialized -> tools/list / tools/call, JSON or single-frame SSE body), returning the same mcp.types.Tool / CallToolResult objects. Managed servers (Genie, UC functions, vector search) keep using streamablehttp_client unchanged. Co-authored-by: Isaac
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DatabricksMCPClient.list_tools()/call_tool()hang (~20s, then fail) when the server URL is an external/managed connection at/api/2.0/mcp/external/<connection>(e.g. the managed Atlassian connection). The same connection works from the AI Playground and from a hand-rolled POST-only client, which makes this confusing.Root cause — transport mismatch, not a broken proxy
External connections are fronted by a POST-only JSON-REST bridge: it rejects any non-POST method with
405 Method Not Allowedand replies withapplication/json, nevertext/event-stream. There is no GET endpoint, no SSE stream, and no server-initiated messages. (This is spec-legal — the GET SSE stream in Streamable HTTP is optional.)DatabricksMCPClient(anddatabricks_openai.agents.McpServer) go through themcpSDK'sstreamablehttp_client, a full Streamable HTTP client that, on connect, opens a background GET SSE stream for server→client messages. Against the bridge that GET gets405and the stream task dies; a later request whose response handling expects coordination with that (now-dead) stream then blocks until timeout. This is the documented upstream issue modelcontextprotocol/python-sdk#1941 ("hangs indefinitely when connecting to POST-only MCP servers", reported against GitHub's POST-only MCP server — same class of server as the bridge).Two adjacent factors compound it: the initialized-notification POST can be treated as fatal by default (openai-agents made tolerance opt-in in #2765), and the SDK advertises a very new
LATEST_PROTOCOL_VERSION.Fix in this PR
Route
EXTERNAL_MCPURLs (already classified inMCP_URL_PATTERNS) through a newPostOnlyMCPClientthat speaks plain request/response JSON-RPC over POST:initialize→notifications/initialized→tools/list/tools/call, parsing the JSON-RPC frame as JSON or, defensively, a single SSEdata:frame. It returns the samemcp.types.Tool/CallToolResultobjects, so it's a drop-in for the external branch. Managed servers (Genie, UC functions, vector search) are unchanged — they keep usingstreamablehttp_client.databricks_mcp/external_client.py— newPostOnlyMCPClient.databricks_mcp/mcp.py—_get_tools_async/_call_tools_asyncbranch onEXTERNAL_MCP.PostOnlyMCPClientand managed →streamablehttp_client(existing tests unchanged).Scope / open questions for reviewers
databricks_openai.agents.McpServeris not addressed here. It subclassesMCPServerStreamableHttp, whose whole design is the streamable transport, so a POST-only server doesn't fitcreate_streams. Fixing the agent path needs a separateMCPServersubclass over POST-only HTTP. Want guidance on whether to add that here or in a follow-up, and where it should live.2025-03-26(what the bridge negotiates). Reviewers may prefer to negotiate from theinitializeresponse instead.Validation status
Reference POST-only client originates from sunish.sheth's dais-debug app.