Skip to content

Conversation

@pcarleton
Copy link
Member

@pcarleton pcarleton commented Jan 14, 2026

Summary

Adds conformance testing for MCP server OAuth authentication implementations.

Design Philosophy

The MCP authorization spec separates the Authorization Server (AS) from the Resource Server (RS/MCP server). This reflects the reality that authorization is often better handled by dedicated tools (Auth0, Keycloak, better-auth, etc.) rather than reimplemented in every SDK.

This conformance suite tests MCP server (RS) implementations, not AS implementations. SDKs are not expected to provide a full AS - they just need to:

  1. Return 401 + WWW-Authenticate for unauthenticated requests
  2. Serve Protected Resource Metadata (PRM) pointing to an AS
  3. Validate Bearer tokens (via introspection or other means)

The conformance suite provides a fake AS for testing, so SDK authors can verify their RS implementation without setting up real auth infrastructure. Alternatively, servers can integrate with their own AS if they prefer.

What's New

CLI Commands

# Run auth tests against an MCP server
# The AS is spun up and URL passed via MCP_CONFORMANCE_AUTH_SERVER_URL environment variable. 
conformance server --suite auth --command 'npx path/to/test-server.ts'

# Run auth tests against an MCP server (with server providing AS)
conformance server --suite auth --url http://localhost:3000/mcp

# Interactive mode for servers requiring browser-based login
conformance server --suite auth --url http://localhost:3000/mcp --interactive

# Standalone fake AS for manual testing
conformance fake-auth-server [--port <port>]

New Files

  • src/fake-auth-server.ts - Standalone fake OAuth AS CLI
  • src/scenarios/server-auth/ - Server auth scenario directory
    • basic-dcr-flow.ts - Tests complete OAuth flow (401 → PRM → AS metadata → CIMD/DCR → Token → Auth'd request)
    • helpers/oauth-client.ts - Observation middleware and ConformanceOAuthProvider
    • spec-references.ts - RFC/spec references (2025-11-25)

Modified Files

  • src/index.ts - Extended server command with --suite auth, --interactive
  • src/runner/server.ts - Added runServerAuthConformanceTest() and startFakeAuthServer()
  • src/scenarios/client/auth/helpers/createAuthServer.ts - Added /introspect endpoint (RFC 7662), CIMD detection
  • src/types.ts - Added ClientScenarioOptions with interactive flag
  • package.json - Added fake-auth-server bin entry

Interactive Mode

For servers that integrate with their own AS requiring browser-based login, use --interactive:

conformance server --suite auth --url http://localhost:3000/mcp --interactive

This starts a callback server on port 3333 and displays the authorization URL for you to open in a browser.

CIMD vs DCR

The fake AS defaults to client_id_metadata_document_supported: true, making CIMD (URL-based client IDs) the preferred method over DCR.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Conformance Suite                             │
│                                                                  │
│  ┌──────────────┐     ┌──────────────────────────────────────┐  │
│  │   Fake AS    │     │  Checker Client (typescript-sdk)      │  │
│  │  (provided)  │◄────│  - Observes HTTP requests             │  │
│  │              │     │  - Generates conformance checks       │  │
│  └──────────────┘     └──────────┬───────────────────────────┘  │
│        ▲                         │                               │
│        │ PRM points here         │ Auth'd MCP requests          │
│        │ (or to own AS)          │                               │
│        │                         ▼                               │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              MCP Server Under Test (RS)                   │   │
│  │  - Returns 401 + WWW-Authenticate for unauth requests    │   │
│  │  - Serves PRM at /.well-known/oauth-protected-resource   │   │
│  │  - Validates Bearer tokens                                │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Conformance Checks

  • auth-401-response - Server returns 401 for unauthenticated requests
  • auth-www-authenticate-header - 401 includes WWW-Authenticate header with Bearer scheme
  • auth-resource-metadata-param - WWW-Authenticate includes resource_metadata parameter
  • auth-prm-discovery - PRM endpoint responds at .well-known/oauth-protected-resource
  • auth-prm-authorization-servers - PRM contains authorization_servers array
  • auth-as-metadata-discovery - AS metadata discovery works
  • auth-as-metadata-fields - AS metadata includes required fields (issuer, authorization_endpoint, token_endpoint, PKCE S256)
  • auth-dcr-registration - Dynamic Client Registration succeeds (when DCR used)
  • auth-dcr-response - DCR response contains client credentials
  • auth-token-request - Token acquisition succeeds
  • auth-token-response - Token response contains access_token
  • auth-authenticated-request - Authenticated MCP call succeeds
  • auth-flow-completion - Complete OAuth flow succeeded

Testing Example

# Start an MCP server with OAuth support
cd ~/code/mcp/typescript-sdk
pnpm exec tsx examples/server/src/simpleStreamableHttp.ts --oauth

# Run conformance tests (uses --interactive since example uses better-auth)
conformance server --suite auth --url http://localhost:3000/mcp --interactive

Related PRs

The runner regex expects the server to output the full MCP endpoint URL.
Updated auth-test-server to output http://localhost:PORT/mcp and the
runner to match URLs with /mcp suffix.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 14, 2026

Open in StackBlitz

npx https://pkg.pr.new/modelcontextprotocol/conformance/@modelcontextprotocol/conformance@105

commit: c34a019

- Add /introspect endpoint to fake auth server (RFC 7662)
- Replace custom bearer auth middleware with SDK's requireBearerAuth
- Auth-test-server now fetches AS metadata and uses introspection endpoint
- Removes ~50 lines of custom auth code in favor of SDK primitives
- Default clientIdMetadataDocumentSupported=true in fake auth server
- Add cimd-client-id check when URL-based client ID is detected
- Add clientMetadataUrl to ConformanceOAuthProvider for CIMD support
- Update test expectations to accept either CIMD or DCR
- Explicitly disable CIMD in token-endpoint-auth tests that require client_secret
- Update MCP Authorization spec refs from 2025-06-18 to 2025-11-25
- Fix anchor links for updated spec headings
- Simplify fake-auth-server.ts by using ServerLifecycle for all cases
- Add optional port parameter to ServerLifecycle.start()
- Add introspection endpoint to fake-auth-server output
…flags

- Remove --auth-url and --auth-command flags
- Reuse existing --url and --command patterns for auth suite
- --command with --suite auth spawns fake AS automatically
- --url with --suite auth tests against already-running server
Add support for testing servers that require browser-based login (like better-auth)
instead of auto-redirect.

When --interactive is specified:
- Starts a callback server on port 3333
- Prints the authorization URL for the user to open in browser
- Waits for the OAuth callback with the authorization code
- Continues the auth flow once the code is received

This enables testing third-party auth servers that use real login pages.
- Remove callback URL as constructor parameter
- Provider now handles redirect_uris internally via clientMetadata getter
- CALLBACK_URL is now a private constant in oauth-client.ts
…idation

- Add FAILURE when PRM discovery is missing or lacks authorization_servers
- Add FAILURE when AS metadata discovery is missing
- Add FAILURE when token request is missing
- Expand AS metadata validation to check:
  - issuer (required per RFC 8414)
  - authorization_endpoint (required for auth code flow)
  - token_endpoint (required)
  - code_challenge_methods_supported includes S256 (required for MCP PKCE)
@tobinsouth
Copy link

I think the fake AS is a great solution here and focusing on the RS is the right design choice (better than #64 !). If we do a AS specific conformance check, I think it should be separate tooling. Maybe something someone at Okta / Entra / Auth0 / Twillio / Arcade / WorkOS etc want to take on.

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.

3 participants