mirror of
https://github.com/open-webui/mcpo.git
synced 2026-11-05 01:27:39 +00:00
[PR #310] fix: reconnect when server invalidates the MCP session (404) #251
Labels
No labels
bug
can't reproduce
documentation
enhancement
good first issue
help wanted
invalid
pull-request
question
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
open-webui/mcpo#251
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/open-webui/mcpo/pull/310
Author: @cdecarlos
Created: 9/4/2026
Status: 🔄 Open
Base:
dev← Head:fix/reconnect-on-session-terminated📝 Commits (1)
8b7ccaefix: reconnect when server invalidates the MCP session (404)📊 Changes
2 files changed (+119 additions, -2 deletions)
View changed files
➕
src/mcpo/tests/test_reconnect.py(+96 -0)📝
src/mcpo/utils/main.py(+23 -2)📄 Description
Problem
call_tool_with_reconnect()only retries onClosedResourceError(a transport-level failure). When the upstream MCP server invalidates a session server-side — TTL expiry on aFileSessionStore-backed server, a restart, etc. — and returns a 404, that surfaces as a hard error to the mcpo client instead of transparently reconnecting, even thoughMCPConnectionManager.reconnect()already exists and handles the transport-failure case fine.Real-world traceback we hit in production:
which mcpo currently turns into an HTTP error response instead of retrying.
Root cause
python-sdk's
StreamableHTTPTransportsynthesizesMcpError(code=32600, message="Session terminated")on any HTTP 404 response to a JSON-RPC request, discarding whatever error body the server actually sent (see_send_session_terminated_errorinmcp/client/streamable_http.py, called unconditionally from_handle_post_requestonstatus_code == 404).One subtlety worth flagging explicitly: that
32600is a bare positive literal hardcoded by the SDK — it is notmcp.types.INVALID_REQUEST(-32600, the JSON-RPC-spec-correct signed value). Matching against the typed constant would silently never fire; the fix matches the literal value the SDK actually emits, with a comment explaining why.Fix
Catch that specific
McpError(matched oncode == 32600 and message == "Session terminated") alongsideClosedResourceErrorincall_tool_with_reconnect()'s retry path, and retry once against a freshly reconnected session. Any otherMcpError— a real tool-level or protocol error — still propagates unchanged.The
ensure_initialized()call path (before any session exists) does not need the same treatment: the synthesized error can only occur on a request carrying an already-establishedsession_id.Testing
src/mcpo/tests/test_reconnect.py: positive case (synthesized session-terminated error triggers exactly one reconnect + successful retry) and two negative cases (an unrelatedMcpError, and — to guard against the code-sign mixup above recurring — a realINVALID_REQUEST(-32600) error with a different message) confirm reconnection is not triggered for either.mcp/sdk-based,FileSessionStore): built mcpo from this branch, established a session, deleted the session's backing file to force the server to 404 on the next call, and confirmed the unpatched code returns a 500 to the mcpo client while this patch transparently negotiates a new session and completes the call.Assisted with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.