-
Notifications
You must be signed in to change notification settings - Fork 374
Description
Bug Report: Auth header not passed to SSE GET requests in StreamableHttpClientTransport
Describe the bug
rmcp does not pass the auth_header
configuration to SSE (Server-Sent Events) GET requests, causing authentication failures when connecting to MCP servers that require authentication for all requests per the MCP specification.
The MCP specification (https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) states that implementations should "Implement proper authentication for all connections", but rmcp only sends the auth header in POST requests, not in GET requests used for SSE streams.
To Reproduce
Steps to reproduce the behavior:
-
Connect to an MCP server that requires Bearer token authentication and returns a session ID (e.g., GitHub Copilot MCP server at
https://api.githubcopilot.com/mcp/
) -
Configure
StreamableHttpClientTransportConfig
with an auth header:
let config = StreamableHttpClientTransportConfig::with_uri("https://api.githubcopilot.com/mcp/")
.auth_header("github_pat_xxxxx");
- Initialize the client:
let client_info = ClientInfo {
protocol_version: ProtocolVersion::V_2025_03_26,
capabilities: ClientCapabilities::default(),
client_info: Implementation {
name: "test-client".to_string(),
title: None,
version: "1.0.0".to_string(),
website_url: None,
icons: None,
},
};
let client = client_info.serve(transport).await?;
- Attempt to list tools:
let tools = client.list_tools(Default::default()).await?;
- Observe error:
Transport closed
or401 Unauthorized
Expected behavior
The auth header should be sent with ALL HTTP requests (POST and GET), allowing the client to:
- Successfully initialize with session ID
- Attempt to open SSE stream with proper authentication
- Either succeed (if server supports SSE) or gracefully handle 405 Method Not Allowed
- Continue operating in stateless or session-based mode as appropriate
Root Cause
In src/transport/streamable_http_client.rs
, there are three locations where get_stream()
is called with None
for the auth parameter:
Location 1: Line ~381 - Initial SSE stream attempt
.get_stream(config.uri.clone(), session_id.clone(), None, None)
// ^^^^ should be config.auth_header.clone()
Location 2: Line ~190 - SSE reconnection
client.get_stream(uri, session_id, last_event_id, None)
// ^^^^ should pass auth_header
Location 3: Line ~474 - StreamableHttpClientReconnect struct
StreamableHttpClientReconnect {
client: self.client.clone(),
session_id: session_id.clone(),
uri: config.uri.clone(),
// Missing: auth_header field
}
Logs
Without the fix
ℹ Validating GitHub token from .env
Using token: github_pat...i8fB
ℹ Connecting to GitHub MCP server...
Attempting connection...
Connection established! Listing tools...
DEBUG: list_tools failed: TransportClosed
Token validation failed
Error: Failed to list tools: Transport closed
Testing with curl (proper auth header)
# This works - returns 405 Method Not Allowed (expected, GitHub doesn't support SSE)
curl -I -X GET \
-H "Accept: text/event-stream" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
https://api.githubcopilot.com/mcp/
# HTTP/2 405
Testing without auth header (rmcp's current behavior)
# This fails - returns 401 Unauthorized
curl -I -X GET \
-H "Accept: text/event-stream" \
-H "Mcp-Session-Id: $SESSION_ID" \
https://api.githubcopilot.com/mcp/
# HTTP/2 401
Proposed Fix
Step 1: Add auth_header
field to struct
struct StreamableHttpClientReconnect<C> {
pub client: C,
pub session_id: Arc<str>,
pub uri: Arc<str>,
pub auth_header: Option<String>, // Add this field
}
Step 2: Pass auth header in retry_connection()
fn retry_connection(&mut self, last_event_id: Option<&str>) -> Self::Future {
let client = self.client.clone();
let uri = self.uri.clone();
let session_id = self.session_id.clone();
let auth_header = self.auth_header.clone(); // Add this line
let last_event_id = last_event_id.map(|s| s.to_owned());
Box::pin(async move {
client
.get_stream(uri, session_id, last_event_id, auth_header) // Pass auth_header
.await
})
}
Step 3: Update instantiations
Line ~381:
.get_stream(config.uri.clone(), session_id.clone(), None, config.auth_header.clone())
Lines ~387 and ~474:
StreamableHttpClientReconnect {
client: self.client.clone(),
session_id: session_id.clone(),
uri: config.uri.clone(),
auth_header: config.auth_header.clone(), // Add this field
}
Environment
- rmcp version: 0.7.0
- Rust version: 1.83+ (tested with rustc 1.83)
- Platform: Linux (WSL2), but issue is platform-independent
- Transport:
StreamableHttpClientTransport
with reqwest
Impact
- Severity: Medium/High
- Prevents connection to any MCP server requiring authentication on GET/SSE requests
- Violates MCP specification requirement for "proper authentication for all connections"
- Affects production use cases like GitHub Copilot MCP integration
Additional context
- This affects any MCP server that requires authentication for all requests, not just POST requests
- The issue is particularly problematic for servers that return session IDs, as rmcp will attempt SSE streams which fail due to missing auth
- Workaround: Use
[patch.crates-io]
with a locally patched version - Successfully validated fix against GitHub Copilot MCP server (
https://api.githubcopilot.com/mcp/
)