Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions docs/specification/draft/basic/lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,18 @@ both kinds of servers can detect which is present:
- **STDIO.** Because there is no per-request status code to drive fallback,
a client that supports both eras **SHOULD** probe with
[`server/discover`](/specification/draft/server/discover) first,
setting its preferred modern version in `_meta`. If the server returns
`Method not found` (`-32601`), fall back to the legacy `initialize`
handshake. If the server returns `UnsupportedProtocolVersionError`, the
server speaks a version of MCP without `initialize` — use one of the
versions in its advertised `supported` list instead of falling back to
`initialize`.
setting its preferred modern version in `_meta`. The client interprets the
response as follows:
- **`UnsupportedProtocolVersionError`**: the server speaks modern MCP
without the requested version. Retry using one of the versions in its
advertised `supported` list rather than falling back to `initialize`.
- **Any other error response, or no response within a reasonable timeout**:
restart the server process and perform the legacy `initialize` handshake

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, not sure if this restart is necessary. I'll check what all the official SDKs do. I agree it's probably cleaner to restart, but also very churny and will slow down initialization for any clients that block on initializing all MCP servers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ffs, they all handle it except Rust, which exits :(

So I don't think we need to proactively restart any server: they either restart themselves or gracefully handle it.

Rust is a problem though.

More generally, this is an issue because the SDKs don't manage stdio process lifecycle, so it's actually the applications that need to implement this, and I think that's a huge problem. We really cannot expect all applications to implement this backwards compat approach.

I think options are:

  1. Fix Rust ASAP so that at least new stdio servers work correctly. Accept that it breaks old Rust MCP stdio servers for modern clients.
  2. Add language to the spec that mandates clients to implement the stdio server respawn and init-fallback logic.
  3. Think of something else?

I lean towards (1) depending on how many big rust stdio servers are out there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Popular rmcp (official Rust SDK) stdio MCP servers

Ranked by GitHub stars. All run rmcp::serve(stdio()).

Stars Repo What it is stdio
2,373★ afnanenayet/diffsitter tree-sitter AST difftool; ships an optional MCP server (8 AST tools) default
1,372★ imhuso/cunzhi (寸止) MCP server: "don't stop early" popup, per-project memory, code search stdio-only
891★ microsoft/wassette Microsoft — runs WebAssembly Components as MCP tools in a Wasmtime sandbox default (also http/sse)
740★ penso/arbor agentic-coding desktop app; ships a dedicated arbor-mcp server default
364★ nwiizo/tfmcp Terraform MCP server (plan/apply/state) for Claude Desktop stdio-only
209★ gbrigandi/mcp-server-wazuh Wazuh SIEM data (alerts, agents, vulns) as MCP tools default
121★ fabio-rovai/open-ontologies ontology engineering — 43 onto_* tools over an Oxigraph store default
120★ tidewave-ai/mcp_proxy_rust presents a stdio MCP server that proxies to remote SSE/HTTP servers stdio-only
54★ radiosilence/fastmail-cli Fastmail JMAP CLI with an mcp subcommand (email tools) stdio-only
53★ jokemanfire/mcp-containerd containerd CRI/ctr operations as MCP tools default

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.

this is an issue because the SDKs don't manage stdio process lifecycle, so it's actually the applications that need to implement this,

I don't think that's true -- can't the SDK do this are part of the initialization that happens today? The SDK is the one that's starting up the stdio process right?

Do we know what the impact on start up time is? Generally a process spin up is pretty lightweight.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think that's true -- can't the SDK do this are part of the initialization that happens today? The SDK is the one that's starting up the stdio process right?

Yes, you are right, I got that wrong, so I think it should be ok to do the restart. The only thing I'd change is to not do it unconditionally: only restart if the server actually dies.

Do we know what the impact on start up time is? Generally a process spin up is pretty lightweight.

For nice languages/runtimes, yes, but e.g. playwright (yarn mcp-server-playwright) takes 750ms on average to spawn and respond to initialize. For a simple echo FastMCP server, it's 400ms warm.

on the fresh process. Legacy servers are not required to use any
particular error code (or to respond at all) for an unknown method
received before `initialize`, so the fallback cannot rely on a single
code such as `-32601`, and the server may be left in an undefined state
after the unrecognized probe.

A client that only supports modern (per-request-metadata) versions does not
need to probe — it simply sends its preferred version and handles
Expand Down
36 changes: 10 additions & 26 deletions docs/specification/draft/basic/transports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,11 @@ re-established after restart.

### Backward Compatibility

A client that supports both modern (per-request-metadata) MCP versions and a
legacy version that requires an `initialize` handshake **SHOULD** probe with
[`server/discover`][server-discover] before sending
any other request. If the server returns `Method not found` (`-32601`), the
client falls back to the legacy `initialize` handshake. If the server returns
`UnsupportedProtocolVersionError`, it speaks a version of MCP without
`initialize` — the client **SHOULD** retry using one of the versions in the
advertised `supported` list rather than falling back to `initialize`. See
[Lifecycle: Backward Compatibility][lifecycle-compat]
for details.
A client that interoperates with both modern (per-request-metadata) MCP
versions and a legacy version requiring an `initialize` handshake **SHOULD**
probe with [`server/discover`][server-discover] before sending any other
request. See [Lifecycle: Backward Compatibility][lifecycle-compat] for the
rule that determines when to fall back to `initialize`.

A client that only supports modern versions does not need to probe.

Expand Down Expand Up @@ -585,22 +580,11 @@ a JSON-RPC error response.

### Backward Compatibility

A client that supports both modern (per-request-metadata) MCP versions and a
legacy version that requires an `initialize` handshake **MAY** detect which
era the server implements by attempting a modern request first. On
`400 Bad Request`, the client **SHOULD** inspect the response body before
falling back: modern servers also use `400` for
[`UnsupportedProtocolVersionError`][unsupported-version],
`MissingRequiredClientCapabilityError`, and header-validation failures.

- If the body contains a recognized modern JSON-RPC error, the server speaks
a modern version of MCP — retry using the advertised `supported` versions
or correct the request, rather than falling back.
- If the body is empty or is not a recognized modern JSON-RPC error, fall
back to `initialize` and continue with the legacy version for subsequent
requests.

See [Lifecycle: Backward Compatibility][lifecycle-compat] for details.
A client that interoperates with both modern (per-request-metadata) MCP
versions and a legacy version requiring an `initialize` handshake **MAY**
detect which era the server implements by attempting a modern request first.
See [Lifecycle: Backward Compatibility][lifecycle-compat] for the rule that
determines when to fall back to `initialize`.

Separately, clients and servers can maintain backward compatibility with the
deprecated [HTTP+SSE transport][http-sse] (from
Expand Down
7 changes: 4 additions & 3 deletions docs/specification/draft/server/discover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ is useful in two scenarios:
- **Up-front version selection.** The client learns the server's supported
versions before sending any other request, avoiding a round-trip error.
- **STDIO backward-compatibility probe.** On stdio, there is no per-request
HTTP status code to drive fallback. A client that supports both modern
HTTP status code to drive fallback, so a client that supports both modern
(per-request `_meta`) and legacy (`initialize` handshake) servers **SHOULD**
send `server/discover` first. If the server returns `Method not found`
(`-32601`), the client falls back to the `initialize` handshake.
send `server/discover` first to detect which era the server speaks. See
[Lifecycle: Backward Compatibility](/specification/draft/basic/lifecycle#backward-compatibility-with-initialization-based-versions)
for the rule that determines when to fall back.

See [Protocol Version Negotiation](/specification/draft/basic/lifecycle#protocol-version-negotiation)
for the full version-selection flow. For HTTP-specific status codes returned for
Expand Down