Skip to content

Conversation

@jdmaturen
Copy link

Summary

  • MockTokenVerifier.verifyAccessToken() throws new Error('Invalid token') for unrecognized tokens, but the SDK's requireBearerAuth middleware only converts InvalidTokenError instances to HTTP 401 responses. Generic Error falls through as HTTP 500.
  • This prevents clients from detecting authentication failures and initiating the OAuth refresh/re-auth flow during conformance testing.
  • Fix: import InvalidTokenError from @modelcontextprotocol/sdk/server/auth/errors.js and throw it instead.

How it was found

While building token refresh conformance scenarios (access token expires → client gets 401 → uses refresh_token grant), the mock server was returning 500 instead of the expected 401 for expired tokens. Traced to this generic Error throw in the verifier.

Test plan

  • All 77 existing tests pass
  • No new tests needed — this is a one-line fix to match the SDK's expected error type

Made with Cursor

The SDK's `requireBearerAuth` middleware only converts `InvalidTokenError`
instances to HTTP 401 responses. Generic `Error` instances fall through
as HTTP 500, which prevents clients from detecting authentication failures
and initiating the OAuth refresh/re-auth flow.

This was discovered while building token refresh conformance scenarios —
the mock server was returning 500 for expired/invalid tokens instead of
the expected 401.

Co-authored-by: Cursor <cursoragent@cursor.com>
jdmaturen added a commit to jdmaturen/conformance that referenced this pull request Feb 9, 2026
Add two new client auth conformance scenarios that test OAuth 2.1
refresh token behavior:

- `auth/token-refresh-basic`: Tests that clients use the refresh_token
  grant to obtain a new access token when the current one expires
  (OAuth 2.1 §6). Server issues 2-second TTL access tokens + refresh
  token; client must detect 401, send grant_type=refresh_token, and
  use the new access token.

- `auth/token-refresh-rotation`: Same flow but the server rotates the
  refresh token on each use (OAuth 2.1 §6.1). Client must store the
  new refresh token and not reuse the old one.

Supporting changes:

- `createAuthServer`: Add `issueRefreshToken`, `rotateRefreshTokens`,
  `accessTokenExpiresIn`, and `onRefreshTokenRequest` options. Add
  full `grant_type=refresh_token` handler with token validation,
  rotation, and conformance checks.

- `createServer`: Add `perRequestServer` option to create a fresh MCP
  Server per request. Required for token refresh tests where requests
  span token expiry boundaries (the default behavior calls
  server.close() on response end, breaking subsequent requests).

- `mockTokenVerifier`: Add token expiration tracking (issuedAt,
  expiresIn per token). Expired tokens now throw InvalidTokenError
  with an INFO conformance check, enabling proper 401 responses.

- `spec-references`: Add OAUTH_2_1_REFRESH_TOKEN (§6) and
  OAUTH_2_1_TOKEN_ROTATION (§6.1) references.

- `everything-client`: Add `runTokenRefreshClient` that exercises the
  full refresh flow (connect → request → wait for expiry → request).

Depends on modelcontextprotocol#138 (InvalidTokenError fix).

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant