feat(mcp): MCP OAuth 2.1 client for tool servers#1196
Open
thotam wants to merge 1 commit into
Open
Conversation
b736263 to
c836588
Compare
Implements a complete MCP OAuth 2.1 authorization flow for tool servers that
require user-delegated access, covering all layers from DB to UI.
## Core OAuth package (internal/mcp/oauth/)
- discovery.go: RFC 9728 protected-resource → RFC 8414 AS metadata → OIDC
fallback chain with 5-min in-memory cache and InvalidateCache()
- dcr.go: RFC 7591 Dynamic Client Registration with response size guard
- flow.go: PKCE (S256) authorization code flow — StartFlow(), ExchangeCode(),
ClientCredentials(), auto-cleanup of expired flows; carries AS issuer through
PendingFlow for status display
- refresher.go: OAuthTokenProvider with in-memory token cache, automatic refresh
on expiry, per-user vs global slot isolation, InvalidateCache/InvalidateServer
## Database
- migrations/000074 + SQLite schema: mcp_oauth_tokens with AES-256-GCM encrypted
access/refresh tokens, partial unique index for global vs per-user rows,
ON DELETE CASCADE from mcp_servers
## Store layer
- store.MCPOAuthTokenStore: Upsert, Get/GetUser, Delete/DeleteUser, and
DeleteServerOAuthTokens (purge all rows for a server)
- PostgreSQL + SQLite implementations
## HTTP handler (internal/http/mcp_oauth.go) — 5 endpoints
- POST /v1/mcp/oauth/start — discovery + optional DCR + PKCE redirect URL;
client_credentials completes server-side (no redirect) and returns completed=true
- GET /v1/mcp/oauth/callback — exchange code, persist token, publish WS event;
payload built via json.Marshal (no reflected XSS via error_description)
- GET /v1/mcp/oauth/status/{id}, DELETE /v1/mcp/oauth/token/{id} — admin-gated
- POST /v1/mcp/oauth/discover/{id} — on-demand discovery probe
- All outbound calls go through the SSRF-safe client with pinned IPs
## Gateway / WebSocket
- pkg/protocol/mcp_events.go: EventMCPOAuthComplete routed only to the initiating
user (admins in-tenant included); fail-closed across tenants
## Agent loop
- getUserMCPTools() injects Authorization: Bearer from OAuthTokenProvider; on a
401 for OAuth servers it purges the cached token so the next turn re-resolves
## Stale-token cleanup on reconfigure
- handleUpdateServer purges all OAuth tokens (global + per-user), drops the
refresher cache, and evicts the pool when a server's URL or OAuth config
(client_id / endpoints / grant_type / scope / auth_type) changes — so the
status UI and agent never use a token minted for the old resource/AS
## Web UI (ui/web/)
- MCPOAuthDialog (WS-driven), unified user-credentials dialog, OAuth settings
fields; handles the no-redirect client_credentials completion
## Tests
- internal/mcp/oauth/*_test.go: discovery cache, PKCE, DCR, refresher
- internal/http/mcp_oauth_test.go + mcp_update_oauth_purge_test.go: routes, auth
gating, WS event, purge-on-URL/OAuth-config-change
- tests/integration: store + encryption + tenant isolation, E2E start→callback,
DeleteServerOAuthTokens
- internal/gateway/event_filter_test.go, internal/agent/loop_mcp_user_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a complete MCP OAuth 2.1 authorization flow so agents can call MCP tool servers that require user-delegated access (RFC 9728 discovery → 8414/OIDC metadata → 7591 DCR → PKCE S256 / refresh / client_credentials), with encrypted token storage, per-user vs global isolation, SSRF-safe outbound calls, and automatic cleanup of stale tokens when a server's URL or OAuth config changes.
Type
main)Target Branch
devChecklist
go build ./...passesgo build -tags sqliteonly ./...passes (if Go changes)go vet ./...passesgo test -race ./...—-raceunavailable locally (no cgo/gcc on Windows dev box). Ran without-race:internal/mcp/oauth,internal/http,internal/gateway,internal/agentall pass. (Two pre-existingstore/pgTestBuildSkillInfo*failures are Windows path-separator only, unrelated to this PR; CI runs-raceon Linux.)cd ui/web && pnpm build$1, $2(PG) /?(SQLite) — no string concatinternal/i18ncatalogs +ui/web/src/i18n/locales/{en,vi,zh}internal/upgrade/version.go(RequiredSchemaVersion → 74; new migration000074_mcp_oauth_tokens)Test Plan
Automated
internal/mcp/oauth/*_test.go— discovery fallback + 5-min cache, PKCE S256 math, RFC 7591 DCR, refresher (cache/expiry/refresh, per-user vs global,InvalidateServer).internal/http/mcp_oauth_test.go— all 5 endpoints, admin gating, WSmcp.oauth_completeevent, callback HTML.internal/http/mcp_update_oauth_purge_test.go— tokens purged on URL change & OAuth-config change; NOT purged on unrelated update.tests/integration/v3_mcp_oauth_*— store CRUD + AES-256-GCM round-trip + tenant isolation,DeleteServerOAuthTokens, E2Estart → callback → DBvia httptest.internal/gateway/event_filter_test.go—mcp.oauth_completerouted only to initiating user / in-tenant admins, fail-closed across tenants.internal/agent/loop_mcp_user_test.go— Bearer token injection + graceful expiry + 401 cache purge.Manual
client_credentialsgrant completes server-side (no popup) and reports authorized.mcp_oauth_tokens, pool reconnects.goclaw migrate upapplies000074cleanly on PostgreSQL; SQLite desktop build starts.Notes for reviewers
605d1e69); 64 files, +6712/−126.json.Marshal(no reflected XSS fromerror_description).