Skip to content

Remote MCP OAuth startup fans out across hosts/reconnects, causing repeated auth and rate limits #3706

@nakul-malhotra

Description

@nakul-malhotra

Describe the bug

Remote HTTP MCP servers that require OAuth can be started many times in one CLI session, causing repeated initialize/OAuth/tool-list work against the same endpoint. In a real session with a single Azure DevOps MCP server configured, the Copilot CLI logs showed 79 MCP client for azure-devops connected events, repeated OAuth churn, 429 responses, and intermittent -32001 Request timed out errors.

The endpoint itself was healthy: DNS/TLS/connectivity were fine, the MCP initialize request returned the expected 401 OAuth challenge, and the OAuth metadata endpoint returned the expected tenant/scopes. The failure mode appears to be client-side startup/auth fan-out rather than network failure.

This is related to, but distinct from, #3456. That issue covers concurrent refresh-token grants once tools are already in use. This issue is about repeated remote MCP host/client startup and OAuth initiation across session/agent contexts and reconnect paths.

Affected version

GitHub Copilot CLI 1.0.60 on macOS.

Configuration shape

Sanitized .mcp.json:

{
  "mcpServers": {
    "azure-devops": {
      "type": "http",
      "url": "https://mcp.dev.azure.com/<org>",
      "timeout": 120000,
      "headers": {
        "X-MCP-Readonly": "true"
      }
    }
  }
}

Note: timeout: 60000 is not an effective increase in 1.0.60 because the runtime applies a 60s floor/default. Raising it above 60000 only mitigates slow requests; it does not address the repeated startup/auth storm.

Observed behavior

  • One configured remote MCP server produced many successful client connection events in the same active session.
  • OAuth metadata/challenge paths were hit repeatedly.
  • The server emitted rate-limit responses (429) during the churn.
  • The client sometimes surfaced -32001 Request timed out.
  • Disabling the remote MCP server by default avoids the local symptom, but removes the tool unless manually re-enabled.

Runtime code-path evidence from the distributed 1.0.60 bundle

The public github/copilot-cli repository appears to contain only the release wrapper (README.md, install.sh, changelog, workflows), so I could not prepare a code PR against the real source. I inspected the installed runtime bundle at ~/.copilot/pkg/darwin-arm64/1.0.60/app.js instead.

Key findings from the minified runtime:

  1. Each log line MCP client for <server> connected is emitted only after a fresh client.connect(...) in setupAndConnectClient, so repeated log lines represent repeated client connections, not repeated logging from one connection.
  2. processHttpServer / processSseServer first try to start the remote client without an auth provider. On auth-required failure, they initiate OAuth and call startHttpMcpClient / startSseMcpClient again with the provider.
  3. buildMcpOAuthHandler either emits mcp.oauth_required or directly runs browserless OAuth. There does not appear to be a global singleflight/dedupe across MCP host instances for the same server URL/resource.
  4. The agent tool config path exposes getMcpServerProviderForAgent, which calls a host cache keyed by an agent/context key. getOrCreateHost creates a new MCP host for that key and starts the same mcpServers config. This can multiply remote MCP clients when multiple agents/contexts need the same configured server.
  5. 1.0.60 also records remoteReconnectMeta for HTTP/SSE servers and attempts reconnect on unexpected client close, which can further multiply connection attempts if the remote transport closes during auth replacement, timeout, or rate-limit responses.
  6. The request options helper applies timeout: Math.max(config.timeout ?? 0, 60000) and resetTimeoutOnProgress: true, so the default/minimum request timeout is already 60s.

Expected behavior

For a given remote MCP server URL/resource/client identity, the CLI should avoid duplicate startup/auth work across all in-process MCP hosts.

Suggested fixes:

  • Add a process-wide singleflight for remote MCP OAuth/startup keyed by a stable remote identity, e.g. (transport type, normalized URL/resource, client_id/sub where applicable, relevant auth config).
  • Coalesce concurrent OAuth requests so multiple hosts await the same in-flight provider/token flow instead of each initiating its own.
  • Reuse/share remote MCP hosts or clients by config fingerprint where safe, instead of starting one remote client per agent/context when the underlying server config is identical.
  • If cached OAuth is already available, avoid the unauthenticated first-connect path, or ensure the failed unauthenticated transport is fully closed and marked intentional before starting the authenticated replacement.
  • Do not auto-reconnect on auth-required/401 replacement or intentional OAuth reconnect; rate-limit reconnects and avoid reconnecting when the server is already marked needs-auth or failed due to auth.
  • Keep the refresh-token singleflight from Concurrent refresh-token requests against the same parent RT kill the OAuth chain on MCP servers with strict reuse detection #3456 shared across every host/client instance that can reach the same OAuth token cache.

Why this matters

Remote MCP servers often back onto shared OAuth/token/rate-limit infrastructure. Starting many independent clients for the same configured remote endpoint can create unnecessary OAuth prompts, rate limits, and request timeouts even when the endpoint is healthy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions