Skip to content

fix: fall back to legacy initialize on general errors - #1088

Open
jamadeo wants to merge 1 commit into
mainfrom
jamadeo/legacy-fallback-lifecycle-mode
Open

fix: fall back to legacy initialize on general errors#1088
jamadeo wants to merge 1 commit into
mainfrom
jamadeo/legacy-fallback-lifecycle-mode

Conversation

@jamadeo

@jamadeo jamadeo commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

fixes #1040

Motivation and Context

The fallback logic was too strict, only falling back on a single METHOD_NOT_FOUND error, while the spec states we should fall back on any error that does not indicate a modern server.

How Has This Been Tested?

Tested against FastMCP+goose

Breaking Changes

No

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@jamadeo
jamadeo requested a review from a team as a code owner July 29, 2026 22:57
@github-actions github-actions Bot added T-test Testing related changes T-core Core library changes T-service Service layer changes labels Jul 29, 2026
// On stdio, legacy servers may reject an unknown pre-initialize request with
// any implementation-defined error (or not respond at all). Treat every
// failure not recognized as modern version negotiation as a legacy peer.
Err(_) => {

@DaleSeo DaleSeo Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also consider this in the issue?
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix might not be quite as straightforward as I'd hoped. The core of the issue is

A client that needs to interoperate with both kinds of servers detects the server’s era with transport-specific mechanics, specified in the binding pages:

  • stdio: probe with server/discover and fall back on any error that is not a recognized modern error.
  • Streamable HTTP: attempt a modern request and inspect the body of a 400 Bad Request before falling back.

from here

so, I think the proper fix will need to implement this slightly differently.

}

#[tokio::test]
async fn auto_startup_falls_back_after_discover_invalid_params() {

@DaleSeo DaleSeo Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth adding a test to catch a future ClientInitializeError variant being silently swept into Err(_)?

@chrisbennight

Copy link
Copy Markdown

I ran into issues form this in rcmp - flagged here: #1040 (comment)

I think the relevant bit is other servers are giving transport level errors (rcmp and mcp-go flagged in that coment), and at a quick glance this pr looks like it retries on the same http worker, which would be dead I think.

@ip2a

ip2a commented Aug 5, 2026

Copy link
Copy Markdown

Confirming this fix resolves my case — a deployed 2025-03-26 server that enforces session IDs via middleware, the same class of failure @chrisbennight described in #1040.

Repro: a deployed streamable-HTTP MCP server (self-reported serverInfo: "Weather Service" v2.x), protocol 2025-03-26, session enforcement enabled.

A stateless server/discover probe gets rejected by the session layer before method dispatch, so it never reaches a -32601:

POST /mcp
(no Mcp-Session-Id header)

HTTP/1.1 400 Bad Request
mcp-session-id: 717fee78781b4526a207c98334554b8b
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Bad Request: Missing session ID"}}

(Note the server both allocates a session id and rejects the request for not carrying one — the session middleware runs before method dispatch, so -32601 is unreachable here.)

Behavior:

  • ClientLifecycleMode::Auto (current rmcp 3.1.0): fails with the -32600 above, since the fallback only triggers on -32601.
  • ClientLifecycleMode::Initialize: connects and lists tools successfully.
  • With this PR's Err(_) => legacy fallback: the -32600 is no longer in the METHOD_NOT_FOUND-only path, so Auto would fall back as intended.

The NoCompatibleProtocolVersion => return Err carve-out is the right disambiguator — it preserves the one signal that positively identifies a modern server.

One suggestion to strengthen coverage: the added test covers INVALID_PARAMS (-32602). Would be worth adding a -32600 case too, since session/auth middleware rejecting a sessionless probe is a distinct and fairly common failure shape on streamable-HTTP. Happy to push a test commit if helpful.

Thanks for the clean, minimal fix — this unblocks Auto against real deployed servers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-service Service layer changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClientLifecycleMode::Auto does not fall back for deployed legacy-server responses

4 participants