Skip to content

fix(oauth-proxy): handle MCP servers without Protected Resource Metadata#2124

Merged
viktormarinho merged 6 commits intomainfrom
fix/oauth-proxy-fallback-for-partial-oauth-servers
Jan 2, 2026
Merged

fix(oauth-proxy): handle MCP servers without Protected Resource Metadata#2124
viktormarinho merged 6 commits intomainfrom
fix/oauth-proxy-fallback-for-partial-oauth-servers

Conversation

@vibegui
Copy link
Copy Markdown
Contributor

@vibegui vibegui commented Dec 31, 2025

Servers like Apify support OAuth but don't implement RFC 9728 Protected Resource Metadata (/.well-known/oauth-protected-resource returns 404). They do expose /.well-known/oauth-authorization-server at the root and return WWW-Authenticate header on 401 responses.

This commit adds fallback handling:

  1. protectedResourceMetadataHandler: When origin returns 404 for the metadata endpoint, check if it supports OAuth via WWW-Authenticate header and generate synthetic metadata pointing to our proxy.

  2. getOriginAuthServer: When Protected Resource Metadata is unavailable or has empty authorization_servers, fall back to the origin's root.

  3. /oauth-proxy/:connectionId/* handler: Same fallback to origin root when Protected Resource Metadata doesn't exist.

Added tests:

  • Synthetic metadata generation when origin supports OAuth via WWW-Authenticate
  • 404 passthrough when origin doesn't support OAuth at all
  • Fallback to origin root for auth server discovery

What is this contribution about?

Describe your changes and why they're needed.

Screenshots/Demonstration

Add screenshots or a Loom video if your changes affect the UI.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

Summary by cubic

Makes the OAuth proxy work with MCP servers that support OAuth but don’t expose RFC 9728 Protected Resource Metadata (e.g., Apify). Adds fallback discovery and synthetic metadata so auth flows still work.

  • Bug Fixes
    • Generate synthetic Protected Resource Metadata when origin returns 404/401/406 but includes WWW-Authenticate, pointing to our proxy.
    • Fall back to the origin root for auth server discovery when metadata is missing or authorization_servers is empty (including /oauth-proxy/:connectionId/*).
    • Pass through 404 when the origin doesn’t support OAuth (no WWW-Authenticate).
    • Added tests for synthetic metadata, 404 passthrough, origin-root fallback, and 406 handling.

Written for commit ecfcce8. Summary will update on new commits.


Note

Improves OAuth compatibility with MCP servers that don’t expose RFC 9728 metadata.

  • Adds NO_METADATA_STATUSES and updates fetchProtectedResourceMetadata to try multiple well-known paths and treat 404/401/406 as "no metadata"
  • Introduces checkOriginSupportsOAuth to detect OAuth support via WWW-Authenticate and generate synthetic resource metadata pointing to our proxy
  • Updates getOriginAuthServer to fall back to origin root when authorization_servers is missing/empty
  • Updates /oauth-proxy/:connectionId/* handling in app.ts to use the new fallback discovery
  • Keeps URL rewriting for resource and authorization_servers to proxy endpoints
  • Adds unit and E2E tests for synthetic metadata generation, 404 passthrough when no OAuth, and fallback to origin-root discovery; expands server list (e.g., Grain, Apify)

Written by Cursor Bugbot for commit ecfcce8. This will update automatically on new commits. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the MCP Gateway benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

Release Options

Should a new version be published when this PR is merged?

React with an emoji to vote on the release type:

Reaction Type Next Version
👍 Prerelease 1.0.9-alpha.1
🎉 Patch 1.0.9
❤️ Minor 1.1.0
🚀 Major 2.0.0

Current version: 1.0.8

Deployment

  • Deploy to production (triggers ArgoCD sync after Docker image is published)

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/mesh/src/api/app.ts">

<violation number="1" location="apps/mesh/src/api/app.ts:229">
P1: Fallback to origin root doesn&#39;t trigger when Protected Resource Metadata exists but has empty/missing `authorization_servers`. If `resourceRes.ok` is true but `authorization_servers` is undefined or empty, the code returns 404 instead of falling back. Consider restructuring to match `getOriginAuthServer()` in `oauth-proxy.ts` which falls through to the fallback when `authServer` is falsy.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mesh/src/api/app.ts
Servers like Apify support OAuth but don't implement RFC 9728 Protected
Resource Metadata (/.well-known/oauth-protected-resource returns 404).
They do expose /.well-known/oauth-authorization-server at the root and
return WWW-Authenticate header on 401 responses.

This commit adds fallback handling:

1. protectedResourceMetadataHandler: When origin returns 404 for the
   metadata endpoint, check if it supports OAuth via WWW-Authenticate
   header and generate synthetic metadata pointing to our proxy.

2. getOriginAuthServer: When Protected Resource Metadata is unavailable
   or has empty authorization_servers, fall back to the origin's root.

3. /oauth-proxy/:connectionId/* handler: Same fallback to origin root
   when Protected Resource Metadata doesn't exist.

Added tests:
- Synthetic metadata generation when origin supports OAuth via WWW-Authenticate
- 404 passthrough when origin doesn't support OAuth at all
- Fallback to origin root for auth server discovery
@vibegui vibegui force-pushed the fix/oauth-proxy-fallback-for-partial-oauth-servers branch from e0b734f to 90b3cb2 Compare January 2, 2026 15:25
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/mesh/src/api/routes/oauth-proxy.ts">

<violation number="1" location="apps/mesh/src/api/routes/oauth-proxy.ts:332">
P2: Duplicate constant definition. `RETRY_STATUSES` and `NO_METADATA_STATUSES` contain identical values and serve the same purpose. Consider extracting this to a shared module-level constant to avoid maintenance issues if the list needs updating.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mesh/src/api/routes/oauth-proxy.ts Outdated
// If origin returns 404, 401, or 406, check if it still supports OAuth via WWW-Authenticate
// Many servers (like Apify, Grain) support OAuth but don't implement RFC 9728 metadata
// 406 is included because some servers return 406 Not Acceptable for .well-known paths
const NO_METADATA_STATUSES = [404, 401, 406];
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jan 2, 2026

Choose a reason for hiding this comment

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

P2: Duplicate constant definition. RETRY_STATUSES and NO_METADATA_STATUSES contain identical values and serve the same purpose. Consider extracting this to a shared module-level constant to avoid maintenance issues if the list needs updating.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/oauth-proxy.ts, line 332:

<comment>Duplicate constant definition. `RETRY_STATUSES` and `NO_METADATA_STATUSES` contain identical values and serve the same purpose. Consider extracting this to a shared module-level constant to avoid maintenance issues if the list needs updating.</comment>

<file context>
@@ -323,9 +326,11 @@ const protectedResourceMetadataHandler = async (c: {
+    // If origin returns 404, 401, or 406, check if it still supports OAuth via WWW-Authenticate
+    // Many servers (like Apify, Grain) support OAuth but don&#39;t implement RFC 9728 metadata
+    // 406 is included because some servers return 406 Not Acceptable for .well-known paths
+    const NO_METADATA_STATUSES = [404, 401, 406];
+    if (!response.ok &amp;&amp; NO_METADATA_STATUSES.includes(response.status)) {
       const wwwAuth = await checkOriginSupportsOAuth(connectionUrl);
</file context>

✅ Addressed in cc5d6b2

@viktormarinho viktormarinho merged commit 78825be into main Jan 2, 2026
6 checks passed
@viktormarinho viktormarinho deleted the fix/oauth-proxy-fallback-for-partial-oauth-servers branch January 2, 2026 16:54
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