SIGN IN SIGN UP

feat(mcp): add embedded OAuth 2.1 authorization (#802)

* docs: design embedded MCP OAuth flow

* docs: plan embedded MCP OAuth implementation

* test(mcp): define OAuth HTTP flow contract

* test(mcp): define OAuth storage contract

* feat(mcp): add OAuth domain types

* feat(mcp): persist OAuth grants and tokens

* feat(mcp): add embedded OAuth authorization server

* feat(mcp): add OAuth configuration defaults

* feat(mcp): wire OAuth configuration and routes

* feat(mcp): mount OAuth before global basic auth

* docs(mcp): add OAuth environment settings

* fix(mcp): harden OAuth authorization UX

* docs(mcp): document OAuth and Claude connector setup

* chore: keep MCP OAuth PR focused

* chore: remove implementation planning artifact

* test(mcp): cover OAuth PKCE and redirect attacks

* ci: verify MCP OAuth branch

* ci: trigger MCP OAuth verification

* chore: remove temporary MCP OAuth verification workflow

* chore: remove temporary MCP OAuth verification trigger

* fix(mcp): harden embedded OAuth authorization

- stop invalid authorization requests before issuing codes and return safe OAuth errors
- bound dynamic client registration storage and prune expired OAuth state
- support native loopback ephemeral ports while preserving exact redirect checks
- exclude the authorization endpoint from CORS and cover security regressions

* fix(mcp): resolve OAuth review feedback

* fix(mcp): close OAuth body-cap route-alias bypass

The header-time OAuth body cap compared the raw request URI byte-for-byte,
but Fiber runs with CaseSensitive and StrictRouting disabled, so
`/oauth/token/`, `/OAUTH/TOKEN`, and absolute-form targets all reach the
same handlers while missing the comparison and falling back to the
application's ~100 MB global BodyLimit.

Derive the match key from the same fasthttp URI parser Fiber uses, then
normalize it the way Fiber's router does (ASCII lowercase, trailing
slashes stripped). Regression tests cover trailing-slash, case, and
absolute-form variants for all three OAuth POST routes, plus a guard that
non-OAuth routes keep the app-wide limit.

Also reject `http://localhost:PORT/...` native redirects: RFC 8252 §8.3
requires a loopback IP literal because resolver or hosts-file changes can
point `localhost` at a non-loopback interface. Loopback IP literals were
already the only form granted the ephemeral-port exception.

* fix(mcp): enhance OAuth localhost redirect handling

Updated the OAuth implementation to accept `localhost` as a valid redirect URI for native clients with explicit ports, aligning with Claude Code compatibility. Adjusted the authorization page to reflect the correct redirect behavior based on whether the redirect is local or external. Improved CSP to allow form actions to the specified redirect origin. Added tests to ensure proper handling of localhost and loopback IP redirects, maintaining strict matching requirements for authorization requests.

* test(mcp): make OAuth requests context-aware

- construct Claude DCR and authorization requests with the test context
- satisfy noctx without changing OAuth compatibility coverage
A
Aldino Kemal committed
eeb30bb632bb12716643695266fcb2894c390e48
Parent: 0427b9f
Committed by GitHub <noreply@github.com> on 8/22/2026, 8:51:28 AM