MCP client sends legacy initialize after successful 2026-07-28 server/discover · Issue #4888 · github/copilot-cli · GitHub
Skip to content

MCP client sends legacy initialize after successful 2026-07-28 server/discover #4888

Open

Description

@Falenos

Summary

Copilot CLI sends a successful MCP 2026-07-28 discovery request and then sends a legacy 2025-11-25 initialize request on the same stdio connection. A dual-era MCP SDK server correctly locks the connection to the modern protocol after server/discover, so it rejects the subsequent handshake with -32022.

This prevents Basic Memory from connecting even though the server successfully answers Copilot's discovery request.

Versions

  • Copilot CLI stable: 1.0.85
  • Also reproduced with prerelease: 1.0.86-2
  • Basic Memory: 0.23.2
  • FastMCP: 4.0.3 and 4.0.4
  • Python MCP SDK: 2.2.0
  • macOS arm64
  • Transport: stdio

Wire sequence

Client sends:

{"jsonrpc":"2.0","id":0,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"copilot-cli","version":"1.0.85"},"io.modelcontextprotocol/clientCapabilities":{"sampling":{},"elicitation":{"form":{},"url":{}}}}}}

Server successfully responds:

{"jsonrpc":"2.0","id":0,"result":{"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"Basic Memory","version":"4.0.3"}},"supportedVersions":["2026-07-28"],"capabilities":{"logging":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false},"extensions":{"io.modelcontextprotocol/ui":{}}},"resultType":"complete"}}

Copilot then sends on the same connection:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{"sampling":{},"elicitation":{"form":{},"url":{}}},"clientInfo":{"name":"copilot-cli","version":"1.0.85"}}}

The server correctly rejects that mixed-era request:

{"jsonrpc":"2.0","id":1,"error":{"code":-32022,"message":"connection is serving the 2026-07-28 protocol; the initialize handshake is not accepted","data":{"supported":["2026-07-28"],"requested":"2025-11-25"}}}

Copilot logs the server as failed to initialize.

Expected behavior

After a successful server/discover, Copilot should adopt the returned modern protocol and must not send initialize on that connection. If Copilot decides discovery failed or is unusable, it should close that connection and open a fresh connection before attempting the legacy handshake.

Additional verification

  • A fresh MCP 1.x client successfully negotiates 2025-11-25 with the same Basic Memory server.
  • Forcing the server into handshake-only mode makes Copilot connect and call Basic Memory tools successfully.
  • Disabling Basic Memory's initialization sync does not fix the issue; server/discover still succeeds and Copilot still sends initialize afterward.

Activity

  1. danswett commented on Sep 21, 2026

    @danswett

    Windows / 1.0.85 — the failure-path variant of this, and why it compounds

    To be precise up front: I have not reproduced the exact case in the report (successful server/discover followed by initialize on the same connection). What I see is the other branch your "Expected behavior" section calls out — discovery that Copilot decides is unusable or times out, followed by a legacy handshake:

    [WARNING] [rust:mcp::client] server/discover returned an unusable JSON response; retrying with legacy initialize
    [WARNING] [rust:mcp::client] server/discover timed out; retrying with legacy initialize
    

    Both variants appear on Windows with CLI 1.0.85 across a mixed set of 15 stdio and http MCP servers. They are not rare: I counted 26 of these warnings in a single 60-second window.

    Adding this mainly because of how it compounds with #4892. That issue causes the whole MCP graph to be force-reloaded twice an hour by a no-op managed-settings refresh. Because each reload re-runs discovery, and each failed discovery costs a second handshake, the observed rate lands at 6 Service initialized as client events per logical server per hour — 90/hour across 15 servers, 1498 over 15 idle hours. Roughly half of that volume is the retry described here.

    So the two issues multiply: #4892 sets the reload frequency, and this one sets the per-reload handshake cost.

    One question that might matter for the fix: when the fallback fires, does the client open a fresh connection before sending legacy initialize, or reuse the existing one? Your report shows reuse on the success path. If the failure path also reuses, then strict dual-era servers would reject the retry for the same -32022 reason, and the cost of a discovery timeout would be a fully failed server rather than just a slow one.

  2. joselcaguilar commented on Oct 19, 2026

    @joselcaguilar

    Here's another data point for the server/discover timeout variant, triggered by a stdio bridge that starts slowly. Environment: GitHub Copilot app 1.1.30 (CLI runtime 1.0.99-2) on macOS 27.0.1 arm64.

    Config (redacted):

    "My custom MCP (fastmcp-remote)": {
      "type": "local",
      "command": "uvx",
      "args": [
        "fastmcp-remote", "https://mcp.example.com/<redacted-path>",
        "--header", "CF-Access-Client-Id: <redacted>",
        "--header", "CF-Access-Client-Secret: <redacted>"
      ],
      "tools": ["*"]
    }

    Error shown in the app. The bridge is fastmcp-remote 4.1.0, which bundles mcp 2.3.0.

    failed to initialize MCP client: JSON-RPC error: -32022: connection is serving the 2026-07-28 protocol; the initialize handshake is not accepted({"supported":["2026-07-28"],"requested":"2025-11-25"}); last stderr: 'stdio' Server stderr (last 10 lines): … 🎉 Update available: 4.1.1 … INFO Starting MCP server 'fastmcp-remote' with transport 'stdio'
    

    What happens:

    1. The client writes server/discover while uvx is still resolving and starting the bridge. On this machine, uvx fastmcp-remote usually takes 5–7 s to start, and up to 28 s on a cold cache.
    2. The client times out (server/discover timed out; retrying with legacy initialize) and writes a legacy initialize (2025-11-25) to the same stdin.
    3. When the bridge finally starts, it reads the buffered server/discover first, which commits the connection to 2026-07-28. It then rejects the queued initialize with -32022.

    On stdio, a request that has been written is never lost, only delayed. So falling back to a legacy initialize on the same pipe after a discover timeout can't succeed against a modern server. Possible fixes:

    • Keep waiting for the discover response on stdio.
    • Restart the process before the legacy fallback.
    • Accept a late discover result instead of failing.

    Side note: the server/discover timed out; retrying with legacy initialize warning doesn't include the server name. When many servers start at once, there's no way to tell which one it's about.

    Possible workaround: install the bridge with uv tool install fastmcp-remote and point command at the installed binary. That cuts startup to about 1 s, but I haven't confirmed that it avoids the error in the app.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions