Skip to content

fix: static token rejected by requireBearerAuth (missing expiresAt)#60

Merged
aliasunder merged 1 commit into
mainfrom
claude/fix-static-token-expiresat
May 20, 2026
Merged

fix: static token rejected by requireBearerAuth (missing expiresAt)#60
aliasunder merged 1 commit into
mainfrom
claude/fix-static-token-expiresat

Conversation

@aliasunder
Copy link
Copy Markdown
Owner

Summary

Real auth bug, surfaced while bridging the HTTP server to stdio for Glama's introspector. A static MCP_AUTH_TOKEN bearer is rejected with 401 at the Express layer, even when the token is correct.

Root cause (confirmed against the SDK source)

@modelcontextprotocol/sdk requireBearerAuth enforces:

if (typeof authInfo.expiresAt !== 'number' || isNaN(authInfo.expiresAt)) {
    throw new InvalidTokenError('Token has no expiration time');   // → 401
}

But verifyAccessToken's static-token fast path returned { token, clientId: "static", scopes: ["vault"] } with no expiresAt → always 401 for the static bearer. The JWT/OAuth path sets expiresAt, so E2E (tested via OAuth) passed and this never showed. The static-bearer path — documented for deploy/local and CLI tools — was never exercised by a real client.

Evidence

A curl straight to /mcp with Authorization: Bearer <token> (bypassing the bridge) returned 401, while the server's MCP_AUTH_TOKEN was confirmed to be exactly the token sent.

Fix

Give the static token a far-future numeric expiresAt (now + 10y, recomputed per request) so it satisfies requireBearerAuth while staying effectively perpetual. Added a test asserting expiresAt is a future number (the old test only checked clientId/scopes/token, which is exactly why the gap slipped through).

Validation

  • 497 / 497 tests pass (added 1)
  • lint + tsc --noEmit clean

Impact / follow-on

  • Fixes static-bearer auth for all direct-Express consumers (deploy/local, CLI), not just Glama.
  • Glama's introspection build pulls from source (git clone + checkout latest main), so once merged a re-run will pick it up.
  • For the published image (deploy/local users), this needs a v0.15.5 release to ship the fix into GHCR.

Generated by Claude Code

The MCP SDK's requireBearerAuth rejects any AuthInfo whose expiresAt is
not a number ("Token has no expiration time" → 401). verifyAccessToken's
static-token fast path returned { token, clientId, scopes } with no
expiresAt, so a static MCP_AUTH_TOKEN bearer was always 401'd at the
Express layer. This went unnoticed because E2E was exercised via OAuth
(JWT tokens carry expiresAt); the static-bearer path — documented for
deploy/local and CLI tools — was never hit by a real client.

Surfaced while bridging the HTTP server to stdio for Glama's introspector:
a direct curl to /mcp with the correct Bearer token returned 401 while
the server's MCP_AUTH_TOKEN was confirmed correct.

Fix: give the static token a far-future numeric expiresAt (now + 10y,
recomputed per request) so it satisfies requireBearerAuth while staying
effectively perpetual. Added a test asserting expiresAt is a future number.
@aliasunder aliasunder merged commit 7d89b8c into main May 20, 2026
5 checks passed
@aliasunder aliasunder deleted the claude/fix-static-token-expiresat branch May 20, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants