You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sub-issue of #86. Bridges incoming MCP authorization requests into the existing upstream-IdP flow, with PKCE-S256 enforcement and short-lived authorization codes persisted in a TTL table.
Context
PR #89 (Stage 1) shipped the RFC 7591 Dynamic Client Registration endpoint and the persistent harper_oauth_mcp_clients table. PR #90 (Stage 2) shipped the well-known discovery documents that advertise authorization_endpoint. This stage builds the endpoint they point to: POST/GET /oauth/mcp/authorize. It does not mint tokens — that's Stage 4. It mints an authorization code that Stage 4 will exchange.
What this adds
/oauth/mcp/authorize request handler (mounted via handleMCPPost for POST and the OAuthResource dispatcher for GET — TBD which verb per spec)
mcp_auth_codes table declared in schema/oauth.graphql with @table(database: "oauth", expiration: 300) — Harper's native TTL handles auth code expiry; works across replicated nodes (no in-memory store needed)
Request validation:
client_id exists in harper_oauth_mcp_clients
redirect_uri exact-matches a registered URI for that client (RFC 6749 §3.1.2.4 + MCP 2025-06-18)
code_challenge_method is S256 (reject plain with invalid_request; OAuth 2.1 §7.5.2)
code_challenge present and well-formed
resource parameter present and canonical-form (RFC 8707 §2)
response_type=code only
Bridge to existing upstream OAuth flow — extends the existing CSRFTokenManager state payload to carry (client_id, resource, code_challenge, redirect_uri, scope) through the GitHub/Google/etc. callback in handleCallback. On successful upstream login, the callback mints an entry into mcp_auth_codes and redirects the user-agent to the MCP client's redirect_uri with code= and state=.
Error responses per OAuth spec — invalid_request, invalid_target, invalid_client, unsupported_response_type returned to the MCP client's redirect_uri as URL fragment params per OAuth 2.1
MCP OAuth Stage 3:
/oauth/mcp/authorizeendpointSub-issue of #86. Bridges incoming MCP authorization requests into the existing upstream-IdP flow, with PKCE-S256 enforcement and short-lived authorization codes persisted in a TTL table.
Context
PR #89 (Stage 1) shipped the RFC 7591 Dynamic Client Registration endpoint and the persistent
harper_oauth_mcp_clientstable. PR #90 (Stage 2) shipped the well-known discovery documents that advertiseauthorization_endpoint. This stage builds the endpoint they point to:POST/GET /oauth/mcp/authorize. It does not mint tokens — that's Stage 4. It mints an authorization code that Stage 4 will exchange.What this adds
/oauth/mcp/authorizerequest handler (mounted viahandleMCPPostfor POST and the OAuthResource dispatcher for GET — TBD which verb per spec)mcp_auth_codestable declared inschema/oauth.graphqlwith@table(database: "oauth", expiration: 300)— Harper's native TTL handles auth code expiry; works across replicated nodes (no in-memory store needed)client_idexists inharper_oauth_mcp_clientsredirect_uriexact-matches a registered URI for that client (RFC 6749 §3.1.2.4 + MCP 2025-06-18)code_challenge_methodisS256(rejectplainwithinvalid_request; OAuth 2.1 §7.5.2)code_challengepresent and well-formedresourceparameter present and canonical-form (RFC 8707 §2)response_type=codeonlyCSRFTokenManagerstate payload to carry(client_id, resource, code_challenge, redirect_uri, scope)through the GitHub/Google/etc. callback inhandleCallback. On successful upstream login, the callback mints an entry intomcp_auth_codesand redirects the user-agent to the MCP client'sredirect_uriwithcode=andstate=.invalid_request,invalid_target,invalid_client,unsupported_response_typereturned to the MCP client'sredirect_urias URL fragment params per OAuth 2.1Spec requirements (all MUST, MCP authorization spec 2025-06-18)
plainrejectedresourceparameter required on/authorize(RFC 8707)redirect_urimatching against registered valuesAcceptance
mcp_auth_codestable declared inschema/oauth.graphqlwith TTL viaexpiration: 300/oauth/mcp/authorizevalidatesclient_id, exactredirect_urimatch, S256 PKCE, canonicalresource,response_type=codeplainPKCE withinvalid_requestresourcewithinvalid_targetclient_idwithinvalid_clientredirect_uriwithinvalid_requestmcp_auth_codesbound to (client_id, resource, code_challenge, redirect_uri, user) withexpiration: 300redirect_uriwithcodeandstateonLoginlifecycle hook fires on MCP-initiated auth, same as for human OAuthDependencies
Out of scope
withMCPAuthwrapper that consumes those tokens (Stage 5)onMCPTokenIssuedhook (Stage 6)🤖 Generated with Claude Code