gemini-cli - 💡(How to fix) Fix StreamableHTTPClientTransport: OAuth token not forwarded to SSE GET request, causing 401 and disconnect [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
google-gemini/gemini-cli#25473Fetched 2026-04-16 07:06:01
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
labeled ×2

Error Message

Found stored OAuth token for server 'myserver' [MCP error] MCP ERROR (myserver) StreamableHTTPError: Streamable HTTP error: Failed to open SSE stream: Unauthorized at StreamableHTTPClientTransport._startOrAuthSse (...) [MCP error] Error during discovery for MCP server 'myserver': Client is not connected, must connect before interacting with the server. Current state is disconnected

Root Cause

In StreamableHTTPClientTransport, the _startOrAuthSse() method opens a GET request for the SSE notification stream but does not include the OAuth Authorization header that was used for the POST initialize request. The server rejects the unauthenticated GET with 401, causing the transport to disconnect.

Code Example

Found stored OAuth token for server 'myserver'
[MCP error] MCP ERROR (myserver) StreamableHTTPError: Streamable HTTP error: Failed to open SSE stream: Unauthorized
    at StreamableHTTPClientTransport._startOrAuthSse (...)
[MCP error] Error during discovery for MCP server 'myserver': Client is not connected, must connect before interacting with the server. Current state is disconnected

---

curl -s -X POST https://<server-host>/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

# Returns a valid initialize response with tools capability

---

{
  "mcpServers": {
    "myserver": {
      "httpUrl": "https://<server-host>/mcp"
    }
  }
}

---

CLI Version: 0.37.2 (also reproduced on 0.39.0-preview.0)
OS: macOS (darwin 24.6.0)
Auth Method: OAuth (oauth-personal)
RAW_BUFFERClick to expand / collapse

What happened?

When connecting to an OAuth-protected MCP server using httpUrl (Streamable HTTP transport), the initial POST request for initialize succeeds with the stored OAuth token, but the subsequent GET request to open the SSE notification stream (_startOrAuthSse) does not include the Authorization header. The server returns 401 Unauthorized, which tears down the entire connection.

The OAuth flow itself completes successfully — the token is stored in ~/.gemini/mcp-oauth-tokens.json and is valid. The server responds correctly to authenticated POST requests (verified independently via curl). The failure is specifically in the SSE GET sub-request within the Streamable HTTP transport.

Debug output

Found stored OAuth token for server 'myserver'
[MCP error] MCP ERROR (myserver) StreamableHTTPError: Streamable HTTP error: Failed to open SSE stream: Unauthorized
    at StreamableHTTPClientTransport._startOrAuthSse (...)
[MCP error] Error during discovery for MCP server 'myserver': Client is not connected, must connect before interacting with the server. Current state is disconnected

Verification that the server works

The MCP server responds correctly when the token is included manually via curl:

curl -s -X POST https://<server-host>/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

# Returns a valid initialize response with tools capability

Configuration

{
  "mcpServers": {
    "myserver": {
      "httpUrl": "https://<server-host>/mcp"
    }
  }
}

Also tried with url (SSE transport) and with explicit headers containing the Bearer token — same result. The headers config is not forwarded to the SSE GET request either.

Steps to reproduce

  1. Configure an MCP server using httpUrl that requires OAuth (supports RFC 8414 discovery) and returns 401 on unauthenticated requests
  2. Run gemini — the OAuth flow triggers and completes successfully
  3. The CLI stores the token, but the connection attempt fails because _startOrAuthSse() sends a GET without the Authorization header
  4. Server returns 401 on the GET, the connection tears down
  5. /mcp shows the server as Disconnected
  6. Restarting the CLI repeats the same cycle

Root cause

In StreamableHTTPClientTransport, the _startOrAuthSse() method opens a GET request for the SSE notification stream but does not include the OAuth Authorization header that was used for the POST initialize request. The server rejects the unauthenticated GET with 401, causing the transport to disconnect.

What did you expect to happen?

The OAuth token should be included in all requests made by the transport — both POST requests and the GET SSE stream request. The connection should remain established after a successful OAuth flow.

Client information

CLI Version: 0.37.2 (also reproduced on 0.39.0-preview.0)
OS: macOS (darwin 24.6.0)
Auth Method: OAuth (oauth-personal)

Login information

Google Account

Anything else we need to know?

Related issues:

  • #18895 — CLI cannot use fresh token in MCP OAuth
  • #23776 — MCP servers with OAuth lose authentication when access token expires mid-session
  • #6639 — MCP client fails to handle 401 errors correctly for Streamable HTTP Transport (closed/fixed, but different manifestation)

The MCP TypeScript SDK's StreamableHTTPClientTransport supports a custom fetch implementation for propagating auth headers across all requests, but it appears Gemini CLI is not utilizing this for the SSE GET request path.

extent analysis

TL;DR

The most likely fix is to modify the StreamableHTTPClientTransport to include the OAuth token in the Authorization header for the SSE GET request.

Guidance

  • Verify that the StreamableHTTPClientTransport class has access to the stored OAuth token and can include it in the Authorization header for the SSE GET request.
  • Check if the fetch implementation in the MCP TypeScript SDK can be utilized to propagate auth headers across all requests, including the SSE GET request.
  • Consider adding a custom headers configuration option to the httpUrl setting to allow users to specify the Authorization header for the SSE GET request.
  • Review related issues (#18895, #23776, #6639) to ensure that the fix does not introduce any regressions.

Example

// Example of how to include the OAuth token in the Authorization header
// for the SSE GET request using a custom fetch implementation
const fetchWithAuth = (url, options) => {
  const token = getTokenFromStorage();
  options.headers = {
    ...options.headers,
    Authorization: `Bearer ${token}`,
  };
  return fetch(url, options);
};

// Usage
_streamOrAuthSse() {
  const url = `${this.httpUrl}/sse`;
  const options = {
    method: 'GET',
    headers: {},
  };
  return fetchWithAuth(url, options);
}

Notes

The provided example is a hypothetical implementation and may require modifications to fit the actual codebase. The fix may also depend on the specific requirements of the MCP server and the OAuth flow.

Recommendation

Apply a workaround by modifying the StreamableHTTPClientTransport to include the OAuth token in the Authorization header for the SSE GET request, as this is the most direct solution to the problem.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING