feat(mcp): add .well-known OAuth discovery endpoints - #8263
Conversation
842ebdb to
7bcc293
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8263 +/- ##
==========================================
+ Coverage 76.08% 76.11% +0.02%
==========================================
Files 445 447 +2
Lines 23994 24011 +17
Branches 6405 6405
==========================================
+ Hits 18255 18275 +20
+ Misses 5739 5736 -3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Add RFC 8414 authorization-server metadata and RFC 9728 protected-resource metadata under /.well-known so MCP clients can auto-discover the OAuth endpoints and the MCP resource URL. Public, license-tier independent. Ref #7431
7bcc293 to
f67ce0f
Compare
cstns
left a comment
There was a problem hiding this comment.
oauth-protected-resource won't scale past the one resource
Two things about this one, they're related and I think the same fix sorts out both.
1. One document can only describe one resource. RFC 9728 has no "multiple resources" shape, so the document describes exactly one resource identifier, which
here is hardcoded to ${base_url}/mcp. As soon as there's a second protected resource (per team MCP endpoint, a separate REST resource, whatever) there's nowhere
to put it and the root document has to pick a winner.
2. The document is at the wrong path for the resource it describes. RFC 9728 §3.1 uses path insertion, so metadata for https://host/mcp belongs at
https://host/.well-known/oauth-protected-resource/mcp. The bare root path is reserved for a resource whose identifier is exactly https://host. Right now a
client fetching the root gets back resource: "https://host/mcp", which doesn't match what it asked for, and stricter clients reject that mismatch. Current MCP
clients will probably still work because the spec has them try the path inserted URL first and fall back to the root, so we're leaning on the fallback rather than
on being correct.
Might be worth having each protected resource serve its own document at its own path:
/.well-known/oauth-protected-resource/mcp # owned by the mcp plugin
/.well-known/oauth-protected-resource # optional alias, if we want the fallback to keep working
That also happens to fix a third thing: this plugin is registered unconditionally in forge/routes/index.js, but /mcp only exists under EE with an active license
(forge/ee/routes/mcp/index.js:11). On OSS or an unlicensed install we're advertising a resource that 404s. If the document moves into the EE mcp plugin it gets
the right path and the right gating for free. The oauth-authorization-server half can stay where it is, one issuer per deployment, so the root path is correct
there and stays correct.
One more, since it's the bit clients actually rely on: RFC 9728 §5.1 and the MCP spec have the resource answer an unauthenticated request with
401 WWW-Authenticate: Bearer resource_metadata="https://host/.well-known/oauth-protected-resource/mcp"
so the client never has to guess a path at all. I couldn't find WWW-Authenticate anywhere in the stack. Happy for it to land in a later PR, just flagging it so it
doesn't fall through the gaps.
So, roughly
- Serve the doc at
/.well-known/oauth-protected-resource/mcp, keep the root path as an alias if we want the fallback - Move it into the EE mcp plugin so it's license gated alongside the resource it describes
- Have
/mcpreturn401+WWW-Authenticate: Bearer resource_metadata="..."(later PR is fine)
Move the RFC 9728 protected-resource document out of the root .well-known handler into the license-gated EE mcp plugin, so it is only advertised where the /mcp resource exists. Serve it at the path-inserted /.well-known/oauth-protected-resource/mcp (RFC 9728 3.1) with the bare path kept as an alias, and challenge unauthenticated /mcp requests with a WWW-Authenticate header pointing at that metadata.
|
Thanks for the thorough review, all three make sense and I have pushed a commit addressing them.
Tests cover the new metadata paths, the alias, and the challenge header. Happy to split point 3 into its own PR if you would prefer. |
Summary
Adds the OAuth discovery endpoints an MCP client reads before it can authenticate.
GET /.well-known/oauth-authorization-server(RFC 8414): advertises the authorize, token and registration endpoints, the supported grant types (authorization_code,refresh_token),code_challenge_methods_supported: ["S256"], andtoken_endpoint_auth_methods_supported: ["none"].GET /.well-known/oauth-protected-resource(RFC 9728): advertises the protected resource (/mcp) and its authorization server.Both endpoints are anonymous and derive their URLs from
base_url.First of a stack of three PRs adding OAuth2 for MCP agents.
Part of #7423. Closes #7431.