Skip to content

fix(auth): keep MCP sessions authorized past token expiry#3678

Open
juliusmarminge wants to merge 1 commit into
mainfrom
t3code/mcp-auth-token-refresh
Open

fix(auth): keep MCP sessions authorized past token expiry#3678
juliusmarminge wants to merge 1 commit into
mainfrom
t3code/mcp-auth-token-refresh

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Jul 3, 2026

Copy link
Copy Markdown
Member

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 from startSession/recoverSessionForThread in apps/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-code with 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_000 in McpSessionRegistry.ts), counted from mint time, with no refresh mechanism whatsoeverresolve() only bumped lastUsedAt for the separate 30-minute idle timeout, never the absolute expiresAt. 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 slides expiresAt forward to now + 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.
  • Bumped DEFAULT_MAXIMUM_LIFETIME_MS from 8h to 30 days (matching SessionStore'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:

  • a never-used credential still expires once it passes the maximum lifetime (regression guard for the cap itself)
  • a credential that's resolved repeatedly at short intervals survives well past what its original mint-time expiry would have been (the actual fix)

cd apps/server && vp test run src/auth src/mcp — 13 files / 79 tests passed.
vp run --filter t3 typecheck — passed (exit 0).

Caveats

  • This fixes the "still-actively-used session dies anyway" and "long-idle-but-legitimate session dies anyway" cases via sliding-window renewal + a much longer backstop cap. It does not add true OAuth-style token refresh/rotation to the MCP credential — the token itself never changes for the life of a thread's provider session, it's the same static bearer the agent process was launched with, just kept valid longer. That's consistent with how the credential is delivered today (static header at process spawn) and avoids touching the provider adapters.
  • If an environment/server process restarts, all in-memory McpSessionRegistry records are lost as before (revokeAll on 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.

resolve now slides expiresAt forward on each successful auth (max(existing, now + maximumLifetimeMs)) while still updating lastUsedAt, 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 resolve calls keeping a token valid past the original expiresAt.

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.resolve now updates expiresAt to max(previous expiresAt, now + maximumLifetimeMs) on every successful use, so actively-used sessions are never cut off by the original expiry.
  • DEFAULT_MAXIMUM_LIFETIME_MS is raised from 8 hours to 30 days; this now acts as a "maximum idle lifetime" rather than an absolute cap.
  • Behavioral Change: tokens for inactive sessions still expire, but tokens for active sessions now have a rolling expiry that resets on every resolve call.

Macroscope summarized 1efa8a3.

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>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e3620132-16e7-4147-b4e7-23bd558aecfb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/mcp-auth-token-refresh

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 3, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant