Skip to content

feat(router): support multiple OAuth authorization servers for MCP - #3148

Merged
asoorm merged 9 commits into
mainfrom
ahmet/router-613-mcp-support-multiple-oauth-authorization-servers-for-mcp
Aug 12, 2026
Merged

feat(router): support multiple OAuth authorization servers for MCP#3148
asoorm merged 9 commits into
mainfrom
ahmet/router-613-mcp-support-multiple-oauth-authorization-servers-for-mcp

Conversation

@asoorm

@asoorm asoorm commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Configure multiple OAuth authorization servers using YAML or environment variables.
    • OAuth metadata advertises all configured authorization servers.
    • Tokens from configured servers authenticate successfully; unknown issuers are rejected.
    • Existing single-server configurations remain supported, with duplicate and empty URLs handled automatically.
    • The single-server setting is now deprecated in favor of the multiple-server option.
  • Documentation

    • Added guidance for multiple authorization servers, JWKS, token validation, and metadata discovery.
    • Updated OAuth quickstart examples.
  • Tests

    • Added coverage for configuration, metadata discovery, authentication, and issuer validation.

Problem

The router MCP server supports OAuth authentication. The config option mcp.oauth.authorization_server_url accepts one authorization server URL. The router advertises this one URL to MCP clients through the RFC 9728 Protected Resource Metadata endpoint. Platform teams with more than one identity provider cannot advertise all of them.

Closes ROUTER-613.

Solution

This PR adds the config option mcp.oauth.authorization_server_urls. The option accepts a list of authorization server URLs. The router advertises all configured servers in the metadata endpoint. MCP clients select one of the advertised servers for authorization.

The new option supersedes authorization_server_url. This PR deprecates the old option. The old option continues to work. See "Backward compatibility" below.

mcp:
  enabled: true
  server:
    base_url: 'https://mcp.example.com'
  oauth:
    enabled: true
    authorization_server_urls:
      - 'https://auth-a.example.com'
      - 'https://auth-b.example.com'
    jwks:
      - url: 'https://auth-a.example.com/.well-known/jwks.json'
      - url: 'https://auth-b.example.com/.well-known/jwks.json'

The environment variable MCP_OAUTH_AUTHORIZATION_SERVER_URLS sets the same option. It accepts a comma-separated list.

Token validation logic is unchanged. The router validates each token against every configured jwks provider. This mechanism already supports multiple issuers. Configure one jwks entry for each authorization server.

Changes

  • router/pkg/config: Add the field AuthorizationServerURLs to MCPOAuthConfiguration. Add the method AuthorizationServers(). The method merges the single URL and the list. The single URL comes first. The method removes duplicate and empty entries.
  • router/pkg/config: Deprecate authorization_server_url. The schema marks the option as deprecated. The Go field carries a // Deprecated: comment.
  • router/pkg/mcpserver: The metadata response, the metadata endpoint registration check, and the startup log now use the merged list.
  • router/pkg/config/config.schema.json: Add the schema entry for the new option.
  • docs-website/router/mcp/oauth/configuration.mdx: Add a "Multiple Authorization Servers" section with an example config. Add the new option to the option table and the environment variable table. Mark the old option as deprecated.
  • docs-website/router/mcp/oauth/quickstart.mdx: Config examples use authorization_server_urls.

Backward compatibility

The option authorization_server_url is deprecated but works unchanged. Deprecation is a signal, not a removal. You can set both options together. The router merges them into one list. Editors that use the config schema show a deprecation hint.

Tests

  • Config unit tests: YAML parsing, environment variable parsing, and merge logic.
  • MCP server unit tests: the RFC 9728 metadata response for single, multiple, and mixed configurations.
  • E2E test with two OAuth test servers. The test asserts three behaviors:
    • The metadata endpoint advertises both servers.
    • Tokens from each configured issuer pass validation.
    • Tokens from an unknown issuer receive HTTP 401.
  • The OAuth test server helper now accepts a configurable JWKS key ID. Two test servers in one test no longer share the key ID test_rsa.

Notes for reviewers

JWKS key lookup matches the token kid per provider. Two issuers with the same kid and different keys can still fail validation. This limitation exists before this PR (see ROUTER-143). Identity providers use distinct key IDs in practice.

Checklist

@mintlify

mintlify Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
wundergraphinc 🟢 Ready View Preview Aug 8, 2026, 10:27 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f37e1b68-59dd-4a56-a16f-dc2851939437

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8afd9 and 184f9d0.

📒 Files selected for processing (1)
  • router-tests/protocol/mcp_oauth_e2e_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • router-tests/protocol/mcp_oauth_e2e_test.go

Walkthrough

Changes

The router now supports multiple MCP OAuth authorization servers. It merges legacy and plural configuration, advertises all configured servers, validates tokens from configured issuers, and rejects tokens from unknown issuers. Documentation and tests cover the new configuration.

Changes

Multiple OAuth authorization servers

Layer / File(s) Summary
Authorization-server configuration contract
router/pkg/config/config.go, router/pkg/config/config.schema.json, router/pkg/config/config_test.go, router/pkg/config/testdata/*, docs-website/router/mcp/oauth/*
Adds authorization_server_urls, environment-variable loading, URL merging with deduplication, schema validation, tests, and documentation.
Protected-resource metadata integration
router/pkg/mcpserver/server.go, router/pkg/mcpserver/protected_resource_metadata_test.go
Uses all configured authorization servers for logging, metadata route registration, and protected-resource metadata responses.
Multi-server OAuth validation
router-tests/testutil/oauth_server.go, router-tests/protocol/mcp_oauth_e2e_test.go
Supports configurable test-server key IDs and verifies metadata, authentication from configured issuers, and rejection of unknown issuers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding support for multiple OAuth authorization servers in the MCP router.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-ef3374683cdffb24ea5efc4b6e04ae2471e36ee1

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.53%. Comparing base (70cd61d) to head (648faa4).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3148      +/-   ##
==========================================
+ Coverage   62.40%   62.53%   +0.13%     
==========================================
  Files         263      263              
  Lines       31048    31060      +12     
==========================================
+ Hits        19375    19424      +49     
+ Misses      10163    10119      -44     
- Partials     1510     1517       +7     
Files with missing lines Coverage Δ
router/pkg/config/config.go 84.68% <100.00%> (+1.68%) ⬆️
router/pkg/mcpserver/server.go 75.08% <100.00%> (+4.52%) ⬆️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
router/pkg/config/config_test.go (1)

2506-2532: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test empty authorization server URLs.

AuthorizationServers removes empty entries. This table does not verify that contract. Add a case with empty values around valid URLs.

Proposed test case
+		{
+			name: "empty urls are removed",
+			config: MCPOAuthConfiguration{
+				AuthorizationServerURLs: []string{"", "https://auth-a.example.com", ""},
+			},
+			expected: []string{"https://auth-a.example.com"},
+		},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@router/pkg/config/config_test.go` around lines 2506 - 2532, Extend the
AuthorizationServers table-driven tests with a case containing empty
authorization server URL values before, between, or after valid URLs. Set the
expected result to include only the valid URLs in their preserved order,
verifying that AuthorizationServers removes empty entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@router-tests/protocol/mcp_oauth_e2e_test.go`:
- Around line 268-279: Update the “rejects tokens from an unknown authorization
server” test to create and sign the token with configured oauthServerA or
oauthServerB, then override its iss claim with oauthServerUnknown.Issuer(). Keep
the connection and unauthorized AuthError assertions unchanged so the test
isolates issuer validation rather than JWKS or signature failure.

---

Nitpick comments:
In `@router/pkg/config/config_test.go`:
- Around line 2506-2532: Extend the AuthorizationServers table-driven tests with
a case containing empty authorization server URL values before, between, or
after valid URLs. Set the expected result to include only the valid URLs in
their preserved order, verifying that AuthorizationServers removes empty
entries.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89f0fa7f-9892-4581-aa09-49f7c56d154e

📥 Commits

Reviewing files that changed from the base of the PR and between 5edbee2 and ae9bed9.

📒 Files selected for processing (11)
  • docs-website/router/mcp/oauth/configuration.mdx
  • docs-website/router/mcp/oauth/quickstart.mdx
  • router-tests/protocol/mcp_oauth_e2e_test.go
  • router-tests/testutil/oauth_server.go
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/config_test.go
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json
  • router/pkg/mcpserver/protected_resource_metadata_test.go
  • router/pkg/mcpserver/server.go

Comment thread router-tests/protocol/mcp_oauth_e2e_test.go
@asoorm
asoorm marked this pull request as ready for review August 9, 2026 06:48
@asoorm
asoorm requested review from a team as code owners August 9, 2026 06:48

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@asoorm
asoorm enabled auto-merge (squash) August 10, 2026 08:44

@fiam fiam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread router-tests/protocol/mcp_oauth_e2e_test.go Outdated
Comment thread router-tests/protocol/mcp_oauth_e2e_test.go
asoorm added 2 commits August 12, 2026 11:51
…tiple-oauth-authorization-servers-for-mcp' into ahmet/router-613-mcp-support-multiple-oauth-authorization-servers-for-mcp

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@asoorm
asoorm merged commit cd0da09 into main Aug 12, 2026
30 checks passed
@asoorm
asoorm deleted the ahmet/router-613-mcp-support-multiple-oauth-authorization-servers-for-mcp branch August 12, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants