OAuth System Clarification: DeployStack implements three distinct OAuth systems:
- User → DeployStack OAuth (Social Login) - See OAuth Providers
- MCP Client → DeployStack OAuth (API Access) - See OAuth2 Server - How VS Code, Cursor, Claude.ai authenticate to satellite APIs
- User → MCP Server OAuth (External Service Access) - This document - How users authorize external services like Notion, Box, Linear
Overview
This document covers the backend implementation for OAuth 2.1 authentication with external MCP servers that require user authorization, such as Notion, Box, Linear, and GitHub Copilot.When MCP Servers Require OAuth
MCP servers that access user-specific resources (files, issues, repositories) require OAuth authorization. Examples:- Notion MCP Server (
https://mcp.notion.com/) - Access user’s Notion pages - Box MCP Server (
https://mcp.box.com/) - Access user’s Box files - Linear MCP Server (
https://mcp.linear.app/sse) - Access user’s Linear issues - GitHub Copilot MCP - Access GitHub repositories
User Flow
- Install - User initiates MCP server installation in frontend
- Authorize - Backend redirects to OAuth provider’s authorization page
- Callback - OAuth provider redirects back with authorization code
- Token Storage - Backend exchanges code for tokens, encrypts and stores them
- Use - Satellite injects tokens when connecting to MCP server
Architecture Overview
The OAuth implementation includes:- OAuth Discovery Service - Detects OAuth requirement and discovers endpoints using RFC 8414/9728
- Authorization Endpoint - Initiates OAuth flow with PKCE, state parameter, and resource parameter
- Callback Endpoint - Exchanges authorization code for tokens
- Re-Authentication Endpoint - User-initiated token refresh when automatic refresh fails
- Token Service - Handles token exchange and refresh operations
- Client Registration Service - Implements RFC 7591 Dynamic Client Registration (DCR)
- Encryption Service - AES-256-GCM encryption for tokens at rest
- Token Refresh Job - Background cron job refreshing expiring tokens
Database Tables
mcpOauthProviders- Pre-registered OAuth providers (for non-DCR auth servers)oauthPendingFlows- Temporary storage during OAuth flow (10-minute expiry)mcpServerInstallations- MCP server installationsmcpOauthTokens- Encrypted access and refresh tokens
Implementation Components
OAuthDiscoveryService
File: services/backend/src/services/OAuthDiscoveryService.ts Purpose: Detects if an MCP server requires OAuth and discovers OAuth endpoints using RFC 8414 and RFC 9728.When OAuth Detection Runs
OAuth detection occurs automatically in two scenarios:- Server Creation (
POST /mcp/servers/global) - When global admin creates new MCP server - Server Updates (
PUT /mcp/servers/global/:id) - When global admin updates existing MCP server
- Server’s OAuth configuration might have changed
- URL might be updated to a different endpoint
- Transport type might change from stdio → http/sse or vice versa
- Security verification ensures
requires_oauthflag stays accurate
OAuth Detection
The service uses a hybrid detection approach to handle different server configurations: Detection Strategy:- Try GET first (fast path for most servers like Notion, Box, Linear)
- Try POST with MCP protocol request if GET returns non-401 (handles servers like Harmonic AI that only protect POST endpoints)
- HTTP 401 Unauthorized response (on GET or POST)
WWW-Authenticate: Bearerheader present
- Standard servers (Notion, Box, Linear): Return 401 on GET for public health check endpoints
- Harmonic-style servers: Return 200 on GET (public endpoint), but 401 on POST (protected MCP protocol endpoint)
OAuth Metadata Discovery
Once OAuth is detected, the service discovers endpoints using multiple methods with priority order: Priority 1: WWW-Authenticate Header Discovery URL Some servers provide a direct discovery URL in theWWW-Authenticate header:
- Discovery URL from
WWW-Authenticateheader (if provided) - RFC 8414 Authorization Server Metadata
- OpenID Connect Discovery
- Give up - OAuth configuration cannot be determined
Metadata Structure
Pre-registered Provider Matching
If the discovered authorization server matches a pre-registered provider pattern, the service returns the provider configuration:OAuth Detection on Server Updates
File: services/backend/src/routes/mcp/servers/update-global.ts Endpoint:PUT /api/mcp/servers/global/:id
Purpose: Re-checks OAuth requirements whenever a global admin updates an MCP server configuration.
Update Detection Logic
When updating a global MCP server, the backend determines “effective values” for OAuth detection:- If update includes new
transport_type→ Use new value - If update doesn’t include
transport_type→ Use existing server’s value - Same logic applies to
remotesfield
Update Scenarios
Scenario 1: URL Change- OAuth detection runs with new URL
requires_oauthupdated based on new server’s response
- OAuth detection runs (now HTTP transport)
requires_oauthset based on detection result
- OAuth detection skipped (stdio doesn’t use OAuth)
requires_oauthexplicitly set tofalse
- OAuth detection still runs with existing URL (security re-check)
- Ensures
requires_oauthreflects current server state
Error Handling
OAuth detection failures during updates are non-blocking:requires_oauth=false as safe default.
Authorization Endpoint
File: services/backend/src/routes/mcp/installations/authorize.ts Endpoint:POST /api/teams/:teamId/mcp/installations/authorize
Purpose: Initiates the OAuth 2.1 authorization flow with PKCE for MCP server installation.
Request Body
Authorization Flow Steps
Extract Server URL
Retrieve MCP server URL from
remotes (HTTP/SSE) or packages (stdio) configuration.Discover OAuth Endpoints
Call
OAuthDiscoveryService.detectAndDiscoverOAuth() to get authorization endpoints.Dynamic Client Registration or Provider Match
- If
registration_endpointexists: Register new client via RFC 7591 - Else if pre-registered provider matches: Use provider credentials
- Else: Return error (cannot proceed)
Create Pending Flow Record
Store temporary OAuth flow data in
oauthPendingFlows table (expires in 10 minutes).Callback Endpoint
File: services/backend/src/routes/mcp/installations/callback.ts Endpoint:GET /api/teams/:teamId/mcp/oauth/callback/:flowId
Purpose: Receives authorization code from OAuth provider, exchanges it for tokens, and completes installation.
Callback Flow Steps
Exchange Code for Tokens
Use PKCE verifier to exchange authorization code for access/refresh tokens.
Create Installation
Create the MCP server installation record (not pending anymore).Per-User Instance Creation: OAuth callback creates the user’s instance with status=‘connecting’. For multi-user teams:
- Installing user’s instance created with their OAuth credentials
- Other team members’ instances created with status=‘awaiting_user_config’ (they must authenticate separately)
- Each user authenticates independently with their own OAuth account
OAuthTokenService
File: services/backend/src/services/OAuthTokenService.ts Purpose: Handles token exchange and refresh operations with OAuth servers.Token Exchange with PKCE
Exchanges authorization code for access/refresh tokens using PKCE verification:none- Public client (PKCE only, no client secret)client_secret_post- Client secret in request body (GitHub, most OAuth providers)client_secret_basic- HTTP Basic Auth header (enterprise providers)
Token Refresh
Refreshes expired access tokens using refresh token:Update Refreshed Tokens
Updates database with newly refreshed encrypted tokens:OAuthClientRegistrationService
File: services/backend/src/services/OAuthClientRegistrationService.ts Purpose: Implements RFC 7591 (OAuth 2.0 Dynamic Client Registration Protocol).Dynamic Client Registration
Registers a new OAuth client with MCP server’s registration endpoint:- MCP server supports
registration_endpointin OAuth metadata - Client ID is generated dynamically per installation
- No pre-registration required with OAuth provider
- MCP server does NOT support
registration_endpoint - Pre-registered provider configured in
mcpOauthProviderstable - Uses fixed client ID and client secret
- Example: GitHub OAuth Apps for GitHub MCP server
Database Schema
mcpOauthProviders Table
Pre-registered OAuth providers for MCP servers that don’t support Dynamic Client Registration.oauthPendingFlows Table
Temporary storage for OAuth flows during authorization (expires in 10 minutes).mcpOauthTokens Table
Encrypted OAuth tokens for MCP server installations.iv:authTag:encryptedData (all hex-encoded)
- IV: 16 bytes (128 bits)
- Auth Tag: 16 bytes (128 bits)
- Encrypted Data: Variable length
(installation_id, user_id, team_id) for fast token lookups by satellite.
Token Lifecycle
Token Issuance
- User authorizes application at OAuth provider
- OAuth provider redirects to callback with authorization code
- Backend exchanges code for access/refresh tokens using PKCE
- Tokens encrypted using AES-256-GCM
- Encrypted tokens stored in
mcpOauthTokenstable - Installation status set to
connecting
Automatic Token Refresh
File: services/backend/src/jobs/refresh-oauth-tokens.ts Cron Schedule: Every 5 minutes Refresh Criteria:- Token has
refresh_token(NOT NULL) - Token has
expires_attimestamp (NOT NULL) - Token expires within next 10 minutes
- Token not already expired
Discover OAuth Endpoints
Re-discover OAuth endpoints for each MCP server (ensures current endpoints).
Token Expiration Handling
When automatic token refresh fails (server offline, invalid refresh token, token revoked), the installation entersrequires_reauth status. Users can recover through self-service re-authentication.
Automatic Refresh Failure:
- Token refresh job attempts refresh
- Refresh fails (network error, invalid_grant, server offline)
- Installation status →
requires_reauth - Status message explains why re-auth is needed
POST /api/teams/:teamId/mcp/installations/:installationId/reauth
Permission: mcp.installations.view (both team_admin and team_user)
Why Both Roles: OAuth tokens are per-user credentials, not team-level configuration. Each user authenticates with their own account.
Flow:
- Frontend detects
requires_reauthstatus and shows “Re-authenticate” button - User clicks button → Backend starts OAuth flow (same as initial authorization)
- OAuth provider redirects to authorization page
- User authorizes → Callback exchanges code for new tokens
- Backend updates existing token record (doesn’t create new installation)
- Installation status →
connecting→online - Satellite receives updated tokens via configuration sync
- Initial: Creates new installation + new token record
- Re-auth: Updates existing installation + existing token record
- Pending flow created with
installation_idreference (links to existing installation) - Callback detects
installation_idand performs UPDATE instead of INSERT - Preserves team configuration (env vars, args, headers)
Token Revocation
When user deletes an MCP server installation:- Installation deleted from
mcpServerInstallations(CASCADE) - Tokens automatically deleted from
mcpOauthTokens(CASCADE foreign key) - Future enhancement: Call OAuth provider’s revocation endpoint
- Satellite receives configuration update removing the installation
Security Implementation
PKCE (Proof Key for Code Exchange)
Required for all OAuth flows to prevent authorization code interception attacks. PKCE Generation:SHA256(code_verifier) == code_challenge before issuing tokens.
State Parameter
Purpose: CSRF protection during OAuth flow. Generation:- Backend generates random state before redirecting to OAuth provider
- State stored in
oauthPendingFlowstable - OAuth provider includes state in callback URL
- Backend verifies state matches stored value
- If mismatch → Reject callback (potential CSRF attack)
Resource Parameter
Purpose: Token audience binding (RFC 8707) to prevent token misuse. Generation:- Tokens bound to specific MCP server and team
- Prevents token reuse across different installations
- OAuth provider includes resource in issued token
Token Encryption
Algorithm: AES-256-GCM (Authenticated Encryption with Associated Data) Encryption:- AES-256: Industry-standard symmetric encryption
- GCM mode: Authenticated encryption prevents tampering
- Random IV: Each encryption uses unique initialization vector
- AAD: Additional authenticated data binds encryption context
- Scrypt: Key derivation function resistant to brute-force attacks
HTTPS Requirements
All OAuth endpoints require HTTPS:- Authorization endpoint
- Token endpoint
- Callback endpoint (redirect URI)
http://localhost allowed for testing.
OAuth Discovery Process
Step-by-Step Discovery Flow
Try POST Request (Harmonic-style Servers)
If GET returns non-401, try POST with MCP protocol request.Why POST? Servers like Harmonic AI allow GET for public health checks but require OAuth on POST endpoints.
Try Discovery URL from Header (Priority 1)
If WWW-Authenticate header includes discovery URL, try it first.
Fetch Authorization Server Metadata (Priority 2 - RFC 8414)
If no discovery URL or it failed, try RFC 8414.
Fallback to OpenID Connect Discovery (Priority 3)
If RFC 8414 fails, try OpenID Connect as final fallback.
Detection Methods by Server Type
Different MCP servers implement OAuth protection at different endpoint levels. DeployStack’s hybrid detection approach handles all configurations:Standard Servers (Notion, Box, Linear)
Behavior: Return 401 on GET requests to base URLHarmonic-style Servers (Harmonic AI)
Behavior: Allow GET for public health checks, require OAuth on POST endpointsServers with Discovery URL
Behavior: Includeoauth_authorization_server in WWW-Authenticate header
Error Handling
Discovery failures:- GET and POST both return non-401: Server does not require OAuth
- Discovery URL from header fails: Try RFC 8414 Authorization Server Metadata
- Authorization server metadata not found: Try OpenID Connect discovery
- All discovery methods fail: Return error to user - OAuth configuration cannot be determined
- Network timeout: Retry with exponential backoff (3 attempts)
- Invalid JSON: Log error and return OAuth not supported
- Discovery URL from
WWW-Authenticateheader (if provided) - RFC 8414 Authorization Server Metadata
- OpenID Connect Discovery
- Give up and return error
Integration Points
Backend → Database
OAuth detection triggers:-
Server Creation:
POST /mcp/servers/global- OAuth detection runs on initial creation
requires_oauthstored inmcpServerstable
-
Server Updates:
PUT /mcp/servers/global/:id- OAuth detection re-runs on every update
requires_oauthupdated to reflect current state- Ensures accuracy even if server’s OAuth config changes
- Remote servers might enable/disable OAuth
- URLs might change to different endpoints
- Transport types might switch between stdio/http/sse
- Security verification ensures flag accuracy
Frontend → Backend
Installation initiation:Backend → Satellite
Satellite retrieves OAuth tokens during configuration fetch: The satellite calls/api/satellites/config which includes OAuth tokens for installations:
Satellite → MCP Server
Token injection in HTTP/SSE requests: Satellite addsAuthorization header when connecting to OAuth-enabled MCP servers:
Testing and Debugging
Manual Testing with Real MCP Servers
Notion MCP Server (Standard Detection):- Add Notion to catalog:
https://mcp.notion.com/ - Backend detects OAuth requirement via GET request (fast path)
- Install as user → Opens Notion OAuth page
- Authorize → Callback completes installation
- Check
mcpOauthTokenstable for encrypted tokens - Verify satellite receives decrypted token in config
- Add Harmonic to catalog:
https://mcp.api.harmonic.ai - Backend tries GET first (returns 200 - public endpoint)
- Backend tries POST with MCP protocol request (returns 401 - OAuth required)
- Extracts discovery URL from
WWW-Authenticateheader - Install as user → Opens Harmonic OAuth page
- Authorize → Callback completes installation
- Verify logs show “OAuth detected via POST”
- Add Box to catalog:
https://mcp.box.com/ - Follow same flow as Notion
- Verify PKCE S256 is used (check logs)
- Test token refresh by manually updating
expires_atto past
Testing Dynamic Client Registration
MCP servers with DCR support:- Notion: ✅ Supports RFC 7591 registration endpoint
- Box: ✅ Supports RFC 7591 registration endpoint
- Linear: ✅ Supports RFC 7591 registration endpoint
- Ensure no pre-registered provider matches
- Check logs for “Registering dynamic OAuth client”
- Verify
oauth_client_idis dynamically generated - Check that client can refresh tokens using generated client ID
Testing Pre-registered Providers
Setup test provider:- Add GitHub MCP server requiring OAuth
- Install → Should match provider by auth server pattern
- Check logs for “Using pre-registered OAuth provider: GitHub (Test)”
- Verify client_id from provider is used instead of DCR
Testing OAuth Detection on Updates
Test Case 1: Update URL to OAuth-enabled serverCommon Issues
Issue: “OAuth not detected” for Harmonic-style servers- Cause: Server returns 200 on GET, only protects POST endpoints
- Solution: Fixed in commit
2d3bf3d7- Now tries POST with MCP protocol request if GET returns non-401 - Verify: Check logs for “Server returned non-401 on GET, trying POST with MCP protocol request”
- Cause: MCP server doesn’t support DCR and no pre-registered provider matches
- Fix: Add provider to
mcpOauthProviderstable with matching auth server pattern
- Cause: WWW-Authenticate header parsing failed
- Debug: Check logs for “OAuth discovery URL found in WWW-Authenticate header”
- Format: Header must contain
oauth_authorization_server="https://..."
- Cause: Cron job not running or refresh token missing
- Fix: Check
refreshExpiringOAuthTokenscron job logs, verifyrefresh_tokenfield is not NULL
- Cause: User took more than 10 minutes to authorize
- Fix: Increase expiry in
authorize.tsor inform user to complete authorization faster
- Cause: Satellite hasn’t polled configuration yet
- Fix: Check satellite logs, verify satellite commands created, wait for next config poll
- Cause:
DEPLOYSTACK_ENCRYPTION_SECRETchanged between encryption and decryption - Fix: Ensure encryption secret is consistent across deployments
Log Analysis
Successful OAuth flow with GET detection (Standard servers):Related Documentation
- OAuth Token Injection - How satellites inject tokens into MCP servers
- OAuth2 Server - MCP client API authentication (different OAuth system)
- OAuth Providers - Social login (GitHub, Google)
- MCP OAuth User Guide - User-facing documentation

