feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers#4441
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview Introduces persisted workspace-scoped OAuth state via new Plumbs OAuth through runtime behavior: Reviewed by Cursor Bugbot for commit 43902b4. Configure here. |
Greptile SummaryThis PR adds OAuth 2.1 + PKCE support for outbound MCP servers, implementing the full consent flow via a browser popup (
Confidence Score: 5/5The PR is safe to merge. All the OAuth consent flow, token storage, and workspace ownership checks are properly implemented and the security issues from previous review rounds have been addressed. All write paths (POST upsert, PATCH, DELETE) verify workspace ownership before touching OAuth state or running token revocation. Tokens are encrypted at rest, PKCE verifiers are encrypted, state is stored as a SHA-256 hash, and the active-flow TTL is anchored to a dedicated stateCreatedAt column so token refreshes cannot extend it. The two remaining comments are style-level concerns with no impact on correctness or security. No files require special attention. apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx removed several useMemo/useCallback wrappers that could slow render cycles in large workspaces, but this is not a correctness issue. Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant Start as /api/mcp/oauth/start
participant Popup as OAuth Popup
participant AS as Authorization Server
participant Callback as /api/mcp/oauth/callback
participant DB as mcp_server_oauth
Browser->>Start: "GET ?serverId=&workspaceId="
Start->>DB: getOrCreateOauthRow()
Start->>AS: mcpAuth() → redirectToAuthorization throws
Start-->>Browser: "{ status: 'redirect', authorizationUrl }"
Browser->>Popup: window.open(authorizationUrl)
Popup->>AS: User consents
AS->>Callback: "GET ?code=&state="
Callback->>DB: loadOauthRowByState(hash(state))
Callback->>DB: clearState() (burn before exchange)
Callback->>AS: mcpAuth() token exchange
AS-->>Callback: access_token + refresh_token
Callback->>DB: saveTokens() (encrypted)
Callback->>DB: clearVerifier()
Callback-->>Popup: "HTML + postMessage({ok:true, serverId})"
Popup->>Browser: postMessage via window.opener
Browser->>Browser: invalidate mcpKeys queries
Reviews (37): Last reviewed commit: "fix(mcp): use boolean credsChanged, not ..." | Re-trigger Greptile |
|
Greptile summary findings addressed in f587e82:
The point about clearing a pre-registered Client ID by emptying the field is a follow-up — |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@greptile |
|
@cursor review |
|
@cursor review |
|
@greptile |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 43902b4. Configure here.
Summary
OAuthClientProviderWWW-Authenticate/oauth-protected-resource)mcp_server_oauthtable; SDK refreshes automatically before expiry/api/mcp/oauth/start→/api/mcp/oauth/callback) withstateCSRF protectionreauth_requiredfrom tool execution when refresh token is invalid so the UI can prompt to reconnectType of Change
Testing
Tested manually against OAuth-protected MCP servers (Linear). Existing header-auth servers regression-checked.
Checklist