fix: use InvalidTokenError instead of generic Error in MockTokenVerifier#138
Merged
pcarleton merged 2 commits intomodelcontextprotocol:mainfrom Feb 13, 2026
Merged
Conversation
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>
4 tasks
pcarleton
approved these changes
Feb 13, 2026
Member
|
thanks @jdmaturen ! |
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MockTokenVerifier.verifyAccessToken()throwsnew Error('Invalid token')for unrecognized tokens, but the SDK'srequireBearerAuthmiddleware only convertsInvalidTokenErrorinstances to HTTP 401 responses. GenericErrorfalls through as HTTP 500.InvalidTokenErrorfrom@modelcontextprotocol/sdk/server/auth/errors.jsand 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
Errorthrow in the verifier.Test plan
Made with Cursor