feat: per-request OAuth token injection with mid-session refresh for HTTP MCP servers

Replace the spawn-time static Authorization header for OAuth-managed HTTP
MCP servers with McpOAuthClient, a custom implementation of rmcp's
StreamableHttpClient trait that resolves the bearer token on every
request via load_or_refresh_mcp_token. Tokens that expire mid-session
now refresh transparently instead of failing tool calls until restart.

On a 401 for an injected token, the wrapper force-refreshes (identity-
aware: a still-unexpired copy of the rejected token is not trusted) and
retries exactly once, matching Claude Code / official SDK semantics.
Both *_with_max_sse_event_size trait methods are overridden to preserve
the inner client's SSE size enforcement, and the inner reqwest client
mirrors rmcp's default (pool_max_idle_per_host(0), no redirects).

SSE, stdio, and static-header HTTP paths are unchanged; startup
warning semantics (McpAuthRequired reasons) are preserved. Verified
live: mid-session backdated token refreshed transparently during an
active atlassian session.
This commit is contained in:
2026-08-14 12:33:46 -06:00
parent dcacb3a962
commit e1604c58ea
4 changed files with 1070 additions and 43 deletions
+4 -10
View File
@@ -1,7 +1,6 @@
use crate::mcp::oauth::McpTokenStatus;
use crate::mcp::{
ConnectedServer, JsonField, McpAuthReason, McpAuthRequired, McpServer, McpTransportType,
is_auth_required_error, oauth, spawn_mcp_server,
ConnectedServer, JsonField, McpAuthRequired, McpServer, McpTransportType,
is_auth_required_error, resolve_http_auth, spawn_mcp_server,
};
use anyhow::Result;
@@ -103,13 +102,8 @@ impl McpFactory {
return Ok(existing);
}
let token_status = if spec.is_remote() {
oauth::load_or_refresh_mcp_token(name).await
} else {
McpTokenStatus::NotAuthenticated
};
let auth_reason = McpAuthReason::from_token_status(&token_status);
let handle = spawn_mcp_server(spec, log_path, token_status.into_token())
let (auth, auth_reason) = resolve_http_auth(name, spec).await;
let handle = spawn_mcp_server(spec, log_path, auth)
.await
.map_err(|e| {
if is_auth_required_error(&e) {