Summary
Copilot CLI's native MCP client sends a proprietary server/discover JSON-RPC request to locally-spawned stdio MCP servers before the standard initialize handshake, whenever it has no cached tool-list snapshot for that server. This violates the MCP lifecycle spec and causes any server that correctly enforces "initialize must be first" to terminate — which the CLI then reports as a permanent "broken pipe" connection failure, silently losing access to that server's tools.
Environment
- Copilot CLI version: 1.0.83 (Windows x64)
- Affected server: any strictly spec-compliant local stdio MCP server (reproduced with CodeScene's
cs-mcp, but the issue is in the CLI's connector, not specific to that server)
Expected behavior
Per the MCP specification, Lifecycle:
"The initialization phase MUST be the first interaction between client and server... The client MUST initiate this phase by sending an initialize request."
"The client SHOULD NOT send requests other than pings before the server has responded to the initialize request."
There is no server/discover method defined anywhere in the MCP spec, and no provision permitting a client to send a probe request ahead of initialize.
Actual behavior
When the CLI's local per-server MCP tool-cache is cold for a given server (e.g. right after a CLI update bumps the tool-cache schema version, invalidating previously cached snapshots), the CLI's native connector sends server/discover as the very first message on the child process's stdin — not initialize. CLI logs show:
[rust:mcp::client] server/discover timed out; retrying with legacy initialize
[rust:copilot_runtime::mcp::native_connector] MCP server connection failed {"server_name":"<name>", ... the server closed its input stream (broken pipe) ...}
By the time the CLI retries with initialize alone, the server process has already exited in response to the unexpected first message — there is no successful fallback, and the server's tools never become available.
Reproduction (minimal)
- Configure a local stdio MCP server that (per spec) treats any non-
initialize first message as a protocol violation and terminates.
- Ensure the CLI has no valid cached tool snapshot for that server (delete
%LOCALAPPDATA%\copilot\mcp-tools\<server>.json, or update across a cache-schema version bump).
- Start a Copilot CLI session that references the server (or simply let it enumerate tools).
- Observe the server's stdin receives
server/discover first, and the server process exits before initialize is ever sent.
We independently verified the root cause with the following evidence:
- Manual stdio test bypassing the CLI entirely: driving the server's JSON-RPC protocol directly (sending
initialize first, as the spec requires) works correctly and returns valid tool results. The server only fails when server/discover (or any other message) precedes initialize.
- Full version bisection of the server: tested its entire public release history (7 versions spanning over a year of releases) against the same
server/discover-before-initialize input — every version exhibits the identical fatal behavior, ruling out a recent server-side regression as the explanation for why this "started happening now."
- CLI log correlation: the same session that first exhibited the failure also logged
Unsupported MCP tool cache schema version: 1 for essentially all configured MCP servers, indicating the local tool-cache schema had just been bumped and invalidated. This aligns with the CLI's own v1.0.76 changelog entry: "MCP tools load faster from definition-scoped snapshots, with process-wide and per-server cache opt-outs" — which appears to be when this discovery-probe-on-cache-miss behavior was introduced.
Suggested fix
- Never send anything other than
initialize as the first message to a freshly spawned local stdio server. If a discovery/caching optimization is desired, negotiate it via the standard experimental capabilities object during the initialize handshake, not as a message preceding it.
- At minimum, if a speculative pre-
initialize probe is sent and the server closes its pipe / never responds, treat that as "this server doesn't support the probe" and immediately respawn the server sending only initialize — rather than treating the dead process as a permanent, unrecoverable connection failure.
Impact
Any spec-compliant local stdio MCP server that doesn't tolerate an unexpected pre-initialize message becomes silently and permanently unusable from Copilot CLI whenever the local tool-cache is cold for that server (e.g., immediately after a CLI upgrade) — with no user-facing indication of why, beyond a generic "broken pipe" log line buried in debug logs.
Summary
Copilot CLI's native MCP client sends a proprietary
server/discoverJSON-RPC request to locally-spawned stdio MCP servers before the standardinitializehandshake, whenever it has no cached tool-list snapshot for that server. This violates the MCP lifecycle spec and causes any server that correctly enforces "initialize must be first" to terminate — which the CLI then reports as a permanent "broken pipe" connection failure, silently losing access to that server's tools.Environment
cs-mcp, but the issue is in the CLI's connector, not specific to that server)Expected behavior
Per the MCP specification, Lifecycle:
There is no
server/discovermethod defined anywhere in the MCP spec, and no provision permitting a client to send a probe request ahead ofinitialize.Actual behavior
When the CLI's local per-server MCP tool-cache is cold for a given server (e.g. right after a CLI update bumps the tool-cache schema version, invalidating previously cached snapshots), the CLI's native connector sends
server/discoveras the very first message on the child process's stdin — notinitialize. CLI logs show:By the time the CLI retries with
initializealone, the server process has already exited in response to the unexpected first message — there is no successful fallback, and the server's tools never become available.Reproduction (minimal)
initializefirst message as a protocol violation and terminates.%LOCALAPPDATA%\copilot\mcp-tools\<server>.json, or update across a cache-schema version bump).server/discoverfirst, and the server process exits beforeinitializeis ever sent.We independently verified the root cause with the following evidence:
initializefirst, as the spec requires) works correctly and returns valid tool results. The server only fails whenserver/discover(or any other message) precedesinitialize.server/discover-before-initializeinput — every version exhibits the identical fatal behavior, ruling out a recent server-side regression as the explanation for why this "started happening now."Unsupported MCP tool cache schema version: 1for essentially all configured MCP servers, indicating the local tool-cache schema had just been bumped and invalidated. This aligns with the CLI's own v1.0.76 changelog entry: "MCP tools load faster from definition-scoped snapshots, with process-wide and per-server cache opt-outs" — which appears to be when this discovery-probe-on-cache-miss behavior was introduced.Suggested fix
initializeas the first message to a freshly spawned local stdio server. If a discovery/caching optimization is desired, negotiate it via the standardexperimentalcapabilities object during theinitializehandshake, not as a message preceding it.initializeprobe is sent and the server closes its pipe / never responds, treat that as "this server doesn't support the probe" and immediately respawn the server sending onlyinitialize— rather than treating the dead process as a permanent, unrecoverable connection failure.Impact
Any spec-compliant local stdio MCP server that doesn't tolerate an unexpected pre-
initializemessage becomes silently and permanently unusable from Copilot CLI whenever the local tool-cache is cold for that server (e.g., immediately after a CLI upgrade) — with no user-facing indication of why, beyond a generic "broken pipe" log line buried in debug logs.