fix(auth): keep MCP sessions authorized past token expiry#3678
fix(auth): keep MCP sessions authorized past token expiry#3678juliusmarminge wants to merge 1 commit into
Conversation
McpSessionRegistry minted a per-thread MCP bearer credential once at provider-session start with a hard 8h maximum lifetime and no refresh path. The token is handed to the agent process as a static header, so once the absolute cap passed, the still-healthy session's MCP calls started failing 401 with no way to recover short of a manual re-authorization (observed 2026-07-03: "t3-code requires re-authorization (token expired)" mid-session). Have `resolve()` transparently slide a credential's expiry forward on every authenticated use, so actively-used sessions never approach the cap, and bump the default cap itself to 30 days (matching SessionStore's browser-session TTL) since the credential is already tightly scoped and explicitly revoked on session stop/error/shutdown. The existing 30-minute idle timeout still reclaims credentials for threads that go truly quiet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
ApprovabilityVerdict: Needs human review Changes to authentication credential lifetime and renewal behavior (extending token validity from 8 hours to 30 days) are security-sensitive and warrant human review, per standard guidance on auth-related changes. You can customize Macroscope's approvability policy. Learn more. |
Diagnosis
The desktop app's MCP server ("t3-code") authenticates agent-session traffic through
McpSessionRegistry(apps/server/src/mcp/McpSessionRegistry.ts). A per-thread bearer credential is minted once, at provider-session start (ProviderService.prepareMcpSession, called fromstartSession/recoverSessionForThreadinapps/server/src/provider/Layers/ProviderService.ts:217-224,400,593), and handed to the agent process as a static config value — e.g.mcp_servers.t3-codewith a bearer header/env var baked in at process launch (apps/server/src/provider/Layers/ClaudeAdapter.ts:3468-3475,apps/server/src/provider/Layers/CodexAdapter.ts:1405-1415).That credential previously carried a hard 8 hour absolute cap (
DEFAULT_MAXIMUM_LIFETIME_MS = 8 * 60 * 60 * 1_000inMcpSessionRegistry.ts), counted from mint time, with no refresh mechanism whatsoever —resolve()only bumpedlastUsedAtfor the separate 30-minute idle timeout, never the absoluteexpiresAt. Since the token is a static header the running agent process can't rotate on its own, any session that outlived 8 hours (a normal outcome for long coding sessions, overnight work, or scheduled/background runs) would have its MCP calls start failing with 401 out from under an otherwise-healthy session — surfacing to the user as "t3-code requires re-authorization (token expired)" (2026-07-03, ~09:16 UTC), fixable only by a manual re-authorization.Fix
apps/server/src/mcp/McpSessionRegistry.ts:resolve()now transparently re-issues the credential's expiry on every authenticated use — it slidesexpiresAtforward tonow + maximumLifetimeMs(never backward) instead of leaving it fixed at mint time. A session that keeps calling MCP tools never approaches the cap; the existing 30-minute idle timeout still reclaims credentials for threads that genuinely go quiet.DEFAULT_MAXIMUM_LIFETIME_MSfrom 8h to 30 days (matchingSessionStore's browser-session TTL) as a backstop for sessions that don't happen to call MCP tools often. This is safe to widen because the credential is already tightly scoped — random 256-bit token, single thread + provider instance, in-memory only, explicitly revoked on session stop/error/environment shutdown (revokeThread/revokeAll) — so the absolute cap wasn't providing much security value beyond what revocation + idle timeout already do; it was just killing healthy long sessions.No changes to the provider adapters, auth session/pairing stores, or DPoP/HTTP auth layers — this is scoped entirely to the MCP credential registry.
Tests
apps/server/src/mcp/McpSessionRegistry.test.ts— added:cd apps/server && vp test run src/auth src/mcp— 13 files / 79 tests passed.vp run --filter t3 typecheck— passed (exit 0).Caveats
McpSessionRegistryrecords are lost as before (revokeAllon shutdown) — that's pre-existing behavior and orthogonal to this bug.🤖 Generated with Claude Code
Note
Medium Risk
Touches MCP bearer credential lifetime and renewal in an auth path; scope is limited to in-memory registry behavior with existing idle timeout and revocation unchanged.
Overview
Fixes mid-session t3-code MCP 401s by changing how bearer credentials age in
McpSessionRegistry.resolvenow slidesexpiresAtforward on each successful auth (max(existing, now + maximumLifetimeMs)) while still updatinglastUsedAt, so long-running agent sessions that keep calling MCP tools are no longer cut off by a fixed mint-time deadline. The default maximum lifetime backstop moves from 8 hours to 30 days (documented as a cap for unused/stale credentials, not a hard session limit).Tests add coverage for unused credentials expiring at the cap and for repeated
resolvecalls keeping a token valid past the originalexpiresAt.Reviewed by Cursor Bugbot for commit 1efa8a3. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Renew MCP session credential expiry on each use to keep active sessions authorized
McpSessionRegistry.resolvenow updatesexpiresAttomax(previous expiresAt, now + maximumLifetimeMs)on every successful use, so actively-used sessions are never cut off by the original expiry.DEFAULT_MAXIMUM_LIFETIME_MSis raised from 8 hours to 30 days; this now acts as a "maximum idle lifetime" rather than an absolute cap.resolvecall.Macroscope summarized 1efa8a3.