Skip to content

Authorization header dropped on JSON-RPC tools/call after successful MCP initialize (Streamable HTTP + OAuth 2.1 / DCR) #3015

@Raznak

Description

@Raznak

Describe the bug

Summary

After a successful OAuth 2.1 + DCR flow against an MCP server using Streamable HTTP, Copilot CLI sends the Authorization: Bearer … header on initialize but omits it on subsequent JSON-RPC requests (tools/list, tools/call).
The server-side session is shown as "connected" in /mcp, but every tool invocation fails with 401.

Environment

  • copilot --version1.0.37 (latest at time of report)
  • macOS (Darwin 25.4.0)
  • MCP server: own implementation, Rust, Streamable HTTP via rmcp 1.4.0
  • Auth: OAuth 2.1 + RFC 7591 (Dynamic Client Registration), public client (PKCE, token_endpoint_auth_method=none)
  • AS metadata exposed at /.well-known/oauth-authorization-server (RFC 8414)
  • Same server works correctly with Claude Desktop and Gemini CLI

Expected: Tool call succeeds (Bearer sent on the JSON-RPC tools/call HTTP request).

Observed: Tool call fails. Server logs show the JSON-RPC tools/call arrived with no Authorization header.

Server-side evidence

{
  "level": "WARN",
  "service": "backend",
  "target": "rmcp::service",
  "message": "response error",
  "id": "4",
  "error": "ErrorData { code: ErrorCode(-32600), message: \"UNAUTHORIZED: missing Authorization header\", data: None }"
}

JSON-RPC id 4 corresponds to a tools/call (ids 0–2 are the MCP handshake: initialize, notifications/initialized, tools/list).

The DCR registration of the Copilot CLI client succeeded:

field value
client_name GitHub Copilot CLI
client_type public
grant_types authorization_code, refresh_token
redirect_uris http://127.0.0.1:<random-port>/
dynamically_registered true

So the OAuth flow itself completes — the bug is downstream, in the MCP transport layer not propagating the Bearer to subsequent JSON-RPC HTTP requests.

Affected version

GitHub Copilot CLI 1.0.37.

Steps to reproduce the behavior

  1. Configure the MCP server in ~/.copilot/mcp-config.json with "type": "http" pointing at a Streamable HTTP MCP that requires Bearer on every request.
  2. Start Copilot CLI, /mcp lists the server as connected (browser opens, OAuth flow completes, refresh + access tokens stored).
  3. Ask Copilot to invoke any tool exposed by the server.

Expected behavior

Tool call succeeds (Bearer sent on the JSON-RPC tools/call HTTP request).

Additional context

Cross-client check

The exact same server, with the exact same DCR + OAuth config, works correctly with:

  • Claude Desktop — Bearer sent on every JSON-RPC request
  • Gemini CLI MCP client — Bearer sent on every JSON-RPC request

So the server is compliant with the MCP Streamable HTTP spec (2025-03-26). The bug is specific to Copilot CLI's handling of the access token
after initialize.

Likely cause

Per the MCP Streamable HTTP spec, each JSON-RPC message is an independent HTTP POST to /mcp. The Authorization header must be attached to every request, not just to initialize or to the SSE long-lived stream.

It looks like Copilot CLI's OAuthProvider/transport binding only attaches the Bearer at session establishment (or to the SSE stream open), not on subsequent POSTs that carry the JSON-RPC payloads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:authenticationLogin, OAuth, device auth, token management, and keychain integrationarea:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registry

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions