Skip to content

feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers#4441

Open
waleedlatif1 wants to merge 28 commits intostagingfrom
waleedlatif1/mcp-oauth
Open

feat(mcp): OAuth 2.1 + PKCE for outbound MCP servers#4441
waleedlatif1 wants to merge 28 commits intostagingfrom
waleedlatif1/mcp-oauth

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Adds spec-compliant OAuth 2.1 + PKCE support for outbound MCP servers via the SDK's OAuthClientProvider
  • Auto-detects OAuth requirement on server create via unauthenticated probe (WWW-Authenticate / oauth-protected-resource)
  • Persists per-user-per-server tokens (encrypted) in new mcp_server_oauth table; SDK refreshes automatically before expiry
  • Popup-based consent flow (/api/mcp/oauth/start/api/mcp/oauth/callback) with state CSRF protection
  • Pre-registered OAuth client support (Client ID + Secret in Advanced settings) for servers without RFC 7591 DCR
  • Surfaces reauth_required from tool execution when refresh token is invalid so the UI can prompt to reconnect

Type of Change

  • New feature

Testing

Tested manually against OAuth-protected MCP servers (Linear). Existing header-auth servers regression-checked.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Adds spec-compliant OAuth support for MCP servers that require it (Linear,
Slack, Notion, Atlassian, etc.) using the SDK's OAuthClientProvider. Tokens
are persisted per-user-per-server and refreshed automatically. Also supports
pre-registered OAuth clients for servers that don't expose Dynamic Client
Registration.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 5, 2026 8:35pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 5, 2026

PR Summary

High Risk
Adds new OAuth 2.1/PKCE authorization flows, token storage, and auth-type detection that change how MCP servers are created/updated and how tools execute; bugs here can break integrations or mishandle credentials.

Overview
Adds OAuth 2.1 + PKCE support for outbound MCP servers, including new /api/mcp/oauth/start and /api/mcp/oauth/callback endpoints that drive a popup-based authorization flow and refresh tools post-auth.

Introduces a new mcp_server_oauth table to persist per-user OAuth artifacts (encrypted tokens, DCR client info, PKCE verifier/state) and extends mcp_servers with authType plus optional pre-registered oauthClientId/oauthClientSecret (secret encrypted, never returned to clients; listing now returns hasOauthClientSecret).

Updates server create/update logic to probe and persist auth mode (none/headers/oauth), clear per-user OAuth state when URL or OAuth credentials change, and mark OAuth servers as disconnected until authorized; client/service/tool execution paths now attach an SDK authProvider, treat OAuth-required/unauthorized cases as reauth-needed (401 reauth_required), and avoid surfacing these as hard connection errors.

Updates the settings UI to configure optional OAuth client credentials under Advanced settings, automatically start OAuth after creating an OAuth server, and provide a “Connect with OAuth” action with query invalidation on successful authorization; also refactors MCP query keys/mutations and bumps @modelcontextprotocol/sdk to 1.29.0.

Reviewed by Cursor Bugbot for commit 3f840d6. Configure here.

Comment thread apps/sim/lib/mcp/oauth/provider.ts
Comment thread apps/sim/hooks/queries/mcp.ts
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR adds spec-compliant OAuth 2.1 + PKCE support for outbound MCP servers, including auto-detection via an unauthenticated probe, encrypted per-user token storage in a new mcp_server_oauth table, a popup-based consent flow with state CSRF protection, and pre-registered client credential support. A previous review cycle addressed a substantial number of edge cases (stale token cleanup, single-mutation OAuth button locking, PKCE verifier encryption, authType downgrade on transient probe failure, and more); the current HEAD reflects all those fixes.

  • New apps/sim/lib/mcp/oauth/ module (probe.ts, provider.ts, storage.ts) encapsulates auth-type detection, the SDK OAuthClientProvider implementation, and encrypted DB persistence; service.ts, client.ts, and connection-manager.ts are updated to wire in the provider for authType === 'oauth' configs.
  • Two new API routes (/api/mcp/oauth/start, /api/mcp/oauth/callback) drive the browser popup flow; the callback burns the state before token exchange and triggers a post-auth tool discovery.
  • The settings UI adds a per-server "Connect with OAuth" button with scoped loading state, a window.postMessage listener for popup completion, and an Advanced settings accordion for pre-registered client credentials.

Confidence Score: 5/5

Safe to merge; the OAuth flow is well-guarded and the previous review cycle resolved all meaningful correctness and security issues.

The core OAuth flow (state CSRF, PKCE verifier encryption, token encryption at rest, scheme-check before window.open, stale-token cleanup on credential change) is correctly implemented. The remaining comments are minor quality suggestions that do not affect correctness.

apps/sim/app/api/mcp/oauth/callback/route.ts — error parameter logged without sanitisation; apps/sim/app/workspace/.../mcp/mcp.tsx — onMessage else-branch and de-memoized selectedServer.

Important Files Changed

Filename Overview
apps/sim/app/api/mcp/oauth/callback/route.ts New OAuth callback handler; validates state/CSRF, burns state before token exchange, correctly HTML-escapes the error param for the popup response, but echoes it verbatim to server logs without sanitisation.
apps/sim/app/api/mcp/oauth/start/route.ts New OAuth start handler; correctly gates on authType, creates/loads the oauth row, drives the SDK auth flow, and returns the redirect URL via McpOauthRedirectRequired — clean and well-guarded.
apps/sim/lib/mcp/oauth/storage.ts New storage layer; encrypts tokens, clientInformation, and codeVerifier; stores state as SHA-256 hash; uses insert-then-select pattern to handle concurrent row creation safely.
apps/sim/lib/mcp/oauth/provider.ts SimMcpOauthProvider implements OAuthClientProvider; supports pre-registered clients (skipping DCR) and throws McpOauthRedirectRequired instead of navigating, enabling server-side flow capture.
apps/sim/lib/mcp/oauth/probe.ts Auth-type probe sends an MCP initialize request with 5s timeout; correctly interprets 401 + WWW-Authenticate Bearer as oauth, 2xx as none, everything else as headers.
apps/sim/lib/mcp/service.ts createClient now builds SimMcpOauthProvider for oauth servers; discoverAllTools and getServerSummaries both handle McpOauthAuthorizationRequiredError and UnauthorizedError by marking servers disconnected instead of errored.
apps/sim/app/api/mcp/servers/route.ts POST handler now detects auth type via probe, preserves existing authType on URL-unchanged edits, encrypts client secret, and only updates OAuth credential fields when explicitly provided.
apps/sim/app/api/mcp/servers/[id]/route.ts PATCH handler correctly clears mcp_server_oauth rows when URL or OAuth credentials change; wraps update and token cleanup in a transaction; strips oauthClientSecret from the response.
apps/sim/hooks/queries/mcp.ts Adds useStartMcpOauth with HTTPS-scheme validation before window.open; refactors mcpKeys to hierarchical factory pattern; converts useForceRefreshMcpTools to a proper useMutation.
apps/sim/app/workspace/[workspaceId]/settings/components/mcp/mcp.tsx Adds OAuth connect button, popup lifecycle management via setInterval, postMessage listener for callback, and per-server connectingOauthServers state; selectedServer and getStoredToolIssues de-memoized from previous useMemo/useCallback.
packages/db/schema.ts Adds mcp_server_oauth table with unique index on (mcpServerId, userId) and plain index on state; adds authType, oauthClientId, oauthClientSecret columns to mcpServers.

Sequence Diagram

sequenceDiagram
    participant UI as Browser (MCP Settings)
    participant Start as /api/mcp/oauth/start
    participant Popup as OAuth Popup
    participant AS as Authorization Server
    participant CB as /api/mcp/oauth/callback
    participant DB as mcp_server_oauth (DB)
    participant SDK as MCP SDK (mcpAuth)

    UI->>Start: GET ?serverId=&workspaceId=
    Start->>DB: getOrCreateOauthRow()
    Start->>SDK: mcpAuth(provider, {serverUrl})
    SDK-->>Start: throws McpOauthRedirectRequired(authorizationUrl)
    Start-->>UI: {status:redirect, authorizationUrl}
    UI->>Popup: window.open(authorizationUrl)
    Popup->>AS: User consents
    AS->>CB: GET /callback?code=&state=
    CB->>DB: loadOauthRowByState(state)
    CB->>DB: clearState() - burn before exchange
    CB->>SDK: mcpAuth(provider, {authorizationCode})
    SDK->>AS: POST /token (code + PKCE verifier)
    AS-->>SDK: access_token + refresh_token
    SDK->>DB: saveTokens() encrypted
    CB->>DB: clearVerifier()
    CB-->>Popup: postMessage({type:mcp-oauth, ok:true, serverId})
    Popup-->>UI: message event
    UI->>UI: invalidate queries, toast.success
Loading

Reviews (20): Last reviewed commit: "fix(mcp): forward serverId on callback f..." | Re-trigger Greptile

Comment thread apps/sim/lib/mcp/oauth/storage.ts
Comment thread apps/sim/lib/mcp/oauth/storage.ts
- provider: clear `state` from DB in `invalidateCredentials` to prevent
  stale state values from matching during CSRF check
- storage: encrypt PKCE `codeVerifier` at rest to match `tokens` /
  `clientInformation` security posture
- queries: `useForceRefreshMcpTools` now writes the fetched payload
  directly into the query cache instead of invalidating, eliminating
  the duplicate network round-trip
- mcp settings: track per-server OAuth pending state so a "Connecting…"
  spinner only disables the card whose flow is in progress
- mcp settings: surface existing `oauthClientId` in edit modal so the
  Advanced section auto-expands and displays the saved value
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

Greptile summary findings addressed in f587e82:

  • Edit modal drops existing OAuth Client ID: editInitialData now includes oauthClientId; the API already returns it (only the secret is masked) so the field populates and Advanced auto-expands.
  • Shared OAuth mutation disables all buttons: per-server pending tracked in a local Set<string>; the spinner is scoped to the card whose flow is in progress.
  • Plaintext PKCE codeVerifier: now encrypted at rest via encryptSecret to match tokens/clientInformation.

The point about clearing a pre-registered Client ID by emptying the field is a follow-up — oauthClientId || undefined collapses an intentional clear into a no-op. Will tackle when adding TTL cleanup for abandoned OAuth sessions.

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/servers/[id]/route.ts
Comment thread apps/sim/app/api/mcp/servers/[id]/route.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/servers/[id]/route.ts Outdated
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/servers/route.ts Outdated
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/tools/execute/route.ts
Comment thread apps/sim/lib/mcp/service.ts Outdated
Comment thread apps/sim/lib/mcp/service.ts Outdated
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/oauth/callback/route.ts
…in edit modal

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 3ea52c9. Configure here.

Comment thread apps/sim/app/api/mcp/tools/execute/route.ts Outdated
Comment thread apps/sim/app/api/mcp/servers/route.ts Outdated
…Id on tool reauth errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/mcp/service.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/hooks/queries/mcp.ts
- use mcp-oauth-${serverId} window target so concurrent OAuth flows on
  different servers don't reuse and clobber the same popup
- drop redundant setQueryData before invalidate in useForceRefreshMcpTools
- replace hardcoded text-red-500 with text-[var(--text-error)] token
- normalize Plus icon to default h-[14px] w-[14px]
- drop useMemo on cheap toolsByServer/selectedServer derivations

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/mcp/oauth/callback/route.ts
Comment thread apps/sim/hooks/queries/mcp.ts
- hoist serverId from try-block const into outer scope so the catch's
  htmlClose carries it through to postMessage. Without it, parent's
  onMessage couldn't clear connectingOauthServers and the UI button
  stayed stuck on "Connecting…" until popup close.
- relax https-only authorization URL check to permit http://localhost,
  http://127.0.0.1, and http://[::1] per OAuth 2.1 loopback exemption,
  unblocking local OAuth-protected MCP server development.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 3f840d6. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant