Skip to content

fix(mcp): use direct fetch for resource_metadata URL from WWW-Authenticate#1120

Open
xzavrel wants to merge 1 commit into
moltis-org:mainfrom
xzavrel:fix/mcp-oauth-resource-metadata-direct-fetch
Open

fix(mcp): use direct fetch for resource_metadata URL from WWW-Authenticate#1120
xzavrel wants to merge 1 commit into
moltis-org:mainfrom
xzavrel:fix/mcp-oauth-resource-metadata-direct-fetch

Conversation

@xzavrel

@xzavrel xzavrel commented Jun 13, 2026

Copy link
Copy Markdown

Summary

Fixes #1119 — MCP OAuth fails with invalid_target for servers that include resource_metadata in their WWW-Authenticate header (Notion, Linear).

Problem

discover_and_register() passes the resource_metadata URL from WWW-Authenticate to fetch_resource_metadata(), which appends /.well-known/oauth-protected-resource — but the URL is already the complete metadata endpoint per RFC 9728 §5.1. This produces a broken double-suffix URL, the fetch fails silently, and the fallback path uses the wrong resource identifier (origin instead of the MCP server URL), causing the authorization server to reject with invalid_target.

Changes

File Change
crates/oauth/src/discovery.rs Add fetch_resource_metadata_direct() — fetches from an already-complete metadata URL without appending .well-known suffix
crates/oauth/src/lib.rs Export the new function
crates/mcp/src/auth.rs Use fetch_resource_metadata_direct in discover_and_register() when URL comes from WWW-Authenticate header

Tests added

  • fetch_resource_metadata_direct_success — Notion-style metadata at custom path
  • fetch_resource_metadata_direct_not_found — error handling
  • discovery_uses_www_authenticate_resource_metadata_directly — end-to-end discovery with WWW-Authenticate header returning correct resource

Validation

Completed

  • Verified Notion and Linear both send complete resource_metadata URLs in WWW-Authenticate
  • Verified the direct URL returns correct resource field
  • Confirmed servers without resource_metadata (Attio) are unaffected (fallback path unchanged)
  • Added unit tests for new function and integration test for the full discovery flow

Remaining

  • cargo test -p moltis-oauth -p moltis-mcp
  • cargo fmt --all -- --check
  • just lint

Manual QA

  1. Start Moltis gateway
  2. Add Notion MCP server (https://mcp.notion.com/mcp, Streamable HTTP)
  3. Verify browser opens Notion consent screen (not invalid_target error)
  4. Complete OAuth flow and verify MCP tools appear

@xzavrel xzavrel force-pushed the fix/mcp-oauth-resource-metadata-direct-fetch branch from 71d6144 to 2f4e837 Compare June 13, 2026 18:32
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a broken double-suffix bug in MCP OAuth discovery where a resource_metadata URL from a WWW-Authenticate header was being passed to fetch_resource_metadata(), which appended /.well-known/oauth-protected-resource again — producing an invalid URL, causing a silent fetch failure, and falling back to an origin-only resource identifier that authorization servers like Notion and Linear reject with invalid_target.

  • discovery.rs: Extracts a private fetch_resource_metadata_from_url() helper and adds a public fetch_resource_metadata_direct() that calls it without constructing the .well-known suffix, eliminating the previous duplication concern.
  • auth.rs: Routes the WWW-Authenticate path through fetch_resource_metadata_direct, while keeping the existing fallback path (no WWW-Authenticate header) unchanged.
  • Three new tests cover the new function's happy path, 404 error handling, and an end-to-end discover_and_register flow with a Notion-style WWW-Authenticate header that asserts both the correct mock endpoint is hit and the resource value comes from the metadata body rather than the origin.

Confidence Score: 5/5

Safe to merge — the change is a targeted, well-scoped fix that corrects a broken URL construction on one specific code path without touching any other paths.

The root cause (double .well-known suffix) is clearly identified and correctly fixed. The shared helper fetch_resource_metadata_from_url eliminates any drift risk. The fallback path (no WWW-Authenticate header) is fully unchanged. Three new tests, including an end-to-end integration test that would have caught the original bug, all cover the critical behavior. No pre-existing logic was reorganized or removed.

No files require special attention. The PR description notes that cargo test, cargo fmt, and just lint have not yet been run locally — confirming those pass before merging is prudent.

Important Files Changed

Filename Overview
crates/oauth/src/discovery.rs Extracts shared fetch_resource_metadata_from_url helper and adds public fetch_resource_metadata_direct. Refactoring is clean — no duplication, previous review concern addressed. Two new unit tests are correct.
crates/mcp/src/auth.rs Swaps fetch_resource_metadata for fetch_resource_metadata_direct on the WWW-Authenticate path. Fallback (no header) is unchanged. Integration test correctly validates the fix end-to-end.
crates/oauth/src/lib.rs Exports fetch_resource_metadata_direct — one-line addition, no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as McpOAuthProvider
    participant WA as WWW-Authenticate Header
    participant RM as Resource Metadata Endpoint
    participant AS as Authorization Server
    participant Reg as Registration Endpoint

    Client->>WA: Parse resource_metadata URL
    Note over Client,WA: URL is already complete (e.g. /.well-known/oauth-protected-resource/mcp)

    alt WWW-Authenticate has resource_metadata
        Client->>RM: GET resource_metadata URL directly (fetch_resource_metadata_direct)
        Note over Client,RM: FIX: no /.well-known suffix appended
        RM-->>Client: "{resource: "/mcp", authorization_servers: [...]}"
    else No WWW-Authenticate
        Client->>RM: "GET {server_url}/.well-known/oauth-protected-resource (fetch_resource_metadata)"
        RM-->>Client: metadata or error → retry at origin
    end

    Client->>AS: GET /.well-known/oauth-authorization-server
    AS-->>Client: AS metadata (auth/token endpoints)

    Client->>Reg: POST /register
    Reg-->>Client: client_id, client_secret

    Client-->>Client: return (client_id, …, resource from metadata)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as McpOAuthProvider
    participant WA as WWW-Authenticate Header
    participant RM as Resource Metadata Endpoint
    participant AS as Authorization Server
    participant Reg as Registration Endpoint

    Client->>WA: Parse resource_metadata URL
    Note over Client,WA: URL is already complete (e.g. /.well-known/oauth-protected-resource/mcp)

    alt WWW-Authenticate has resource_metadata
        Client->>RM: GET resource_metadata URL directly (fetch_resource_metadata_direct)
        Note over Client,RM: FIX: no /.well-known suffix appended
        RM-->>Client: "{resource: "/mcp", authorization_servers: [...]}"
    else No WWW-Authenticate
        Client->>RM: "GET {server_url}/.well-known/oauth-protected-resource (fetch_resource_metadata)"
        RM-->>Client: metadata or error → retry at origin
    end

    Client->>AS: GET /.well-known/oauth-authorization-server
    AS-->>Client: AS metadata (auth/token endpoints)

    Client->>Reg: POST /register
    Reg-->>Client: client_id, client_secret

    Client-->>Client: return (client_id, …, resource from metadata)
Loading

Reviews (2): Last reviewed commit: "fix(mcp): use direct fetch for resource_..." | Re-trigger Greptile

Comment thread crates/oauth/src/discovery.rs
…icate

When the WWW-Authenticate header contains a resource_metadata parameter
(RFC 9728 §5.1), the URL is already the complete metadata endpoint.
Passing it through fetch_resource_metadata() incorrectly appends another
/.well-known/oauth-protected-resource suffix, producing a broken URL.

This causes the resource_metadata branch to fail silently, and the
fallback path discovers the origin-level resource metadata which returns
the wrong resource identifier (origin instead of the MCP server URL).
The authorization server then rejects the request with invalid_target.

Add fetch_resource_metadata_direct() that fetches from an already-known
URL without appending the .well-known suffix. Use it in
discover_and_register() for URLs extracted from WWW-Authenticate headers.

Fixes moltis-org#1119
@penso

penso commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@greptileai review

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.

[Bug]: MCP OAuth fails with invalid_target for servers using resource_metadata in WWW-Authenticate (Notion, Linear)

2 participants