MCP Authentication: MCP-specific authentication patterns and integration guides with OAuth 2.1 and Dynamic Client Registration --- # DOCUMENT BOUNDARY --- # Overview: MCP server authentication > Secure your Model Context Protocol (MCP) servers with Scalekit's drop-in OAuth 2.1 authorization solution Model Context Protocol (MCP) is an open standard that gives AI apps a consistent, secure way to connect to external tools and data sources. A helpful way to picture it is USB‑C for AI integrations: instead of building a custom connector for every service, MCP provides one interface that works across different models, platforms, and backends. That makes it much easier to build agent-style apps that can actually do work, but it also makes authorization a bigger deal, because once an agent can act on your behalf, you need clear, tight control over what it can access and what actions it’s allowed to take. At its core, MCP follows a client-server architecture where a host application can connect to multiple servers: * **MCP hosts**: AI applications like Claude Desktop, IDEs, or custom AI tools that need to access external resources * **MCP clients**: Protocol clients that maintain connections between hosts and servers * **MCP servers**: Lightweight programs that expose specific capabilities (tools, data, or services) through the standardized protocol * **Data sources**: Local files, databases, APIs, and services that MCP servers can access This architecture enables a ecosystem where AI models can seamlessly integrate with hundreds of different services without requiring custom code for each integration. ## The path to secure MCP: OAuth 2.1 integration [Section titled “The path to secure MCP: OAuth 2.1 integration”](#the-path-to-secure-mcp-oauth-21-integration) Recognizing these challenges, the MCP specification evolved to incorporate robust authorization mechanisms. The Model Context Protocol provides authorization capabilities at the transport level, enabling MCP clients to make requests to restricted MCP servers on behalf of resource owners. The **MCP specification chose OAuth 2.1 as its authorization framework** for several compelling reasons | | | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Industry standard | OAuth 2.1 is a well-established, widely-adopted standard for delegated authorization, with extensive tooling and ecosystem support. | | Security best practices | OAuth 2.1 incorporates lessons learned from OAuth 2.0, removing deprecated flows and enforcing security measures like PKCE (Proof Key for Code Exchange). | | Flexibility | Supports multiple grant types suitable for different MCP use cases: **Authorization code**: When AI agents act on behalf of human users **Client credentials**: For machine-to-machine integrations | | Ecosystem compatibility | Works with existing identity providers and authorization servers, making it easier for enterprises to integrate MCP into their existing security infrastructure. | This authorization mechanism is based on established specifications listed below, but implements a selected subset of their features to ensure security and interoperability while maintaining simplicity: * **OAuth 2.1**: Core authorization framework with enhanced security * **OAuth 2.0 Authorization Server Metadata (RFC8414)**: Standardized server discovery * **OAuth 2.0 Dynamic Client Registration Protocol (RFC7591)**: Automatic client registration * **OAuth 2.0 Protected Resource Metadata (RFC9728)**: Resource server discovery * **Client ID Metadata Document (CIMD)**: Lets authorization servers fetch client metadata directly from a client-hosted document for authorization ## The authorization flow in practice [Section titled “The authorization flow in practice”](#the-authorization-flow-in-practice) Now let’s zoom in and see how the MCP OAuth 2.1 flow unfolds step-by-step: ### Discovery phase [Section titled “Discovery phase”](#discovery-phase) 1. **MCP client** encounters a protected MCP server 2. **Server** responds with `401 Unauthorized` and `WWW-Authenticate` header pointing to Scalekit Auth Server 3. **Client** discovers Scalekit Auth Server capabilities through metadata endpoints ### Authorization phase [Section titled “Authorization phase”](#authorization-phase) 4. **Client** registers with Scalekit Auth Server (if using DCR) 5. **Scalekit Auth Server** issues client credentials (if using DCR) 6. **Client** initiates appropriate OAuth flow 7. **User** grants consent (for Authorization Code flow) 8. **Scalekit Auth Server** issues access token with appropriate scopes ### Client registration [Section titled “Client registration”](#client-registration) #### Dynamic client registration [Section titled “Dynamic client registration”](#dynamic-client-registration) MCP clients and authorization servers SHOULD support the OAuth 2.1 Dynamic Client Registration Protocol to allow MCP clients to obtain OAuth client IDs without user interaction. This enables seamless onboarding of new AI agents without manual configuration. #### Client ID Metadata Document (CIMD) [Section titled “Client ID Metadata Document (CIMD)”](#client-id-metadata-document-cimd) MCP clients SHOULD support the Client ID Metadata Document (CIMD) specification, which allows clients to publish their OAuth client metadata at a well-known URL under their control. This enables authorization servers to automatically retrieve and validate client metadata without requiring an explicit dynamic registration request, simplifying onboarding for new AI agents while maintaining secure, decentralized client configuration. ### Access phase [Section titled “Access phase”](#access-phase) 9. **Client** includes access token in requests to MCP server 10. **MCP server** validates token and enforces scope-based permissions 11. **Server** processes request and returns response 12. **All interactions** are logged for audit and compliance ## Key security enhancements in MCP OAuth 2.1 [Section titled “Key security enhancements in MCP OAuth 2.1”](#key-security-enhancements-in-mcp-oauth-21) MCP’s OAuth 2.1 profile reduces a few common risks in the authorization code flow. The key enhancements are: * **Mandatory PKCE**: Clients must use PKCE to help prevent authorization code interception. * **Strict redirect URI validation**: Servers must only allow pre-registered redirect URIs and enforce an exact match to reduce redirect attacks. * **Short-lived tokens**: Authorization servers should issue short-lived access tokens to limit impact if a token leaks. * **Granular scopes**: Use narrow scopes (for example, `todo:read`, `todo:write`) so apps request only what they need and users can understand what they’re granting. --- # DOCUMENT BOUNDARY --- # Add OAuth 2.1 authorization to MCP servers > Secure your Model Context Protocol (MCP) servers with Scalekit's drop-in OAuth 2.1 authorization solution and protect your AI integrations This guide shows you how to add production-ready OAuth 2.1 authorization to your Model Context Protocol (MCP) server using Scalekit. You’ll learn how to secure your MCP server so that only authenticated and authorized users can access your tools through AI hosts like Claude Desktop, Cursor, or VS Code. ### Build with a coding agent * Claude Code ```bash /plugin marketplace add scalekit-inc/claude-code-authstack ``` ```bash /plugin install mcp-auth@scalekit-auth-stack ``` * Codex ```bash curl -fsSL https://raw.githubusercontent.com/scalekit-inc/codex-authstack/main/install.sh | bash ``` ```bash # Restart Codex # Plugin Directory -> Scalekit Auth Stack -> install mcp-auth ``` * GitHub Copilot CLI ```bash copilot plugin marketplace add scalekit-inc/github-copilot-authstack ``` ```bash copilot plugin install mcp-auth@scalekit-auth-stack ``` * 40+ agents ```bash npx skills add scalekit-inc/skills --skill adding-mcp-oauth ``` [Continue building with AI →](/dev-kit/build-with-ai/mcp-auth/) MCP servers expose tools that AI hosts can discover and execute to interact with your resources. For example: * A sales team member could use Claude Desktop to view customer information, update records, or set follow-up reminders * A developer could use VS Code or Cursor with a GitHub MCP server to perform everyday GitHub actions through chat * An autonomous agent could use an MCP server to perform actions such as look up the account details in a CRM system When you build MCP servers, multiple AI hosts may need to discover and use your server to interact with your resources. Scalekit handles the complex authentication and authorization for you, so you can focus on building better tools and improving functionality. 1. ## Get Scalekit SDK [Section titled “Get Scalekit SDK”](#get-scalekit-sdk) To get started, make sure you have your Scalekit account and API credentials ready. If you haven’t created a Scalekit account yet, you can [sign up and get a free account](https://app.scalekit.com/ws/signup). Next, install the Scalekit SDK for your language: * Node.js ```bash npm install @scalekit-sdk/node ``` * Python ```sh pip install scalekit-sdk-python ``` Use the Scalekit dashboard to register your MCP server and configure MCP hosts (or AI agents following the MCP client protocol) to use Scalekit as the authorization server. The Scalekit SDK validates tokens after users have been authenticated and authorized to access your MCP server. 2. ## Add MCP server to get drop-in OAuth2.1 authorization server [Section titled “Add MCP server to get drop-in OAuth2.1 authorization server”](#add-mcp-server-to-get-drop-in-oauth21-authorization-server) In the Scalekit dashboard, go to **MCP servers** and select **Add MCP server**. ![Add MCP server](/.netlify/images?url=_astro%2Fmcp-create.wpqhshLD.png\&w=1068\&h=864\&dpl=6a01bf5aba8408000850fe26) 1. Provide a **name** for your MCP server to help you identify it easily. This name appears on the Consent page that MCP hosts display to users when authorizing access to your MCP server. 2. Enable **dynamic client registration** for MCP hosts. This allows MCP hosts to automatically register with Scalekit (and your authorization server), eliminating the need for manual registration and making it easier for users to adopt your MCP server secur. 3. Enable **Client ID Metadata Document (CIMD)** to allow your authorization server to fetch client metadata from MCP hosts and authorize them automatically. 4. Click **Save** to register the server. Note: If your MCP server is intended for use by public MCP clients such as Claude, Cursor, or VS Code, it is recommended to keep both DCR and CIMD enabled. Clients that support CIMD will use the CIMD flow, while clients that do not yet support CIMD can fall back to Dynamic Client Registration. This ensures your MCP server remains compatible with the widest range of MCP clients while preserving a smooth authorization experience. Toggling DCR or CIMD? If you enable or disable DCR or CIMD, be sure to restart your MCP server. Certain MCP frameworks, like FastMCP, cache authorization server details, and a restart ensures the updated configuration is correctly applied. 3. ## Let MCP clients discover your OAuth2.1 authorization server [Section titled “Let MCP clients discover your OAuth2.1 authorization server”](#let-mcp-clients-discover-your-oauth21-authorization-server) MCP protocol directs any MCP client to discover your OAuth2.1 authorization server by calling a public endpoint on your MCP server. This endpoint is called `.well-known/oauth-protected-resource` and your MCP server must host this endpoint. ![MCP server setup](/.netlify/images?url=_astro%2Fmcp-metadata.BIWBrsCY.png\&w=1126\&h=1326\&dpl=6a01bf5aba8408000850fe26) Copy the resource metadata JSON from **Dashboard > MCP Servers > Your server > Metadata JSON** and implement it in your `.well-known/oauth-protected-resource` endpoint. The `authorization_servers` field contains your Scalekit resource identifier, which clients use to initiate the OAuth flow. * Node.js ```javascript // MCP client discovery endpoint // Use case: Allow MCP clients to discover OAuth authorization server configuration app.get('/.well-known/oauth-protected-resource', (req, res) => { res.json({ // From Scalekit dashboard > MCP servers > Your server > Metadata JSON "authorization_servers": [ "https:///resources/" ], "bearer_methods_supported": [ "header" // Bearer token in Authorization header ], "resource": "https://mcp.yourapp.com", // Your MCP server URL "resource_documentation": "https://mcp.yourapp.com/docs", // A URL to the documentation of the resource server "scopes_supported": ["todo:read", "todo:write"] // Dashboard-configured scopes }); }); ``` * Python ```python from fastapi import FastAPI from fastapi.responses import JSONResponse app = FastAPI() # OAuth Protected Resource Metadata endpoint - Required for MCP client discovery # Copy the actual authorization server URL and metadata from your Scalekit dashboard. # The values shown here are examples - replace with your actual configuration. @app.get("/.well-known/oauth-protected-resource") async def get_oauth_protected_resource(): return JSONResponse({ "authorization_servers": [ "https:///resources/" ], "bearer_methods_supported": [ "header" ], "resource": "https://mcp.yourapp.com", "resource_documentation": "https://mcp.yourapp.com/docs", "scopes_supported": ["todo:read", "todo:write"] }) ``` 4. ## Validate all MCP client requests have a valid access token [Section titled “Validate all MCP client requests have a valid access token”](#validate-all-mcp-client-requests-have-a-valid-access-token) Your MCP server should validate that all incoming requests contain a valid access token. Leverage Scalekit SDKs to validate tokens and verify essential claims such as `aud` (audience), `iss` (issuer), `exp` (expiration), `iat` (issued at), and `scope` (permissions). * Node.js auth-config.js ```javascript 1 import { Scalekit } from '@scalekit-sdk/node'; 2 3 // Initialize Scalekit client with environment credentials 4 // Reference installation guide for client setup details 5 const scalekit = new Scalekit( 6 process.env.SCALEKIT_ENVIRONMENT_URL, 7 process.env.SCALEKIT_CLIENT_ID, 8 process.env.SCALEKIT_CLIENT_SECRET 9 ); 10 11 // Resource configuration 12 // Get these values from Scalekit dashboard > MCP servers > Your server 13 // For FastMCP: Use base URL with trailing slash (e.g., https://mcp.example.com/) 14 const RESOURCE_ID = 'https://your-mcp-server.com'; // If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard. 15 const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource'; 16 17 // WWW-Authenticate header for unauthorized responses 18 // This helps clients understand how to authenticate properly 19 export const WWWHeader = { 20 HeaderKey: 'WWW-Authenticate', 21 HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"` 22 }; ``` * Python auth\_config.py ```python 1 from scalekit import ScalekitClient 2 from scalekit.common.scalekit import TokenValidationOptions 3 import os 4 5 # Initialize Scalekit client with environment credentials 6 # Reference installation guide for client setup details 7 scalekit_client = ScalekitClient( 8 env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), 9 client_id=os.getenv("SCALEKIT_CLIENT_ID"), 10 client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") 11 ) 12 13 # Resource configuration 14 # Get these values from Scalekit dashboard > MCP servers > Your server 15 # For FastMCP: Use base URL with trailing slash (e.g., https://mcp.example.com/) 16 RESOURCE_ID = "https://your-mcp-server.com" # If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard. 17 METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource" 18 19 # WWW-Authenticate header for unauthorized responses 20 # This helps clients understand how to authenticate properly 21 WWW_HEADER = { 22 "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"' 23 } ``` Extract the Bearer token from incoming MCP client requests. MCP clients send tokens in the `Authorization: Bearer ` header format. * Node.js ```javascript 1 // Extract Bearer token from Authorization header 2 // Use case: Validate requests from AI hosts like Claude Desktop, Cursor, or VS Code 3 const authHeader = req.headers['authorization']; 4 const token = authHeader?.startsWith('Bearer ') 5 ? authHeader.split('Bearer ')[1]?.trim() 6 : null; 7 8 if (!token) { 9 throw new Error('Missing or invalid Bearer token'); 10 } ``` * Python ```python 1 # Extract Bearer token from Authorization header 2 # Use case: Validate requests from AI hosts like Claude Desktop, Cursor, or VS Code 3 auth_header = request.headers.get("Authorization", "") 4 token = None 5 if auth_header.startswith("Bearer "): 6 token = auth_header.split("Bearer ")[1].strip() 7 8 if not token: 9 raise ValueError("Missing or invalid Bearer token") ``` Validate the token against your configured resource audience to ensure it was issued for your specific MCP server. The resource identifier must match the Server URL you registered earlier. * Node.js Validate token ```javascript 1 // Security: Validate token against configured resource audience 2 // This ensures the token was issued for your specific MCP server 3 await scalekit.validateToken(token, { 4 issuer: '' 5 audience: [RESOURCE_ID] 6 }); ``` * Python Validate token ```python 1 # Method 1: validate_access_token - Returns boolean (True/False) 2 # Use this method when you only need to verify token validity without detailed error information. 3 # This approach is suitable for simple authorization checks where you don't need token claims. 4 def validate_token_with_issuer_audience(token: str) -> bool: 5 """ 6 Validates a token and returns True if valid, False otherwise. 7 8 :param token: The token to validate 9 :return: True if token is valid, False otherwise 10 """ 11 options = TokenValidationOptions( 12 issuer="", 13 audience=[RESOURCE_ID] 14 ) 15 16 try: 17 is_valid = scalekit_client.validate_access_token(token, options=options) 18 return is_valid 19 except Exception as ex: 20 print(f"Token validation failed: {ex}") 21 return False 22 23 # Method 2: validate_token - Returns token claims/payload 24 # Use this method when you need access to token claims (user info, scopes, etc.) or detailed error information. 25 # This approach is suitable for authorization that requires specific user context or scope validation. 26 def validate_token_and_get_claims(token: str) -> dict: 27 """ 28 Validates a token with specific audience and raises exception on failure. 29 30 :param token: The token to validate 31 :raises: ScalekitValidateTokenFailureException if validation fails 32 """ 33 options = TokenValidationOptions( 34 issuer="", 35 audience=[RESOURCE_ID], 36 required_scopes=["todo:read", "todo:write"] # Optional: validate specific scopes for finer access control 37 ) 38 39 scalekit_client.validate_token(token, options=options) ``` #### Complete middleware implementation [Section titled “Complete middleware implementation”](#complete-middleware-implementation) Combine token extraction and validation into a complete authentication middleware that protects all your MCP endpoints. * Node.js ```javascript import { Scalekit } from '@scalekit-sdk/node'; import { NextFunction, Request, Response } from 'express'; const scalekit = new Scalekit( process.env.SCALEKIT_ENVIRONMENT_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const RESOURCE_ID = 'https://your-mcp-server.com'; // If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard. const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource'; export const WWWHeader = { HeaderKey: 'WWW-Authenticate', HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"` }; export async function authMiddleware(req: Request, res: Response, next: NextFunction) { try { // Security: Allow public access to well-known endpoints for metadata discovery // This enables MCP clients to discover your OAuth configuration if (req.path.includes('.well-known')) { return next(); } // Extract Bearer token from Authorization header const authHeader = req.headers['authorization']; const token = authHeader?.startsWith('Bearer ') ? authHeader.split('Bearer ')[1]?.trim() : null; if (!token) { throw new Error('Missing or invalid Bearer token'); } // Security: Validate token against configured resource audience await scalekit.validateToken(token, { audience: [RESOURCE_ID] }); next(); } catch (err) { // Return proper OAuth 2.0 error response with WWW-Authenticate header return res .status(401) .set(WWWHeader.HeaderKey, WWWHeader.HeaderValue) .end(); } } // Apply authentication middleware to all MCP endpoints app.use('/', authMiddleware); ``` * Python ```python from scalekit import ScalekitClient from scalekit.common.scalekit import TokenValidationOptions from fastapi import Request, HTTPException, status from fastapi.responses import Response import os scalekit_client = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") ) RESOURCE_ID = "https://your-mcp-server.com" # If no Server URL is set in Scalekit, use the autogenerated resource ID (e.g., res_123456789) from your dashboard. METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource" # WWW-Authenticate header for unauthorized responses WWW_HEADER = { "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"' } async def auth_middleware(request: Request, call_next): # Security: Allow public access to well-known endpoints for metadata discovery if request.url.path.startswith("/.well-known"): return await call_next(request) # Extract Bearer token from Authorization header auth_header = request.headers.get("Authorization", "") token = None if auth_header.startswith("Bearer "): token = auth_header.split("Bearer ")[1].strip() if not token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, headers=WWW_HEADER ) # Security: Validate token against configured resource audience try: options = TokenValidationOptions( issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), audience=[RESOURCE_ID] ) scalekit_client.validate_token(token, options=options) except Exception: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, headers=WWW_HEADER ) return await call_next(request) # Apply authentication middleware to all MCP endpoints app.middleware("http")(auth_middleware) ``` 5. ## Implement scope-based tool authorization Optional [Section titled “Implement scope-based tool authorization ”](#implement-scope-based-tool-authorization-) Add scope validation at the MCP tool execution level to ensure tools are only executed when the user has authorized the MCP client with the required permissions. This provides fine-grained access control and follows the principle of least privilege. * Node.js ```diff 1 // Security: Validate token has required scope for this specific tool execution 2 // Use case: Ensure users only have access to authorized MCP tools 3 try { 4 await scalekit.validateToken( 5 token, { 6 audience: [RESOURCE_ID], 7 requiredScopes: [scope] 8 } 9 ); 10 } catch(error) { 11 // Return OAuth 2.0 compliant error for insufficient scope 12 return res.status(403).json({ 13 error: 'insufficient_scope', 14 error_description: `Required scope: ${scope}`, 15 scope: scope 16 }); 17 } ``` * Python ```diff 1 # Security: Validate token has required scope for this specific tool execution 2 # Use case: Ensure users only have access to authorized MCP tools 3 try: 4 scalekit_client.validate_access_token( 5 token, 6 options=TokenValidationOptions( 7 audience=[RESOURCE_ID], 8 +required_scopes=[scope] 9 ) 10 ) 11 except ScalekitValidateTokenFailureException as ex: 12 # Return OAuth 2.0 compliant error for insufficient scope 13 return { 14 "error": "insufficient_scope", 15 "error_description": f"Required scope: {scope}", 16 "scope": scope 17 } ``` 6. ## Enable additional authentication methods [Section titled “Enable additional authentication methods”](#enable-additional-authentication-methods) Beyond the OAuth 2.1 authorization you’ve implemented, you can enable additional authentication methods that work seamlessly with your MCP server’s token validation: **[Enterprise SSO](/mcp/auth-methods/enterprise/)** Enable organizations to authenticate through their identity providers (Okta, Azure AD, Google Workspace). Your MCP server continues validating tokens the same way, while Scalekit handles: * Centralized access control through existing enterprise identity systems * Single sign-on experience for organization members * Compliance with corporate security policies **[Social logins](/mcp/auth-methods/social/)** Allow users to authenticate via Google, GitHub, Microsoft, and other social providers. Your existing token validation logic remains unchanged while providing: * Quick onboarding for individual users * Familiar authentication experience * Reduced friction for personal and small team use cases These authentication methods require no changes to your MCP server implementation—you continue validating tokens exactly as shown in the previous steps. **[Bring your own auth](/mcp/auth-methods/custom-auth/)** allows you to use your own authentication system to authenticate users to your MCP server. Your MCP server now has production-ready OAuth 2.1 authorization! You’ve successfully implemented a secure authorization flow that protects your MCP tools and ensures only authenticated users can access them through AI hosts. **Try the demo**: Download and run our [sample MCP server](https://github.com/scalekit-inc/mcp-auth-demos) with authentication already configured to see the complete integration in action. In summary, **Scalekit OAuth authorization server** Acts as the identity provider for your MCP server. * Authenticates users and agents * Issues access tokens with fine-grained scopes * Manages OAuth 2.1 flows (authorization code, client credentials) * Supports dynamic client registration for easy onboarding **Your MCP server** Validates incoming access tokens and enforces the permissions encoded in each token. Only requests with valid, authorized tokens are allowed. This separation of responsibilities ensures a clear boundary: Scalekit handles identity and token issuance, while your MCP server focuses on business logic of executing the actual tool calls. --- # DOCUMENT BOUNDARY --- # MCP Servers - Additional Reading > Explore advanced topics for MCP servers, including OAuth 2.1 flows, scope design, dynamic client registration, and security best practices. MCP Clients that want to get authorized to access your MCP Server need to follow either of the below OAuth 2.1 Flows Supported by Scalekit. ## OAuth 2.1 Flows Supported [Section titled “OAuth 2.1 Flows Supported”](#oauth-21-flows-supported) ### Authorization Code Flow [Section titled “Authorization Code Flow”](#authorization-code-flow) Ideal when an AI agent or MCP Client acts on behalf of a human user: ```javascript 1 // Step 1: Redirect user to authorization server 2 const authURL = new URL('https://your-org.scalekit.com/oauth/authorize'); 3 authURL.searchParams.set('response_type', 'code'); 4 authURL.searchParams.set('client_id', 'your-client-id'); 5 authURL.searchParams.set('redirect_uri', 'https://your-app.com/callback'); 6 authURL.searchParams.set('scope', 'mcp:tools:calendar:read mcp:tools:email:send'); 7 authURL.searchParams.set('state', generateSecureRandomString()); 8 authURL.searchParams.set('code_challenge', generatePKCEChallenge()); 9 authURL.searchParams.set('code_challenge_method', 'S256'); 10 11 // Step 2: Handle callback and exchange code for token 12 app.get('/callback', async (req, res) => { 13 const { code, state } = req.query; 14 15 // Verify state parameter to prevent CSRF 16 if (!isValidState(state)) { 17 return res.status(400).json({ error: 'Invalid state parameter' }); 18 } 19 20 const tokenResponse = await fetch('https://your-org.scalekit.com/oauth/token', { 21 method: 'POST', 22 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 23 body: new URLSearchParams({ 24 grant_type: 'authorization_code', 25 code, 26 client_id: 'your-client-id', 27 redirect_uri: 'https://your-app.com/callback', 28 code_verifier: getPKCEVerifier() // From PKCE challenge generation 29 }) 30 }); 31 32 const tokens = await tokenResponse.json(); 33 // Store tokens securely and proceed with MCP calls 34 }); ``` ### Client Credentials Flow [Section titled “Client Credentials Flow”](#client-credentials-flow) Perfect for automated agents that don’t represent a specific user but want to access your MCP Server on their own behalf. This is typically used for Machine-to-Machine (M2M) authentication. ```javascript 1 const getMachineToken = async () => { 2 const response = await fetch('https://your-org.scalekit.com/oauth/token', { 3 method: 'POST', 4 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 5 body: new URLSearchParams({ 6 grant_type: 'client_credentials', 7 client_id: 'your-service-client-id', 8 client_secret: 'your-service-client-secret', 9 scope: 'mcp:tools:inventory:check mcp:resources:store-data', 10 audience: 'https://your-mcp-server.com', 11 }) 12 }); 13 14 return await response.json(); 15 }; ``` ## Scope Design Best Practices [Section titled “Scope Design Best Practices”](#scope-design-best-practices) Design OAuth scopes that reflect your MCP server’s actual capabilities and security requirements: ### Hierarchical Scopes [Section titled “Hierarchical Scopes”](#hierarchical-scopes) ```javascript 1 // Resource-based scopes 2 'mcp:resources:customer-data:read' // Read customer data 3 'mcp:resources:customer-data:write' // Modify customer data 4 'mcp:resources:*' // All resources (admin-level) 5 6 // Tool-based scopes 7 'mcp:tools:weather' // Weather API access 8 'mcp:tools:calendar:read' // Read calendar events 9 'mcp:tools:calendar:write' // Create/modify calendar events 10 'mcp:tools:email:send' // Send emails 11 'mcp:tools:*' // All tools access 12 13 // Action-based scopes 14 'mcp:exec:workflows:risk-assessment' // Execute risk assessment workflow 15 'mcp:exec:functions:data-analysis' // Run data analysis functions ``` ### Scope Validation Helpers [Section titled “Scope Validation Helpers”](#scope-validation-helpers) ```javascript 1 const ScopeValidator = { 2 hasScope: (userScopes, requiredScope) => { 3 return userScopes.includes(requiredScope) || 4 userScopes.includes(requiredScope.split(':').slice(0, -1).join(':') + ':*'); 5 }, 6 7 hasAnyScope: (userScopes, allowedScopes) => { 8 return allowedScopes.some(scope => ScopeValidator.hasScope(userScopes, scope)); 9 }, 10 11 validateToolAccess: (userScopes, toolName) => { 12 const toolScope = `mcp:tools:${toolName}`; 13 const wildcardScope = 'mcp:tools:*'; 14 return userScopes.includes(toolScope) || userScopes.includes(wildcardScope); 15 } 16 }; 17 18 // Usage in MCP tool handlers 19 app.post('/mcp/tools/:toolName', (req, res) => { 20 const { toolName } = req.params; 21 const userScopes = req.auth.scopes; 22 23 if (!ScopeValidator.validateToolAccess(userScopes, toolName)) { 24 return res.status(403).json({ 25 error: 'insufficient_scope', 26 error_description: `Access to tool '${toolName}' requires appropriate scope` 27 }); 28 } 29 30 // Process tool request 31 }); ``` ## Dynamic Client Registration [Section titled “Dynamic Client Registration”](#dynamic-client-registration) Scalekit supports Dynamic Client Registration (DCR) to enable seamless integration for new MCP clients that want to connect to your MCP Server. MCP clients can auto-register using DCR: ```javascript 1 // MCP clients can auto-register using DCR 2 const registerClient = async (clientMetadata) => { 3 const response = await fetch('https://your-org.scalekit.com/resource-server/oauth/register', { 4 method: 'POST', 5 headers: { 'Content-Type': 'application/json' }, 6 body: JSON.stringify({ 7 client_name: 'AI Sales Assistant', 8 client_uri: 'https://sales-ai.company.com', 9 redirect_uris: ['https://sales-ai.company.com/oauth/callback'], 10 grant_types: ['authorization_code', 'refresh_token'], 11 response_types: ['code'], 12 scope: 'mcp:tools:crm:read mcp:tools:email:send', 13 audience: 'https://your-mcp-server.com', 14 token_endpoint_auth_method: 'client_secret_basic', 15 ...clientMetadata 16 }) 17 }); 18 19 return await response.json(); 20 // Returns: { client_id, client_secret, client_id_issued_at, ... } 21 }; ``` ## Security Implementation [Section titled “Security Implementation”](#security-implementation) ### Rate Limiting by Client [Section titled “Rate Limiting by Client”](#rate-limiting-by-client) Implement client-specific rate limits: ```javascript 1 import rateLimit from 'express-rate-limit'; 2 3 const createClientRateLimit = () => { 4 return rateLimit({ 5 windowMs: 15 * 60 * 1000, // 15 minutes 6 limit: (req) => { 7 // Different limits based on client type or scopes 8 const scopes = req.auth?.scopes || []; 9 if (scopes.includes('mcp:tools:*')) return 1000; // Premium client 10 if (scopes.includes('mcp:tools:basic')) return 100; // Basic client 11 return 50; // Default limit 12 }, 13 keyGenerator: (req) => req.auth?.clientId || req.ip, 14 message: { 15 error: 'rate_limit_exceeded', 16 error_description: 'Too many requests from this client' 17 } 18 }); 19 }; 20 21 app.use('/mcp', createClientRateLimit()); ``` ### Comprehensive Logging [Section titled “Comprehensive Logging”](#comprehensive-logging) Track all OAuth and MCP interactions: ```javascript 1 const auditLogger = { 2 logTokenRequest: (clientId, grantType, scopes, success) => { 3 console.log(JSON.stringify({ 4 event: 'oauth_token_request', 5 timestamp: new Date().toISOString(), 6 client_id: clientId, 7 grant_type: grantType, 8 requested_scopes: scopes, 9 success 10 })); 11 }, 12 13 logMCPAccess: (req, toolName, success, error = null) => { 14 console.log(JSON.stringify({ 15 event: 'mcp_tool_access', 16 timestamp: new Date().toISOString(), 17 user_id: req.auth?.userId, 18 client_id: req.auth?.clientId, 19 tool_name: toolName, 20 scopes: req.auth?.scopes, 21 success, 22 error: error?.message, 23 ip_address: req.ip, 24 user_agent: req.get('User-Agent') 25 })); 26 } 27 }; 28 29 // Use in your MCP handlers 30 app.post('/mcp/tools/:toolName', async (req, res) => { 31 const { toolName } = req.params; 32 33 try { 34 // Process tool request 35 const result = await processToolRequest(toolName, req.body); 36 37 auditLogger.logMCPAccess(req, toolName, true); 38 res.json(result); 39 } catch (error) { 40 auditLogger.logMCPAccess(req, toolName, false, error); 41 res.status(500).json({ error: 'Tool execution failed' }); 42 } 43 }); ``` ### Health Check Endpoints [Section titled “Health Check Endpoints”](#health-check-endpoints) Monitor your MCP server and authorization integration: ```javascript 1 app.get('/health', async (req, res) => { 2 const health = { 3 status: 'healthy', 4 timestamp: new Date().toISOString(), 5 services: { 6 mcp_server: 'healthy', 7 oauth_server: 'unknown' 8 } 9 }; 10 11 try { 12 // Test OAuth server connectivity 13 const oauthTest = await fetch('https://your-org.scalekit.com/.well-known/oauth-authorization-server'); 14 health.services.oauth_server = oauthTest.ok ? 'healthy' : 'degraded'; 15 } catch (error) { 16 health.services.oauth_server = 'unhealthy'; 17 health.status = 'degraded'; 18 } 19 20 const statusCode = health.status === 'healthy' ? 200 : 503; 21 res.status(statusCode).json(health); 22 }); ``` ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ### Common Issues and Solutions [Section titled “Common Issues and Solutions”](#common-issues-and-solutions) **Token Validation Failures** ```javascript 1 // Debug token validation issues 2 const debugTokenValidation = async (token) => { 3 try { 4 // Check token structure 5 const [header, payload, signature] = token.split('.'); 6 console.log('Token Header:', JSON.parse(atob(header))); 7 console.log('Token Payload:', JSON.parse(atob(payload))); 8 9 // Validate with detailed error info 10 await jwtVerify(token, JWKS, { 11 issuer: 'https://your-org.scalekit.com', 12 audience: 'https://your-mcp-server.com' 13 }); 14 } catch (error) { 15 console.error('Token validation error:', { 16 name: error.name, 17 message: error.message, 18 code: error.code 19 }); 20 } 21 }; ``` **CORS Issues with Authorization Server** ```javascript 1 // Configure CORS for OAuth endpoints 2 app.use('/oauth', cors({ 3 origin: 'https://your-org.scalekit.com', 4 credentials: true, 5 methods: ['GET', 'POST', 'OPTIONS'], 6 allowedHeaders: ['Authorization', 'Content-Type', 'MCP-Protocol-Version'] 7 })); ``` **Scope Permission Debugging** ```javascript 1 const debugScopes = (req, res, next) => { 2 console.log('Request Scopes:', { 3 user_scopes: req.auth?.scopes, 4 required_scope: req.requiredScope, 5 has_permission: req.auth?.scopes?.includes(req.requiredScope) 6 }); 7 next(); 8 }; ``` ### Error Response Standards [Section titled “Error Response Standards”](#error-response-standards) Follow OAuth 2.1 and MCP error response formats: ```javascript 1 const sendOAuthError = (res, error, description, statusCode = 400) => { 2 res.status(statusCode).json({ 3 error, 4 error_description: description, 5 error_uri: 'https://your-mcp-server.com/docs/errors' 6 }); 7 }; 8 9 // Usage examples 10 app.use((error, req, res, next) => { 11 if (error.name === 'TokenExpiredError') { 12 return sendOAuthError(res, 'invalid_token', 'Access token has expired', 401); 13 } 14 15 if (error.name === 'InsufficientScopeError') { 16 return sendOAuthError(res, 'insufficient_scope', `Required scope: ${error.requiredScope}`, 403); 17 } 18 19 // Default error 20 sendOAuthError(res, 'server_error', 'An unexpected error occurred', 500); 21 }); ``` ## Advanced Configuration [Section titled “Advanced Configuration”](#advanced-configuration) ### Custom Scope Mapping [Section titled “Custom Scope Mapping”](#custom-scope-mapping) Map OAuth scopes to internal permissions: ```javascript 1 const scopePermissionMap = { 2 'mcp:tools:weather': ['weather:read'], 3 'mcp:tools:calendar:read': ['calendar:events:read'], 4 'mcp:tools:calendar:write': ['calendar:events:read', 'calendar:events:write'], 5 'mcp:tools:email:send': ['email:send', 'contacts:read'], 6 'mcp:resources:customer-data': ['customers:read', 'customers:write'] 7 }; 8 9 const getPermissionsFromScopes = (scopes) => { 10 const permissions = new Set(); 11 scopes.forEach(scope => { 12 const scopePermissions = scopePermissionMap[scope] || []; 13 scopePermissions.forEach(permission => permissions.add(permission)); 14 }); 15 return Array.from(permissions); 16 }; ``` ### Refresh Token Management [Section titled “Refresh Token Management”](#refresh-token-management) Handle token refresh for long-running agents: ```javascript 1 const TokenManager = { 2 async refreshToken(refreshToken) { 3 const response = await fetch('https://your-org.scalekit.com/oauth2/token', { 4 method: 'POST', 5 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 6 body: new URLSearchParams({ 7 grant_type: 'refresh_token', 8 refresh_token: refreshToken, 9 client_id: 'your-client-id', 10 client_secret: 'your-client-secret' 11 }) 12 }); 13 14 return await response.json(); 15 }, 16 17 async autoRefreshWrapper(tokenStore, makeRequest) { 18 try { 19 return await makeRequest(tokenStore.accessToken); 20 } catch (error) { 21 if (error.status === 401) { 22 // Token expired, try refresh 23 const newTokens = await this.refreshToken(tokenStore.refreshToken); 24 tokenStore.accessToken = newTokens.access_token; 25 tokenStore.refreshToken = newTokens.refresh_token; 26 27 // Retry original request 28 return await makeRequest(tokenStore.accessToken); 29 } 30 throw error; 31 } 32 } 33 }; ``` --- # DOCUMENT BOUNDARY --- # MCP authentication patterns > Authentication patterns: Human users via OAuth Authorization Code flow, autonomous agents via Client Credentials flow, and downstream integrations using API keys, OAuth, or token cascading Scalekit provides secure authentication for MCP servers across three distinct patterns, each corresponding to different interaction models and trust boundaries. Understanding which pattern applies to your use case ensures you implement the right security model for your MCP server architecture. This guide covers all three authentication patterns: human-to-MCP interactions, agent-to-MCP communication, and MCP-to-downstream integrations. Each pattern uses different OAuth 2.1 flows and has specific configuration requirements explained with sequence diagrams and practical guidance. ## Pattern comparison [Section titled “Pattern comparison”](#pattern-comparison) Understanding the differences between these patterns helps you choose the right approach for your architecture. Each pattern serves specific use cases and has different security characteristics. | Aspect | Human → MCP | Agent/Machine → MCP | MCP → Downstream | | -------------------- | ---------------------------------------------- | -------------------------------------- | -------------------------------------- | | **Actor** | Human using AI host (Claude, ChatGPT, VS Code) | Autonomous agent or service | MCP Server making backend calls | | **OAuth Flow** | Authorization Code | Client Credentials | Varies by sub-pattern | | **Initiator** | User interaction in MCP client | Programmatic request | MCP server implementation code | | **Token Lifetime** | Medium (typically hours) | Configurable (typically long-lived) | Depends on downstream system | | **User Consent** | Required during authorization flow | Not applicable (pre-configured) | Not applicable | | **Scope Assignment** | During consent prompt | At client registration | At implementation time | | **Best For** | Interactive human workflows | Scheduled tasks, autonomous operations | Backend integration with APIs/services | | **Complexity** | Medium (handles browser flow) | Low (direct token request) | Varies (simple to complex) | ## Pattern 1: Human interacting with MCP server [Section titled “Pattern 1: Human interacting with MCP server”](#pattern-1-human-interacting-with-mcp-server) When a human uses a compliant MCP host application, that host acts as the OAuth client. It initiates authorization with the Scalekit Authorization Server, obtains a scoped access token, and interacts securely with the MCP Server on behalf of the user. This pattern represents the most common interaction model for real-world MCP use cases - humans interacting with an MCP server through AI host applications like Claude Desktop, VS Code, Cursor, or Windsurf, while Scalekit ensures tokens are valid, scoped, and auditable. ### Authorization sequence [Section titled “Authorization sequence”](#authorization-sequence) ### How it works [Section titled “How it works”](#how-it-works) 1. **Initiation** – The human configures an MCP server in their MCP client application. 2. **Challenge** – The MCP Server responds with an HTTP `401` containing a `WWW-Authenticate` header that points to the Scalekit Authorization Server. 3. **Authorization Flow** – The MCP Client opens the user’s browser to initiate the OAuth 2.1 authorization flow. During this step, the Scalekit Authorization Server handles user authentication through Magic Link & OTP, Passkeys, Social login providers (like Google, GitHub, or LinkedIn), or Enterprise SSO integrations (such as Okta, Microsoft Entra ID, or ADFS). The user is then prompted to grant consent for the requested scopes. Once approved, Scalekit returns an authorization code, which the MCP Client exchanges for an access token. 4. **Token Issuance** – Scalekit issues an OAuth 2.1 access token containing claims and scopes (for example, `todo:read`, `calendar:write`) that represent the user’s permissions. 5. **Authorized Request** – The client calls the MCP Server again, now attaching the Bearer token in the `Authorization` header. 6. **Validation and Execution** – The MCP Server validates the token issued by Scalekit and executes the requested tool. ### Implementation [Section titled “Implementation”](#implementation) #### 1. Register your MCP server in the Scalekit Dashboard [Section titled “1. Register your MCP server in the Scalekit Dashboard”](#1-register-your-mcp-server-in-the-scalekit-dashboard) Create a new MCP server in the Scalekit Dashboard to obtain your server credentials and configure authentication settings. #### 2. Implement the protected resource metadata endpoint [Section titled “2. Implement the protected resource metadata endpoint”](#2-implement-the-protected-resource-metadata-endpoint) Add a `.well-known/oauth-protected-resource` endpoint that provides your MCP server’s authentication configuration to clients. #### 3. Configure scopes for your server capabilities [Section titled “3. Configure scopes for your server capabilities”](#3-configure-scopes-for-your-server-capabilities) Define OAuth scopes that correspond to the tools and permissions your MCP server exposes. #### 4. Set up token validation middleware [Section titled “4. Set up token validation middleware”](#4-set-up-token-validation-middleware) Implement middleware to validate incoming JWT tokens from Scalekit before processing MCP tool requests. #### 5. Test the complete authentication flow [Section titled “5. Test the complete authentication flow”](#5-test-the-complete-authentication-flow) Verify the end-to-end flow works with an MCP client to ensure secure authentication. For complete implementation guidance, see the [MCP OAuth 2.1 quickstart](/authenticate/mcp/quickstart/) or framework-specific guides for [FastMCP](/authenticate/mcp/fastmcp-quickstart/), [FastAPI + FastMCP](/authenticate/mcp/fastapi-fastmcp-quickstart/), and [Express.js](/authenticate/mcp/expressjs-quickstart/). ## Pattern 2: Agent / machine interacting with MCP server [Section titled “Pattern 2: Agent / machine interacting with MCP server”](#pattern-2-agent--machine-interacting-with-mcp-server) An autonomous agent or any machine-to-machine process can directly interact with an MCP Server secured by Scalekit. In this model, the agent acts as a confidential OAuth client, authenticated using a `client_id` and `client_secret` issued by Scalekit. This pattern uses the OAuth 2.1 Client Credentials flow, allowing the agent to obtain an access token without user interaction. Tokens are scoped and time-bound, ensuring secure and auditable automation between services. ### Authorization sequence [Section titled “Authorization sequence”](#authorization-sequence-1) ### Client registration [Section titled “Client registration”](#client-registration) #### 1. Navigate to the MCP Server Clients tab [Section titled “1. Navigate to the MCP Server Clients tab”](#1-navigate-to-the-mcp-server-clients-tab) Go to **[Dashboard](https://app.scalekit.com) > MCP Servers** and select your MCP Server. Click on the **Clients** tab. ![Clients tab](/.netlify/images?url=_astro%2Fmcp-client-nav.C6UPUhIu.png\&w=1148\&h=1242\&dpl=6a01bf5aba8408000850fe26) #### 2. Create a new M2M client [Section titled “2. Create a new M2M client”](#2-create-a-new-m2m-client) Click **Create Client** to start the client creation process. ![Create client](/.netlify/images?url=_astro%2Fmcp-clients-tab.UgPaVUGm.png\&w=3020\&h=1040\&dpl=6a01bf5aba8408000850fe26) #### 3. Copy your client credentials [Section titled “3. Copy your client credentials”](#3-copy-your-client-credentials) Copy the **client\_id** and **client\_secret** immediately - the secret will not be shown again for security reasons. Store these securely in your agent’s configuration. ![Client credentials](/.netlify/images?url=_astro%2Fmcp-client-sidesheet.D9KN4b5q.png\&w=3020\&h=1500\&dpl=6a01bf5aba8408000850fe26) #### 4. Configure client scopes [Section titled “4. Configure client scopes”](#4-configure-client-scopes) Optionally, set scopes (e.g., `todo:read`, `todo:write`) that correspond to the permissions configured for your MCP Server. Click **Save** to complete the setup. ### Requesting an access token [Section titled “Requesting an access token”](#requesting-an-access-token) Once you have the client credentials, the agent can request a token directly from the Scalekit Authorization Server: Request access token ```bash 1 curl --location '{{env_url}}/oauth/token' \ 2 --header 'Content-Type: application/x-www-form-urlencoded' \ 3 --data-urlencode 'grant_type=client_credentials' \ 4 --data-urlencode 'client_id={{client_id}}' \ 5 --data-urlencode 'client_secret={{secret_value}}' \ 6 --data-urlencode 'scope=todo:read todo:write' ``` Scalekit responds with a JSON payload containing the access token: Token response ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0...", "token_type": "Bearer", "expires_in": 3600, "scope": "todo:read todo:write" } ``` Use the `access_token` in the `Authorization` header when calling your MCP Server’s endpoints. ### Implementation [Section titled “Implementation”](#implementation-1) #### 1. Create an M2M client for your target MCP server [Section titled “1. Create an M2M client for your target MCP server”](#1-create-an-m2m-client-for-your-target-mcp-server) Use the Scalekit Dashboard to create a Machine-to-Machine client for the MCP server you want to authenticate with. #### 2. Store client credentials securely [Section titled “2. Store client credentials securely”](#2-store-client-credentials-securely) Store the `client_id` and `client_secret` using environment variables or a secrets manager. Never hardcode credentials in your agent code. #### 3. Implement token requests in your agent [Section titled “3. Implement token requests in your agent”](#3-implement-token-requests-in-your-agent) Before making MCP calls, request access tokens using the OAuth 2.1 Client Credentials flow from the Scalekit Authorization Server. #### 4. Add token caching and refresh logic [Section titled “4. Add token caching and refresh logic”](#4-add-token-caching-and-refresh-logic) Implement caching to store tokens until they expire, and refresh them automatically to maintain uninterrupted service. #### 5. Attach tokens to MCP tool requests [Section titled “5. Attach tokens to MCP tool requests”](#5-attach-tokens-to-mcp-tool-requests) Include the access token as a Bearer token in the `Authorization` header when calling MCP server tools. For hands-on experience, use the FastMCP Todo Server from the [FastMCP quickstart](/authenticate/mcp/fastmcp-quickstart/). Create an M2M client and run your token request programmatically within your agent code. ## Pattern 3: MCP server integrating with downstream systems [Section titled “Pattern 3: MCP server integrating with downstream systems”](#pattern-3-mcp-server-integrating-with-downstream-systems) In real-world scenarios, an MCP Server often needs to make backend calls - to your own APIs, to another MCP Server, or to external APIs such as CRM, ticketing, or SaaS tools. This section explains three secure ways to perform these downstream integrations, each corresponding to a different trust boundary and authorization pattern. ### Sub-pattern 3a: Using API keys or custom tokens [Section titled “Sub-pattern 3a: Using API keys or custom tokens”](#sub-pattern-3a-using-api-keys-or-custom-tokens) Your MCP Server can communicate with internal or external backend systems that have their own authorization servers or API key-based access. In this setup, the MCP Server manages its own credentials securely (for example, in environment variables, a vault, or secrets manager) and injects them when making downstream calls. #### Authorization sequence [Section titled “Authorization sequence”](#authorization-sequence-2) #### When to use this pattern [Section titled “When to use this pattern”](#when-to-use-this-pattern) * External APIs have their own authentication (AWS, Stripe, Twilio, etc.) * Internal systems use proprietary authentication mechanisms * Legacy systems that don’t support OAuth 2.1 * You control credential management and rotation #### Example scenario [Section titled “Example scenario”](#example-scenario) * The MCP Server stores an API key as `EXTERNAL_API_KEY` in environment variables * When a tool (e.g., `get_weather_data`) is called, your MCP server attaches the key in the request headers * The backend API validates the key and responds with data * The MCP Server processes and returns the formatted response to the client ### Sub-pattern 3b: MCP-to-MCP communication [Section titled “Sub-pattern 3b: MCP-to-MCP communication”](#sub-pattern-3b-mcp-to-mcp-communication) If you have two MCP Servers that need to communicate - for example, `crm-mcp` calling tools from `tickets-mcp` - you can follow the same authentication pattern described in **Pattern 2** above. The calling MCP Server (in this case, `crm-mcp`) acts as an autonomous agent, authenticating with the receiving MCP Server via OAuth 2.1 Client Credentials Flow. Once the token is issued by Scalekit, the calling MCP uses it to call tools exposed by the second MCP Server. #### Authorization sequence [Section titled “Authorization sequence”](#authorization-sequence-3) #### Implementation [Section titled “Implementation”](#implementation-2) The implementation follows Pattern 2 (Agent/Machine → MCP): 1. Create an M2M client for the receiving MCP server in Scalekit 2. Configure the calling MCP server with the client credentials 3. Request tokens using the Client Credentials flow 4. Call the receiving MCP’s tools with the Bearer token For detailed implementation guidance, refer to the [Pattern 2 section](#pattern-2-agent--machine-interacting-with-mcp-server) above. ### Sub-pattern 3c: Cascading the same token [Section titled “Sub-pattern 3c: Cascading the same token”](#sub-pattern-3c-cascading-the-same-token) In some cases, you may want your MCP Server to forward (or “cascade”) the same access token it received from the client - for example, when your backend system lies within the same trust boundary as the Scalekit Authorization Server and can validate the token based on its issuer, scopes, and expiry. #### Authorization sequence [Section titled “Authorization sequence”](#authorization-sequence-4) #### When to use this pattern [Section titled “When to use this pattern”](#when-to-use-this-pattern-1) Use token cascading when: * Both systems (MCP Server and backend API) trust the same Authorization Server (Scalekit) * The backend API can validate JWTs using public keys or JWKS URL * Scopes and issuer claims (`iss`, `scope`, `exp`) are sufficient to determine access * You need to preserve the original user context across service boundaries Trust boundary consideration Only cascade tokens across services that share the same trust boundary. If your backend API does not validate Scalekit-issued tokens, use a separate service credential or the Client Credentials flow (sub-pattern 3b) instead. #### Implementation requirements [Section titled “Implementation requirements”](#implementation-requirements) For the backend API to validate cascaded tokens: 1. Configure the backend to validate JWT signatures using Scalekit’s public keys 2. Verify the token’s `iss` (issuer) claim matches your Scalekit environment 3. Check the `aud` (audience) claim includes the backend API’s identifier 4. Validate the `exp` (expiration) claim to reject expired tokens 5. Verify required scopes are present in the token’s `scope` claim ## Choosing the right pattern [Section titled “Choosing the right pattern”](#choosing-the-right-pattern) Use this decision guide to select the appropriate authentication pattern for your use case: **For human users accessing MCP tools:** → Use **Pattern 1: Human → MCP** (Authorization Code Flow) **For autonomous agents or scheduled tasks:** → Use **Pattern 2: Agent/Machine → MCP** (Client Credentials Flow) **For MCP server making backend calls:** * External APIs with their own auth → Use **Pattern 3a: API Keys** * Another MCP server you control → Use **Pattern 3b: MCP-to-MCP** (Client Credentials Flow) * Backend within same trust boundary → Use **Pattern 3c: Token Cascading** ## Next steps [Section titled “Next steps”](#next-steps) Now that you understand the authentication patterns, you can: * Follow the [MCP OAuth 2.1 quickstart](/authenticate/mcp/quickstart/) to implement Pattern 1 or Pattern 2 * Explore framework-specific implementations: * [FastMCP quickstart](/authenticate/mcp/fastmcp-quickstart/) for Python with built-in provider * [FastAPI + FastMCP quickstart](/authenticate/mcp/fastapi-fastmcp-quickstart/) for custom Python middleware * [Express.js quickstart](/authenticate/mcp/expressjs-quickstart/) for Node.js/TypeScript servers * Review the [MCP authentication demos](https://github.com/scalekit-inc/mcp-auth-demos) on GitHub for complete working examples --- # DOCUMENT BOUNDARY --- # MCP Auth code samples > MCP Auth authentication examples and patterns ### [Add Auth to Node.js MCP Servers](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) [Add Scalekit auth to a Node.js MCP server with minimal setup. Includes a working example with user greeting.](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) ### [Add Auth to Python MCP Servers](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) [Add Scalekit auth to a Python MCP server in minutes. Includes a working example with user greeting.](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) ### [Secure FastMCP Apps with Auth](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) [Build a secure FastMCP app with Scalekit. Features a complete todo list with protected endpoints and session management.](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) --- # DOCUMENT BOUNDARY --- # Bring your own auth into your MCP server > Federated authentication system with Scalekit's OAuth 2.1 authorization layer for MCP servers If you already have an authentication system in place, you can use Scalekit as a drop-in OAuth 2.1 authorization layer for your MCP servers. This federated approach allows you to maintain your existing auth infrastructure while adding standards-compliant OAuth 2.1 authorization for MCP clients. **Why use federated authentication?** * **Preserve existing auth**: Keep your current authentication system and user management * **Standards compliance**: Add OAuth 2.1 authorization without rebuilding your auth layer * **Seamless integration**: Users authenticate with your familiar login experience * **Centralized control**: Maintain full control over user authentication and policies When an MCP client initiates authentication, Scalekit acts as a bridge between the MCP client and your existing authentication system. The flow involves redirecting users to your login endpoint, validating their identity, and passing user information back to Scalekit to complete the OAuth 2.1 flow. 1. ## Initiate authentication flow [Section titled “Initiate authentication flow”](#initiate-authentication-flow) When the MCP client starts the authentication flow by calling `/oauth/authorize` on Scalekit, Scalekit redirects the user to your configured login endpoint with two critical parameters: * `login_request_id` string : Unique identifier for this login request * `state` string : OAuth state parameter to maintain security across requests **Example redirect URL:** ```sh https:///login?login_request_id=&state= ``` 2. ## Authenticate the user in your system [Section titled “Authenticate the user in your system”](#authenticate-the-user-in-your-system) When the user lands on your login page, process authentication using your existing logic?whether that’s username/password, SSO, biometric authentication, or any other method your system supports. After successful authentication, make a secure backend-to-backend POST request to Scalekit with the authenticated user’s information. Send user details to Scalekit ```bash curl --location '/api/v1/connections//auth-requests//user' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data-raw '{ "sub": "1234567890", "email": "alice@example.com", "given_name": "Alice", "family_name": "Doe", "email_verified": true, "phone_number": "+1234567890", "phone_number_verified": false, "name": "Alice Doe", "preferred_username": "alice.d", "picture": "https://example.com/avatar.jpg", "gender": "female", "locale": "en-US" }' ``` 3. ## Redirect back to Scalekit [Section titled “Redirect back to Scalekit”](#redirect-back-to-scalekit) After receiving a successful response from Scalekit confirming the user details were accepted, redirect the user back to Scalekit’s callback endpoint with the `state` parameter. **Callback URL format:** ```sh /sso/v1/connections//partner:callback?state= ``` The `state_value` must match the `state` parameter you received in step 1. This ensures the authentication flow’s integrity and prevents CSRF attacks. State validation Always verify that the `state` value you send back matches exactly what you received initially. Mismatched state values should be rejected. 4. ## Complete the OAuth flow [Section titled “Complete the OAuth flow”](#complete-the-oauth-flow) After processing the callback from your authentication system, Scalekit automatically handles the remaining OAuth 2.1 flow steps: * Displays the consent screen to the user (if required) * Generates the authorization code * Handles token exchange requests from the MCP client * Issues access tokens with appropriate scopes The MCP client receives valid OAuth 2.1 tokens and can now access your MCP server with the authenticated user’s identity. Your MCP server now supports federated authentication with your existing auth system --- # DOCUMENT BOUNDARY --- # Express.js quickstart > Build a production-ready Express.js MCP server with TypeScript, custom middleware for OAuth token validation, and Scalekit authentication. This guide shows you how to build a production-ready Express.js MCP server with TypeScript and Scalekit’s OAuth authentication. You’ll implement custom middleware for token validation, expose OAuth resource metadata for client discovery, and create MCP tools that enforce authorization using the MCP SDK. Use this quickstart when you’re building Node.js-based MCP servers and want fine-grained control over request handling. The Express integration gives you flexibility to add custom routes, middleware chains, integrate with existing Express applications, and handle complex authorization requirements. The full code is available on [GitHub](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node). **Prerequisites** * A [Scalekit account](https://app.scalekit.com) with permission to manage MCP servers * **Node.js 20+** installed locally * Familiarity with Express.js, TypeScript, and OAuth token validation * Basic understanding of MCP server architecture 1. ## Register your MCP server in Scalekit [Section titled “Register your MCP server in Scalekit”](#register-your-mcp-server-in-scalekit) Create a protected resource entry so Scalekit can issue tokens that your custom Express middleware validates. 1. Navigate to **[Dashboard](https://app.scalekit.com) > MCP Servers > Add MCP Server**. 2. Enter a descriptive name (for example, `Greeting MCP`). 3. Set **Server URL** to `http://localhost:3002/` (keep the trailing slash). 4. Click **Save** to create the server. ![Greeting MCP Register](/.netlify/images?url=_astro%2Fgreeting-mcp-register.C9jsKOBy.png\&w=836\&h=1314\&dpl=6a01bf5aba8408000850fe26) When you save, Scalekit displays the OAuth-protected resource metadata. Copy this JSON—you’ll use it in your `.env` file. ![Greeting MCP Protected JSON](/.netlify/images?url=_astro%2Fgreeting-protected-json.DaFlRuyP.png\&w=716\&h=860\&dpl=6a01bf5aba8408000850fe26) 2. ## Create your project directory [Section titled “Create your project directory”](#create-your-project-directory) Set up a clean directory structure for your TypeScript Express project. Terminal ```bash 1 mkdir express-mcp-node 2 cd express-mcp-node ``` 3. ## Add package dependencies [Section titled “Add package dependencies”](#add-package-dependencies) Create a `package.json` with scripts and all required dependencies for Express, TypeScript, and the MCP SDK. Terminal ```bash 1 cat <<'EOF' > package.json 2 { 3 "name": "express-mcp-node", 4 "version": "1.0.0", 5 "type": "module", 6 "scripts": { 7 "dev": "tsx src/server.ts", 8 "build": "tsc", 9 "start": "node dist/server.js" 10 }, 11 "dependencies": { 12 "@modelcontextprotocol/sdk": "^1.13.0", 13 "@scalekit-sdk/node": "^2.0.1", 14 "cors": "^2.8.5", 15 "dotenv": "^16.4.5", 16 "express": "^5.1.0", 17 "zod": "^3.25.57" 18 }, 19 "devDependencies": { 20 "@types/cors": "^2.8.19", 21 "@types/express": "^4.17.21", 22 "@types/node": "^20.11.19", 23 "tsx": "^4.7.0", 24 "typescript": "^5.4.5" 25 } 26 } 27 EOF ``` 4. ## Configure TypeScript [Section titled “Configure TypeScript”](#configure-typescript) Add a TypeScript configuration file optimized for ES2022 modules and strict type checking. Terminal ```bash 1 cat <<'EOF' > tsconfig.json 2 { 3 "compilerOptions": { 4 "target": "ES2022", 5 "module": "ES2022", 6 "moduleResolution": "node", 7 "esModuleInterop": true, 8 "forceConsistentCasingInFileNames": true, 9 "strict": false, 10 "skipLibCheck": true, 11 "resolveJsonModule": true, 12 "outDir": "dist", 13 "rootDir": "src", 14 "types": ["node"] 15 }, 16 "include": ["src/**/*"] 17 } 18 EOF ``` 5. ## Install dependencies [Section titled “Install dependencies”](#install-dependencies) Install all packages declared in `package.json`. Terminal ```bash 1 npm install ``` 6. ## Configure environment variables [Section titled “Configure environment variables”](#configure-environment-variables) Create a `.env` file with your Scalekit credentials and the protected resource metadata from step 1. Terminal ```bash 1 cat <<'EOF' > .env 2 PORT=3002 3 SK_ENV_URL=https://.scalekit.com 4 SK_CLIENT_ID= 5 SK_CLIENT_SECRET= 6 MCP_SERVER_ID= 7 PROTECTED_RESOURCE_METADATA='' 8 EXPECTED_AUDIENCE=http://localhost:3002/ 9 EOF 10 11 open .env ``` | Variable | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `PORT` | Local port for the Express server. Must match the Server URL registered in Scalekit (defaults to `3002`). | | `SK_ENV_URL` | Your Scalekit environment URL from **Dashboard > Settings > API Credentials** | | `SK_CLIENT_ID` | Client ID from **Dashboard > Settings > API Credentials**. Used with `SK_CLIENT_SECRET` to initialize the SDK. | | `SK_CLIENT_SECRET` | Client secret from **Dashboard > Settings > API Credentials**. Keep this secret and rotate regularly. | | `MCP_SERVER_ID` | The MCP server ID from **Dashboard > MCP Servers**. Not directly used in this implementation but documented for reference. | | `PROTECTED_RESOURCE_METADATA` | The complete OAuth resource metadata JSON from step 1. Clients use this to discover authorization requirements. | | `EXPECTED_AUDIENCE` | The audience value that tokens must include. Should match your server’s public URL (e.g., `http://localhost:3002/`). | Protect your credentials Never commit `.env` to version control. Add it to `.gitignore` immediately and use a secret manager in production (e.g., AWS Secrets Manager, HashiCorp Vault, or your deployment platform’s secrets service). 7. ## Implement the Express MCP server [Section titled “Implement the Express MCP server”](#implement-the-express-mcp-server) Create `src/server.ts` with the complete server implementation. This includes the Scalekit client initialization, authentication middleware for token validation, CORS configuration, and the greeting MCP tool. src/server.ts ```typescript 1 import 'dotenv/config'; 2 import cors from 'cors'; 3 import express, { NextFunction, Request, Response } from 'express'; 4 import { z } from 'zod'; 5 import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 6 import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; 7 import { Scalekit } from '@scalekit-sdk/node'; 8 9 // Load environment variables 10 const PORT = Number(process.env.PORT ?? 3002); 11 const SK_ENV_URL = process.env.SK_ENV_URL ?? ''; 12 const SK_CLIENT_ID = process.env.SK_CLIENT_ID ?? ''; 13 const SK_CLIENT_SECRET = process.env.SK_CLIENT_SECRET ?? ''; 14 const EXPECTED_AUDIENCE = process.env.EXPECTED_AUDIENCE ?? ''; 15 const PROTECTED_RESOURCE_METADATA = process.env.PROTECTED_RESOURCE_METADATA ?? ''; 16 17 // Use case: Configure OAuth resource metadata URL for MCP clients 18 // This allows MCP clients to discover authorization requirements via WWW-Authenticate header 19 // Security: The WWW-Authenticate header signals to clients where to obtain tokens 20 const RESOURCE_METADATA_URL = `http://localhost:${PORT}/.well-known/oauth-protected-resource`; 21 22 // WWW-Authenticate header for 401 responses 23 const WWW_HEADER_KEY = 'WWW-Authenticate'; 24 const WWW_HEADER_VALUE = `Bearer realm="OAuth", resource_metadata="${RESOURCE_METADATA_URL}"`; 25 26 // Initialize Scalekit client for token validation 27 // Security: Use SDK to validate JWT signatures and claims 28 // This prevents accepting forged or tampered tokens 29 const scalekit = new Scalekit(SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET); 30 31 // Initialize MCP server with greeting tool 32 // Context: The McpServer handles MCP protocol details while Express handles HTTP routing 33 const server = new McpServer({ name: 'Greeting MCP', version: '1.0.0' }); 34 35 // Use case: Simple greeting tool demonstrating OAuth-protected MCP operations 36 // Context: This tool is protected by the authentication middleware applied to all routes 37 server.tool( 38 'greet_user', 39 'Greets the user with a personalized message.', 40 { 41 name: z.string().min(1, 'Name is required'), 42 }, 43 async ({ name }: { name: string }) => ({ 44 content: [ 45 { 46 type: 'text', 47 text: `Hi ${name}, welcome to Scalekit!` 48 } 49 ] 50 }) 51 ); 52 53 // Initialize Express application 54 const app = express(); 55 56 // Enable CORS for cross-origin MCP clients 57 // Use case: Allow MCP clients from different origins to connect 58 app.use(cors({ origin: true, credentials: false })); 59 60 // Parse JSON request bodies 61 // Context: MCP protocol uses JSON-RPC format 62 app.use(express.json()); 63 64 // Use case: Expose OAuth resource metadata for MCP client discovery 65 // This endpoint allows clients to discover authorization requirements and server capabilities 66 // Context: MCP clients use this metadata to initiate the OAuth flow 67 app.get('/.well-known/oauth-protected-resource', (_req: Request, res: Response) => { 68 if (!PROTECTED_RESOURCE_METADATA) { 69 res.status(500).json({ error: 'PROTECTED_RESOURCE_METADATA config missing' }); 70 return; 71 } 72 73 const metadata = JSON.parse(PROTECTED_RESOURCE_METADATA); 74 res.type('application/json').send(JSON.stringify(metadata, null, 2)); 75 }); 76 77 // Use case: Health check endpoint for monitoring and load balancers 78 // Context: Keep this separate from protected endpoints for deployment health checks 79 app.get('/health', (_req: Request, res: Response) => { 80 res.json({ status: 'healthy' }); 81 }); 82 83 // Security: Validate Bearer tokens on all protected endpoints 84 // Public endpoints (health, metadata) are exempt from authentication 85 // This prevents unauthorized access to MCP tools and operations 86 app.use(async (req: Request, res: Response, next: NextFunction) => { 87 // Allow public endpoints without authentication 88 // Use case: Health checks for monitoring; metadata for client discovery 89 if (req.path === '/.well-known/oauth-protected-resource' || req.path === '/health') { 90 next(); 91 return; 92 } 93 94 // Extract Bearer token from Authorization header 95 // Use case: OAuth 2.1 Bearer token format (RFC 6750) 96 // Security: Reject requests without valid Bearer token prefix 97 const header = req.headers.authorization; 98 const token = header?.startsWith('Bearer ') 99 ? header.slice('Bearer '.length).trim() 100 : undefined; 101 102 if (!token) { 103 res.status(401) 104 .set(WWW_HEADER_KEY, WWW_HEADER_VALUE) 105 .json({ error: 'Missing Bearer token' }); 106 return; 107 } 108 109 try { 110 // Validate token using Scalekit SDK 111 // Security: Verifies signature, expiration, issuer, and audience claims 112 // Context: This critical step prevents accepting tokens from other issuers 113 await scalekit.validateToken(token, { audience: [EXPECTED_AUDIENCE] }); 114 next(); 115 } catch (error) { 116 res.status(401) 117 .set(WWW_HEADER_KEY, WWW_HEADER_VALUE) 118 .json({ error: 'Token validation failed' }); 119 } 120 }); 121 122 // Handle MCP protocol requests at root path 123 // Use case: Process authenticated MCP tool requests using StreamableHTTPServerTransport 124 // Context: The transport layer handles MCP JSON-RPC communication 125 app.post('/', async (req: Request, res: Response) => { 126 const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); 127 await server.connect(transport); 128 129 try { 130 await transport.handleRequest(req, res, req.body); 131 } catch (error) { 132 res.status(500).json({ error: 'MCP transport error' }); 133 } 134 }); 135 136 // Start the Express server 137 app.listen(PORT, () => { 138 console.log(`MCP server running on http://localhost:${PORT}`); 139 }); ``` 8. ## Start the Express server [Section titled “Start the Express server”](#start-the-express-server) Start the Express server in development mode with auto-reload enabled. The server will listen on `http://localhost:3002/` and display logs indicating Express is ready to receive authenticated MCP requests. Terminal ```bash 1 npm run dev ``` The server starts on `http://localhost:3002/` and logs indicate Express is ready. The MCP endpoint at `/` accepts authenticated POST requests, and the metadata endpoint is accessible at `/.well-known/oauth-protected-resource`. 9. ## Connect with MCP Inspector [Section titled “Connect with MCP Inspector”](#connect-with-mcp-inspector) Test your server end-to-end using the MCP Inspector to verify the OAuth flow works correctly. This allows you to see the authentication handshake and test calling your MCP tools with validated tokens. Terminal ```bash 1 npx @modelcontextprotocol/inspector@latest ``` In the Inspector UI: 1. Enter your MCP Server URL: `http://localhost:3002/` 2. Click **Connect** to initiate the OAuth flow 3. Authenticate with Scalekit when prompted 4. Run the `greet_user` tool with any name ![MCP Inspector](/.netlify/images?url=_astro%2Fmcp-inspector-google.B0jhj-ep.png\&w=3022\&h=1318\&dpl=6a01bf5aba8408000850fe26) You now have a working Express.js MCP server with Scalekit-protected OAuth authentication. Extend this implementation by adding more MCP tools using `server.tool()` with Zod schema validation, implementing scope-based authorization using custom middleware, integrating with your existing Express application, or adding features like rate limiting and request logging using Express’s middleware ecosystem. --- # DOCUMENT BOUNDARY --- # FastAPI + FastMCP quickstart > Build a production-ready MCP server with FastAPI custom middleware for OAuth token validation and Scalekit authentication. This guide shows you how to build a production-ready FastAPI + FastMCP server with Scalekit’s OAuth authentication. You’ll implement custom middleware for token validation, expose OAuth resource metadata for client discovery, and create MCP tools that enforce authorization. Use this quickstart when you need more control over your server’s behavior than FastMCP’s built-in provider offers. The FastAPI integration gives you flexibility to add custom middleware, implement additional endpoints, integrate with existing FastAPI applications, and handle complex authorization requirements. The full code is available on [GitHub](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python). **Prerequisites** * A [Scalekit account](https://app.scalekit.com) with permission to manage MCP servers * **Python 3.11+** installed locally * Familiarity with FastAPI and OAuth token validation * Basic understanding of MCP server architecture 1. ## Register your MCP server in Scalekit [Section titled “Register your MCP server in Scalekit”](#register-your-mcp-server-in-scalekit) Create a protected resource entry so Scalekit can issue tokens that your custom FastAPI middleware validates. 1. Navigate to **[Dashboard](https://app.scalekit.com) > MCP Servers > Add MCP Server**. 2. Enter a descriptive name (for example, `Greeting MCP`). 3. Set **Server URL** to `http://localhost:3002/` (keep the trailing slash). 4. Click **Save** to create the server. ![Greeting MCP Register](/.netlify/images?url=_astro%2Fgreeting-mcp-register.C9jsKOBy.png\&w=836\&h=1314\&dpl=6a01bf5aba8408000850fe26) When you save, Scalekit displays the OAuth-protected resource metadata. Copy this JSON—you’ll use it in your `.env` file. ![Greeting MCP Protected JSON](/.netlify/images?url=_astro%2Fgreeting-protected-json.DaFlRuyP.png\&w=716\&h=860\&dpl=6a01bf5aba8408000850fe26) 2. ## Create your project directory [Section titled “Create your project directory”](#create-your-project-directory) Set up a clean directory structure with a Python virtual environment to isolate FastAPI and FastMCP dependencies. Terminal ```bash 1 mkdir fastapi-mcp-python 2 cd fastapi-mcp-python 3 python3 -m venv .venv 4 source .venv/bin/activate ``` 3. ## Add dependencies [Section titled “Add dependencies”](#add-dependencies) Create a `requirements.txt` file with all required packages and install them. Terminal ```bash 1 cat <<'EOF' > requirements.txt 2 mcp>=1.0.0 3 fastapi>=0.104.0 4 fastmcp>=0.8.0 5 uvicorn>=0.24.0 6 pydantic>=2.5.0 7 python-dotenv>=1.0.0 8 httpx>=0.25.0 9 python-jose[cryptography]>=3.3.0 10 cryptography>=41.0.0 11 scalekit-sdk-python>=2.4.0 12 starlette>=0.27.0 13 EOF 14 15 pip install -r requirements.txt ``` 4. ## Configure environment variables [Section titled “Configure environment variables”](#configure-environment-variables) Create a `.env` file with your Scalekit credentials and the protected resource metadata from step 1. Terminal ```bash 1 cat <<'EOF' > .env 2 PORT=3002 3 SK_ENV_URL=https://.scalekit.com 4 SK_CLIENT_ID= 5 SK_CLIENT_SECRET= 6 MCP_SERVER_ID= 7 PROTECTED_RESOURCE_METADATA='' 8 EXPECTED_AUDIENCE=http://localhost:3002/ 9 EOF 10 11 open .env ``` | Variable | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `PORT` | Local port for the FastAPI server. Must match the Server URL registered in Scalekit (defaults to `3002`). | | `SK_ENV_URL` | Your Scalekit environment URL from **Dashboard > Settings > API Credentials** | | `SK_CLIENT_ID` | Client ID from **Dashboard > Settings > API Credentials**. Used with `SK_CLIENT_SECRET` to initialize the SDK. | | `SK_CLIENT_SECRET` | Client secret from **Dashboard > Settings > API Credentials**. Keep this secret and rotate regularly. | | `MCP_SERVER_ID` | The MCP server ID from **Dashboard > MCP Servers**. Not directly used in this implementation but documented for reference. | | `PROTECTED_RESOURCE_METADATA` | The complete OAuth resource metadata JSON from step 1. Clients use this to discover authorization requirements. | | `EXPECTED_AUDIENCE` | The audience value that tokens must include. Should match your server’s public URL (e.g., `http://localhost:3002/`). | Protect your credentials Never commit `.env` to version control. Add it to `.gitignore` immediately and use a secret manager in production (e.g., AWS Secrets Manager, HashiCorp Vault, or your deployment platform’s secrets service). 5. ## Implement the FastAPI + FastMCP server [Section titled “Implement the FastAPI + FastMCP server”](#implement-the-fastapi--fastmcp-server) Create `main.py` with the complete server implementation. This includes the Scalekit client initialization, authentication middleware for token validation, CORS configuration, and the greeting MCP tool. main.py ```python 1 import json 2 import os 3 from fastapi import FastAPI, Request, Response 4 from fastmcp import FastMCP, Context 5 from scalekit import ScalekitClient 6 from scalekit.common.scalekit import TokenValidationOptions 7 from starlette.middleware.cors import CORSMiddleware 8 from dotenv import load_dotenv 9 10 load_dotenv() 11 12 # Load environment variables 13 PORT = int(os.getenv("PORT", "3002")) 14 SK_ENV_URL = os.getenv("SK_ENV_URL", "") 15 SK_CLIENT_ID = os.getenv("SK_CLIENT_ID", "") 16 SK_CLIENT_SECRET = os.getenv("SK_CLIENT_SECRET", "") 17 EXPECTED_AUDIENCE = os.getenv("EXPECTED_AUDIENCE", "") 18 PROTECTED_RESOURCE_METADATA = os.getenv("PROTECTED_RESOURCE_METADATA", "") 19 20 # Use case: Configure OAuth resource metadata URL for MCP clients 21 # This allows MCP clients to discover authorization requirements via WWW-Authenticate header 22 # Security: The WWW-Authenticate header signals to clients where to obtain tokens 23 RESOURCE_METADATA_URL = f"http://localhost:{PORT}/.well-known/oauth-protected-resource" 24 WWW_HEADER = { 25 "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"' 26 } 27 28 # Initialize Scalekit client for token validation 29 # Security: Use SDK to validate JWT signatures and claims 30 # This prevents accepting forged or tampered tokens 31 scalekit_client = ScalekitClient( 32 env_url=SK_ENV_URL, 33 client_id=SK_CLIENT_ID, 34 client_secret=SK_CLIENT_SECRET, 35 ) 36 37 # Initialize FastMCP with stateless HTTP transport 38 # HTTP transport allows MCP clients to connect via standard OAuth flows 39 mcp = FastMCP("Greeting MCP", stateless_http=True) 40 41 42 @mcp.tool( 43 name="greet_user", 44 description="Greets the user with a personalized message." 45 ) 46 async def greet_user(name: str, ctx: Context | None = None) -> dict: 47 """ 48 Use case: Simple greeting tool demonstrating OAuth-protected MCP operations 49 Context: This tool is protected by the authentication middleware 50 """ 51 return { 52 "content": [ 53 { 54 "type": "text", 55 "text": f"Hi {name}, welcome to Scalekit!" 56 } 57 ] 58 } 59 60 61 # Mount FastMCP as a FastAPI application 62 # Context: This allows us to layer FastAPI middleware on top of FastMCP 63 mcp_app = mcp.http_app(path="/") 64 app = FastAPI(lifespan=mcp_app.lifespan) 65 66 # Enable CORS for cross-origin MCP clients 67 # Use case: Allow MCP clients from different origins to connect 68 app.add_middleware( 69 CORSMiddleware, 70 allow_origins=["*"], 71 allow_credentials=True, 72 allow_methods=["GET", "POST", "OPTIONS"], 73 allow_headers=["*"] 74 ) 75 76 77 @app.middleware("http") 78 async def auth_middleware(request: Request, call_next): 79 """ 80 Security: Validate Bearer tokens on all protected endpoints. 81 Public endpoints (health, metadata) are exempt from authentication. 82 This prevents unauthorized access to MCP tools and operations. 83 """ 84 # Allow public endpoints without authentication 85 # Use case: Health checks for monitoring; metadata for client discovery 86 if request.url.path in {"/health", "/.well-known/oauth-protected-resource"}: 87 return await call_next(request) 88 89 # Extract Bearer token from Authorization header 90 # Use case: OAuth 2.1 Bearer token format (RFC 6750) 91 # Security: Reject requests without valid Bearer token prefix 92 auth_header = request.headers.get("authorization") 93 if not auth_header or not auth_header.startswith("Bearer "): 94 return Response( 95 '{"error": "Missing Bearer token"}', 96 status_code=401, 97 headers=WWW_HEADER, 98 media_type="application/json" 99 ) 100 101 token = auth_header.split("Bearer ", 1)[1].strip() 102 103 # Validate token using Scalekit SDK 104 # Security: Verifies signature, expiration, issuer, and audience claims 105 # Context: This critical step prevents accepting tokens from other issuers 106 options = TokenValidationOptions( 107 issuer=SK_ENV_URL, 108 audience=[EXPECTED_AUDIENCE] 109 ) 110 111 try: 112 is_valid = scalekit_client.validate_access_token(token, options=options) 113 if not is_valid: 114 raise ValueError("Invalid token") 115 except Exception: 116 return Response( 117 '{"error": "Token validation failed"}', 118 status_code=401, 119 headers=WWW_HEADER, 120 media_type="application/json" 121 ) 122 123 # Token is valid, proceed with request 124 # This allows MCP clients to call tools with authenticated context 125 return await call_next(request) 126 127 128 @app.get("/.well-known/oauth-protected-resource") 129 async def oauth_metadata(): 130 """ 131 Use case: Expose OAuth resource metadata for MCP client discovery 132 This endpoint allows clients to discover authorization requirements and server capabilities 133 Context: MCP clients use this metadata to initiate the OAuth flow 134 """ 135 if not PROTECTED_RESOURCE_METADATA: 136 return Response( 137 '{"error": "PROTECTED_RESOURCE_METADATA config missing"}', 138 status_code=500, 139 media_type="application/json" 140 ) 141 142 metadata = json.loads(PROTECTED_RESOURCE_METADATA) 143 return Response( 144 json.dumps(metadata, indent=2), 145 media_type="application/json" 146 ) 147 148 149 @app.get("/health") 150 async def health_check(): 151 """ 152 Use case: Health check endpoint for monitoring and load balancers 153 Context: Keep this separate from protected endpoints for deployment health checks 154 """ 155 return {"status": "healthy"} 156 157 158 # Mount the FastMCP application at root path 159 app.mount("/", mcp_app) 160 161 162 if __name__ == "__main__": 163 import uvicorn 164 # Start server with auto-reload for development 165 # Production: Use 'uvicorn main:app --host 0.0.0.0 --port 3002 --workers 4' behind a reverse proxy 166 uvicorn.run(app, host="0.0.0.0", port=PORT) ``` 6. ## Start the FastAPI server [Section titled “Start the FastAPI server”](#start-the-fastapi-server) Start the FastAPI server in development mode with auto-reload enabled. The server will listen on `http://localhost:3002/` and display logs indicating FastAPI is ready to receive authenticated MCP requests. Terminal ```bash 1 python main.py ``` The server starts on `http://localhost:3002/` and logs indicate FastAPI is ready. The MCP endpoint accepts authenticated requests, and the metadata endpoint is accessible at `/.well-known/oauth-protected-resource`. 7. ## Connect with MCP Inspector [Section titled “Connect with MCP Inspector”](#connect-with-mcp-inspector) Test your server end-to-end using the MCP Inspector to verify the OAuth flow works correctly. This allows you to see the authentication handshake and test calling your MCP tools with validated tokens. Terminal ```bash 1 npx @modelcontextprotocol/inspector@latest ``` In the Inspector UI: 1. Enter your MCP Server URL: `http://localhost:3002/` 2. Click **Connect** to initiate the OAuth flow 3. Authenticate with Scalekit when prompted 4. Run the `greet_user` tool with any name ![MCP Inspector](/.netlify/images?url=_astro%2Fmcp-inspector-google.B0jhj-ep.png\&w=3022\&h=1318\&dpl=6a01bf5aba8408000850fe26) You now have a working FastAPI + FastMCP server with Scalekit-protected OAuth authentication. Extend this implementation by adding more MCP tools with the `@mcp.tool` decorator, implementing scope-based authorization using custom middleware, integrating with your existing FastAPI application, or adding features like rate limiting and request logging using FastAPI’s middleware pipeline. --- # DOCUMENT BOUNDARY --- # FastMCP quickstart > FastMCP todo server with OAuth scope validation and CRUD operations. This guide shows you how to build a production-ready FastMCP server protected by Scalekit’s OAuth authentication. You’ll register your server as a protected resource, implement scope-based authorization for CRUD operations, and validate tokens on every request. Use this quickstart to experience a working reference implementation with a simple todo application. The todo app demonstrates how to enforce `todo:read` and `todo:write` scopes across multiple tools. After completing this guide, you can apply the same authentication pattern to secure your own FastMCP tools. The full code is available on [GitHub](https://github.com/scalekit-inc/mcp-demo/tree/main/todo-fastmcp). **Prerequisites** * A [Scalekit account](https://app.scalekit.com) with permission to manage MCP servers * **Python 3.11+** installed locally * Familiarity with OAuth scopes and basic terminal commands 1. ## Register your MCP server in Scalekit [Section titled “Register your MCP server in Scalekit”](#register-your-mcp-server-in-scalekit) Create a protected resource entry so Scalekit can issue scoped tokens that FastMCP validates on every request. 1. Navigate to **[Dashboard](https://app.scalekit.com) > MCP Servers > Add MCP Server**. 2. Enter a descriptive name (for example, `FastMCP Todo Server`). 3. Set **Server URL** to `http://localhost:3002/` (keep the trailing slash). This field is a required.\ For a server running at `http://localhost:3002/mcp`, register `http://localhost:3002/`. FastMCP appends `/mcp` automatically, so always provide the base URL with a trailing slash. 4. Create or link the scopes below, then click **Save**. ![Register FastMCP server](/.netlify/images?url=_astro%2Fregister-fastmcp.yj75FoPt.png\&w=772\&h=1316\&dpl=6a01bf5aba8408000850fe26) | Scope | Description | Required | | ------------ | -------------------------------------------- | -------- | | `todo:read` | Grants read access to todo tasks | Yes | | `todo:write` | Allows creating, updating, or deleting tasks | Yes | 2. ## Create your FastMCP todo server [Section titled “Create your FastMCP todo server”](#create-your-fastmcp-todo-server) Prepare a fresh directory and virtual environment to keep FastMCP dependencies isolated. Terminal ```bash 1 mkdir -p fastmcp-todo 2 cd fastmcp-todo 3 python3 -m venv venv 4 source venv/bin/activate ``` 3. ## Add dependencies and configuration templates [Section titled “Add dependencies and configuration templates”](#add-dependencies-and-configuration-templates) Create the support files that FastMCP and Scalekit expect, then install the required libraries. Terminal ```bash 1 cat <<'EOF' > requirements.txt 2 fastmcp>=2.13.0.2 3 python-dotenv>=1.0.0 4 EOF 5 6 pip install -r requirements.txt 7 8 cat <<'EOF' > env.example 9 PORT=3002 10 SCALEKIT_ENVIRONMENT_URL=https://your-environment-url.scalekit.com 11 SCALEKIT_CLIENT_ID=your_client_id 12 SCALEKIT_RESOURCE_ID=mcp_server_id 13 MCP_URL=http://localhost:3002/ 14 EOF ``` 4. ## Implement the FastMCP todo server [Section titled “Implement the FastMCP todo server”](#implement-the-fastmcp-todo-server) Copy the following code into `server.py`. It registers the Scalekit provider, defines an in-memory todo store, and exposes CRUD tools guarded by OAuth scopes. server.py ```python 1 """Scalekit-authenticated FastMCP server providing in-memory CRUD tools for todos. 2 3 This example demonstrates how to protect FastMCP tools with OAuth scopes. 4 Each tool validates the required scope before executing operations. 5 """ 6 7 import os 8 import uuid 9 from dataclasses import dataclass, asdict 10 from typing import Optional 11 12 from dotenv import load_dotenv 13 from fastmcp import FastMCP 14 from fastmcp.server.auth.providers.scalekit import ScalekitProvider 15 from fastmcp.server.dependencies import AccessToken, get_access_token 16 17 load_dotenv() 18 19 # Use case: Configure FastMCP server with OAuth protection 20 # Security: Scalekit provider validates every request's Bearer token 21 mcp = FastMCP( 22 "Todo Server", 23 stateless_http=True, 24 auth=ScalekitProvider( 25 environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), 26 client_id=os.getenv("SCALEKIT_CLIENT_ID"), 27 resource_id=os.getenv("SCALEKIT_RESOURCE_ID"), 28 # FastMCP appends /mcp automatically; keep base URL with trailing slash only 29 mcp_url=os.getenv("MCP_URL"), 30 ), 31 ) 32 33 34 @dataclass 35 class TodoItem: 36 id: str 37 title: str 38 description: Optional[str] 39 completed: bool = False 40 41 def to_dict(self) -> dict: 42 return asdict(self) 43 44 45 # Use case: In-memory storage for demo purposes 46 # Production: Replace with your database or persistent storage 47 _TODO_STORE: dict[str, TodoItem] = {} 48 49 50 def _require_scope(scope: str) -> Optional[str]: 51 """ 52 Security: Validate that the current request's token includes the required scope. 53 This prevents unauthorized access to protected operations. 54 """ 55 token: AccessToken = get_access_token() 56 if scope not in token.scopes: 57 return f"Insufficient permissions: `{scope}` scope required." 58 return None 59 60 61 @mcp.tool 62 def create_todo(title: str, description: Optional[str] = None) -> dict: 63 """ 64 Use case: Create a new todo item for task tracking 65 Requires: todo:write scope 66 """ 67 error = _require_scope("todo:write") 68 if error: 69 return {"error": error} 70 71 todo = TodoItem(id=str(uuid.uuid4()), title=title, description=description) 72 _TODO_STORE[todo.id] = todo 73 return {"todo": todo.to_dict()} 74 75 76 @mcp.tool 77 def list_todos(completed: Optional[bool] = None) -> dict: 78 """ 79 Use case: Retrieve all todos, optionally filtered by completion status 80 Requires: todo:read scope 81 """ 82 error = _require_scope("todo:read") 83 if error: 84 return {"error": error} 85 86 todos = [ 87 todo.to_dict() 88 for todo in _TODO_STORE.values() 89 if completed is None or todo.completed == completed 90 ] 91 return {"todos": todos} 92 93 94 @mcp.tool 95 def get_todo(todo_id: str) -> dict: 96 """ 97 Use case: Retrieve a specific todo by ID 98 Requires: todo:read scope 99 """ 100 error = _require_scope("todo:read") 101 if error: 102 return {"error": error} 103 104 todo = _TODO_STORE.get(todo_id) 105 if todo is None: 106 return {"error": f"Todo `{todo_id}` not found."} 107 108 return {"todo": todo.to_dict()} 109 110 111 @mcp.tool 112 def update_todo( 113 todo_id: str, 114 title: Optional[str] = None, 115 description: Optional[str] = None, 116 completed: Optional[bool] = None, 117 ) -> dict: 118 """ 119 Use case: Update existing todo properties or mark as complete 120 Requires: todo:write scope 121 """ 122 error = _require_scope("todo:write") 123 if error: 124 return {"error": error} 125 126 todo = _TODO_STORE.get(todo_id) 127 if todo is None: 128 return {"error": f"Todo `{todo_id}` not found."} 129 130 if title is not None: 131 todo.title = title 132 if description is not None: 133 todo.description = description 134 if completed is not None: 135 todo.completed = completed 136 137 return {"todo": todo.to_dict()} 138 139 140 @mcp.tool 141 def delete_todo(todo_id: str) -> dict: 142 """ 143 Use case: Remove a todo from the system 144 Requires: todo:write scope 145 """ 146 error = _require_scope("todo:write") 147 if error: 148 return {"error": error} 149 150 todo = _TODO_STORE.pop(todo_id, None) 151 if todo is None: 152 return {"error": f"Todo `{todo_id}` not found."} 153 154 return {"deleted": todo_id} 155 156 157 if __name__ == "__main__": 158 # Start HTTP transport server 159 mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) ``` 5. ## Provide runtime secrets [Section titled “Provide runtime secrets”](#provide-runtime-secrets) Copy the environment template and populate the values from your Scalekit dashboard. Terminal ```bash 1 cp env.example .env 2 open .env ``` | Variable | Description | | -------------------------- | ---------------------------------------------------------------------------------------- | | `SCALEKIT_ENVIRONMENT_URL` | Your Scalekit environment URL from **Dashboard > Settings** | | `SCALEKIT_CLIENT_ID` | Client ID from **Dashboard > Settings** | | `SCALEKIT_RESOURCE_ID` | The resource identifier assigned to your MCP server (starts with `res_`) | | `MCP_URL` | The base public URL you registered (keep trailing slash, e.g., `http://localhost:3002/`) | | `PORT` | Local port for FastMCP HTTP transport (defaults to `3002`) | Store secrets securely Avoid committing `.env` to source control. Use your team’s secret manager in production and rotate credentials if they appear in logs or terminal history. 6. ## Run the FastMCP server locally [Section titled “Run the FastMCP server locally”](#run-the-fastmcp-server-locally) Start the server so it can accept authenticated MCP requests at `/mcp`. Terminal ```bash 1 source venv/bin/activate 2 python server.py ``` When the server boots successfully, you’ll see FastMCP announce the HTTP transport and listen on `http://localhost:3002/`, ready to enforce Scalekit-issued tokens. ![Run MCP server](/.netlify/images?url=_astro%2Fvenv-activate-fastmcp.UYaMwNRn.png\&w=2986\&h=926\&dpl=6a01bf5aba8408000850fe26) 7. ## Connect with an MCP client [Section titled “Connect with an MCP client”](#connect-with-an-mcp-client) Use any MCP-compatible client to exercise the todo tools with scoped tokens. During development, the MCP Inspector demonstrates how the Scalekit provider enforces scopes end-to-end. Terminal ```bash 1 npx @modelcontextprotocol/inspector@latest ``` In the Inspector UI, point the client to `http://localhost:3002/mcp` and click **Connect**. The client initiates OAuth authentication with Scalekit. After successful authentication, run any tool—the server exposes `create_todo`, `list_todos`, `get_todo`, `update_todo`, and `delete_todo`. ![MCP Inspector](/.netlify/images?url=_astro%2Fmcp-inspector-fastmcp.CcqqKz2X.png\&w=3024\&h=1502\&dpl=6a01bf5aba8408000850fe26) Once you’re satisfied with the quickstart example, extend `server.py` with your own FastMCP tools or replace the in-memory store with your production data source. Scalekit’s provider handles authentication for any toolset you add. --- # DOCUMENT BOUNDARY --- # New to MCP? > Lock down MCP connections with OAuth 2.1 so agents get only the access they need AI systems are moving beyond chatbots to agents that act in the real world. They handle sensitive data and run complex workflows. As they grow, they need a secure, standard way to connect. The Model Context Protocol (MCP) provides that standard. It defines how AI applications safely discover and use external tools and data. MCP incorporates OAuth 2.1 authorization mechanisms at the transport level. This enables MCP clients to make secure requests to restricted MCP servers on behalf of resource owners. | Features | Benefit | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | Industry standard | Well-established authorization framework with extensive tooling and ecosystem support | | Security best practices | Incorporates improvements over OAuth 2.0, removing deprecated flows and enforcing security measures like PKCE | | Multiple grant types | Supports different use cases: **Authorization code** for human user scenarios and **Client credentials** for machine-to-machine integrations | | Ecosystem compatibility | Integrates with existing identity providers and authorization servers | ## Complete MCP OAuth 2.1 flow [Section titled “Complete MCP OAuth 2.1 flow”](#complete-mcp-oauth-21-flow) Here’s the complete end-to-end authorization flow showing all phases from discovery to token refresh in a single sequence diagram: ### Understanding the MCP authorization flow [Section titled “Understanding the MCP authorization flow”](#understanding-the-mcp-authorization-flow) MCP OAuth 2.1 provides secure, standardized authorization for AI agents accessing protected resources. The flow establishes trust, authenticates users, authorizes access, and maintains security throughout the session lifecycle by building each phase on the previous one. --- # DOCUMENT BOUNDARY --- # Managing MCP Clients > Manage MCP clients by viewing registered MCP clients, tracking user consent, and revoking access to your MCP servers. To maintain security and control over your MCP Server, you need to manage which client applications can access it. Scalekit provides several ways for clients to connect, including automatic registration for modern apps and manual pre-registration for custom or trusted clients. This guide covers the different types of MCP clients and shows you how to: * View all registered clients * See which users have granted consent to a client * Revoke user access for any client There are three main categories of MCP Clients that can interact with your MCP Server: ## 1. Automatic registration with DCR [Section titled “1. Automatic registration with DCR”](#1-automatic-registration-with-dcr) These are MCP Clients that automatically register themselves as OAuth clients. Most modern MCP clients, such as Claude Desktop, OpenAI, VS Code, and Cursor, support Dynamic Client Registration (DCR). They initiate the registration process and start the OAuth Authorization flow with the Scalekit server to obtain an access token without requiring manual configuration. During the consent flow, users see your **environment domain** as the requesting identifier — not “Scalekit” and not your application name. This is by design: the domain identifies the authorization server handling the request, similar to how Google OAuth shows the requesting domain. To display a custom branded domain, configure a [custom domain](/agentkit/advanced/custom-domain) for your Scalekit environment. ## 2. Manual client pre-registration [Section titled “2. Manual client pre-registration”](#2-manual-client-pre-registration) These are MCP Clients that you manually register in the Scalekit Dashboard. This is useful when you want to restrict access to specific, pre-approved clients or when you are building a custom client that requires fixed credentials. You can create OAuth clients that can either act as themselves or on behalf of the user. ### How to pre-register a client [Section titled “How to pre-register a client”](#how-to-pre-register-a-client) If you need to manually register an MCP Client, you can do so in the Scalekit Dashboard. 1. Navigate to the **Clients** section of your MCP Server. 2. Click the **Create Client** button. ![Create Client](/_astro/mcp_create_client.lIT_Y1hO.png) **Configuration:** * **Client name**: A display name (e.g., “My Custom Client”). * **Redirect URI**: The URL where the client will redirect users after authorization. 3. **Choosing the right OAuth flow:** * **For Client Credentials Flow**: Leave the Redirect URI field empty. Your application will authenticate using only the `client_id` and `client_secret`. This is suitable for server-to-server communication. * **For Authorization Code Grant Flow**: Provide one or more Redirect URIs where users will be redirected after granting consent. This is required for user-facing applications that need to act on behalf of users. Once the client is created, you will receive a `client_id` and `client_secret` to configure in your application. ![Redirect URI](/_astro/mcp_configure_client.CQDvSRQa.png) ### 2.1 OAuth client credential flow [Section titled “2.1 OAuth client credential flow”](#21-oauth-client-credential-flow) Use this flow when your MCP Client needs to act on its own behalf rather than on behalf of a specific user. This is ideal for machine-to-machine communication scenarios. **When to use:** * Backend services or server-side applications * Automated scripts or batch processes * System integrations that don’t require user interaction * Applications that need to access resources without user context **Characteristics:** * No user interaction required * No redirect URI needed * Client authenticates using `client_id` and `client_secret` * Access token represents the client itself ### 2.2 OAuth authorization code grant flow [Section titled “2.2 OAuth authorization code grant flow”](#22-oauth-authorization-code-grant-flow) Use this flow when your MCP Client needs to act on behalf of a user. This is the standard OAuth flow that requires user consent. **When to use:** * User-facing applications (web, desktop, or mobile) * Applications that need to access user-specific resources * Scenarios requiring explicit user consent * Applications where actions should be attributed to specific users **Characteristics:** * Requires user authentication and consent * Redirect URI is mandatory * Client receives authorization code, exchanges it for access token * Access token represents the user’s authorization ## 3. Registration via metadata URL (CIMD) [Section titled “3. Registration via metadata URL (CIMD)”](#3-registration-via-metadata-url-cimd) These are MCP Clients that support Client ID Metadata Document (CIMD), an OAuth 2.0 mechanism that allows clients to use a URL as their client identifier. When a CIMD-compatible client initiates the OAuth flow, Scalekit fetches the client’s metadata (such as name, redirect URIs, and other registration information) from the provided URL. This provides an alternative registration method without requiring manual pre-registration or Dynamic Client Registration, making it easier for clients to authenticate across different authorization servers. ## Manage registered clients [Section titled “Manage registered clients”](#manage-registered-clients) ### View all registered clients [Section titled “View all registered clients”](#view-all-registered-clients) You can view a list of all MCP Clients that have been registered with your MCP Server (both DCR and pre-registered) in the Scalekit Dashboard. 1. Go to your MCP Server in the dashboard. 2. Click on the **Clients** tab. ![View all MCP Clients](/.netlify/images?url=_astro%2Fview_all_clients.ClEAh2pi.png\&w=2544\&h=896\&dpl=6a01bf5aba8408000850fe26) ### View consented users [Section titled “View consented users”](#view-consented-users) For each registered MCP Client that uses the OAuth Authorization Code Grant Flow, you can view all users who have granted consent. 1. From the **Clients** list, click on a specific client. 2. Navigate to the **Consents** tab to see the list of users who have authorized this client. ![View Consented Users](/.netlify/images?url=_astro%2Fview_consented_users.bNB41DHP.png\&w=2050\&h=1500\&dpl=6a01bf5aba8408000850fe26) ### Revoke user access [Section titled “Revoke user access”](#revoke-user-access) As an administrator, you can revoke a user’s consent for a specific MCP Client at any time. This is useful when: * A user requests to revoke access * You need to remove access for security reasons * An employee leaves the organization * You want to force re-authentication **To revoke access:** 1. Navigate to the specific MCP Client from the **Clients** list. 2. Go to the **Consents** tab. 3. Find the user whose access you want to revoke. 4. Click the **Revoke** or **Delete** action for that user. Once revoked, the user will need to go through the authorization flow again to grant consent if they want to use the MCP Client. --- # DOCUMENT BOUNDARY --- # Agent / Machine interacting with MCP Server > Learn how an autonomous agent or machine securely authenticates with an MCP Server using OAuth 2.1 Client Credentials flow in Scalekit. An **autonomous agent** or any **machine-to-machine process** can directly interact with an **MCP Server** secured by Scalekit. In this model, the agent acts as a **confidential OAuth client**, authenticated using a `client_id` and `client_secret` issued by Scalekit. This topology uses the **OAuth 2.1 Client Credentials flow**, allowing the agent to obtain an access token without user interaction. Tokens are scoped and time-bound, ensuring secure and auditable automation between services. *** ## Authorization Sequence [Section titled “Authorization Sequence”](#authorization-sequence) *** ## How It Works [Section titled “How It Works”](#how-it-works) **Client Registration** Before an agent can request tokens, you must create a **Machine-to-Machine (M2M) client** for your MCP Server in Scalekit. Steps to create a client: 1. Navigate to **Dashboard ? MCP Servers** and select your MCP Server. Go to the **Clients** tab. ![Clients tab placeholder](/.netlify/images?url=_astro%2Fmcp-client-nav.C6UPUhIu.png\&w=1148\&h=1242\&dpl=6a01bf5aba8408000850fe26) 2. Click **Create Client**. ![Create client placeholder](/.netlify/images?url=_astro%2Fmcp-clients-tab.UgPaVUGm.png\&w=3020\&h=1040\&dpl=6a01bf5aba8408000850fe26) 3. Copy the **client\_id** and **client\_secret** immediately - the secret will not be shown again. ![Client Sidesheet](/.netlify/images?url=_astro%2Fmcp-client-sidesheet.D9KN4b5q.png\&w=3020\&h=1500\&dpl=6a01bf5aba8408000850fe26) 4. Optionally, set scopes (e.g., `todo:read`, `todo:write`) that correspond to the permissions configured for your MCP Server. Hit **Save** *** ## Requesting an Access Token [Section titled “Requesting an Access Token”](#requesting-an-access-token) Once you have the client credentials, the agent can request a token directly from the Scalekit Authorization Server: Terminal ```bash 1 curl --location '{{env_url}}/oauth/token' \ 2 --header 'Content-Type: application/x-www-form-urlencoded' \ 3 --data-urlencode 'grant_type=client_credentials' \ 4 --data-urlencode 'client_id={{client_id}}' \ 5 --data-urlencode 'client_secret={{secret_value}}' \ 6 --data-urlencode 'scope=todo:read todo:write' ``` Scalekit responds with a JSON payload similar to: ```json 1 { 2 "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0...", 3 "token_type": "Bearer", 4 "expires_in": 3600, 5 "scope": "todo:read todo:write" 6 } ``` Use the `access_token` in the `Authorization` header when calling your MCP Server’s endpoint. *** ## Try It Yourself [Section titled “Try It Yourself”](#try-it-yourself) If you’d like to simulate this flow, use the same **FastMCP Todo Server** from the [FastMCP Example](/authenticate/mcp/fastmcp-quickstart). Create an **M2M client** in the Scalekit Dashboard and run your token request using `curl` or programmatically within your agent. Once the token is obtained, attach it as a Bearer token in the `Authorization` header when calling your MCP Server’s tools. --- # DOCUMENT BOUNDARY --- # Human interacting with MCP Server > Learn how a human authenticates with an MCP Server via OAuth 2.1 when using MCP-compliant hosts such as ChatGPT, Claude, VSCode, or Windsurf. When a human uses a compliant MCP host, that host acts as the OAuth client. It initiates authorization with the Scalekit Authorization Server, obtains a scoped access token, and interacts securely with the MCP Server on behalf of the user. This topology represents the most common interaction model for real-world MCP usecases - **humans interacting with an MCP**, while Scalekit ensures tokens are valid, scoped, and auditable. *** ## Authorization Sequence [Section titled “Authorization Sequence”](#authorization-sequence) *** ## How It Works [Section titled “How It Works”](#how-it-works) 1. **Initiation** ? The human configures an MCP server in their MCP client. 2. **Challenge** ? The MCP Server responds with an HTTP `401` containing a `WWW-Authenticate` header that points to the Scalekit Authorization Server. 3. **Authorization Flow** ? The MCP Client opens the user’s browser to initiate the OAuth 2.1 authorization flow. During this step, the Scalekit Authorization Server handles user authentication through Passwordless, Passkeys, Social login providers (like Google, GitHub, or LinkedIn), or Enterprise SSO integrations (such as Okta, Microsoft Entra ID, or ADFS). The user is then prompted to grant consent for the requested scopes. Once approved, Scalekit returns an authorization code, which the MCP Client exchanges for an access token. 4. **Token Issuance** ? Scalekit issues an OAuth 2.1 access token containing claims and scopes (for example, `todo:read`, `calendar:write`) that represent the user’s permissions. 5. **Authorized Request** ? The client calls the MCP Server again, now attaching the Bearer token in the `Authorization` header. 6. **Validation and Execution** ? The MCP Server validates the token issued by scalekit and executes the requested tool. *** ## Try It Yourself [Section titled “Try It Yourself”](#try-it-yourself) Head to the **[FastMCP Examples section](/authenticate/mcp/fastmcp-quickstart)** to experience this topology in action. There you’ll register a FastMCP server, configure Scalekit Auth, and observe token issuance and validation end-to-end. --- # DOCUMENT BOUNDARY --- # MCP Server interacting with MCPs / APIs > Understand how an MCP Server integrates with internal systems, other MCP servers, or external APIs using secure tokens or API keys. In real-world scenarios, an **MCP Server** often needs to make backend calls - to your **own APIs**, to **another MCP Server**, or to **external APIs** such as CRM, ticketing, or SaaS tools. This page explains three secure ways to perform these downstream integrations, each corresponding to a different trust boundary and authorization pattern. ## 1. Using API Keys or Custom Tokens [Section titled “1. Using API Keys or Custom Tokens”](#1-using-api-keys-or-custom-tokens) Your MCP Server can communicate with internal or external backend systems that have their own authorization servers or API key?based access. In this setup, the MCP Server manages its own credentials securely (for example, an environment variable, vault, or secrets manager) and injects them when making downstream calls. ### Example [Section titled “Example”](#example) * The MCP Server stores an API key as `EXTERNAL_API_KEY` in environment variables. * When a tool (e.g., `get_weather_data`) is called, your MCP server attaches the key in the request. * The backend API validates the key and responds with data. *** ## 2. Interacting with Another MCP Server autonomously [Section titled “2. Interacting with Another MCP Server autonomously”](#2-interacting-with-another-mcp-server-autonomously) If you have two MCP Servers that need to communicate - for example, `crm-mcp` calling tools from `tickets-mcp` - you can follow the same authentication pattern described in the [Agent ? MCP](/authenticate/mcp/topologies/agent-mcp/) topology. The calling MCP Server (in this case, `crm-mcp`) acts as an **autonomous agent**, authenticating with the receiving MCP Server via **OAuth 2.1 Client Credentials Flow**. Once the token is issued by Scalekit, the calling MCP uses it to call tools exposed by the second MCP Server. You can find a detailed explanation of this topology in [this section](/authenticate/mcp/topologies/agent-mcp). *** ## 3. Cascading the Same Token to Downstream Systems [Section titled “3. Cascading the Same Token to Downstream Systems”](#3-cascading-the-same-token-to-downstream-systems) In some cases, you may want your MCP Server to forward (or “cascade”) the **same access token** it received from the client - for example, when your backend system lies within the same trust boundary as the Scalekit Authorization Server and can validate the token based on its issuer, scopes, and expiry. ### When to Use This Pattern [Section titled “When to Use This Pattern”](#when-to-use-this-pattern) * Both systems (MCP Server and backend MCP/API) trust **the same Authorization Server** (Scalekit). * The backend API can validate JWTs using public keys or JWKS URL. * Scopes and issuer claims (`iss`, `scope`, `exp`) are sufficient to determine access. --- # DOCUMENT BOUNDARY --- # Troubleshooting MCP auth > Troubleshooting guide for common errors while adding auth for MCP Servers This guide helps you diagnose and resolve common issues when integrating Scalekit as an authentication server for your MCP servers. When you add authentication to MCP servers, you may encounter configuration problems, network issues, or client-specific limitations. This reference covers the most common scenarios and provides step-by-step solutions. Use this guide to troubleshoot setup problems, resolve CORS and network issues, understand client-specific behavior, and implement best practices for your authentication setup. ## Configuration & Setup Issues [Section titled “Configuration & Setup Issues”](#configuration--setup-issues) ### My POST to `/auth-requests/` returns a 404 or “invalid ID” error [Section titled “My POST to /auth-requests/ returns a 404 or “invalid ID” error”](#my-post-to-auth-requests-returns-a-404-or-invalid-id-error) You may be passing the MCP server’s resource ID instead of the connection ID in the URL path. These are two different identifiers with different purposes: | Identifier | Format | Purpose | | --------------- | ---------- | ----------------------------------------------------------------------------- | | `resource_id` | `res_xxx` | Identifies the MCP server; used in token audiences and client registration | | `connection_id` | `conn_xxx` | Identifies your BYOA auth connection; required in `/auth-requests/` endpoints | The correct endpoint uses `connection_id`: ```txt 1 /api/v1/connections//auth-requests//user ``` To find your `connection_id`: open **Dashboard > MCP Servers > \[your server] > Advanced Configurations > Connection ID**. *** ### I’m getting an access token but no refresh token [Section titled “I’m getting an access token but no refresh token”](#im-getting-an-access-token-but-no-refresh-token) Add the `offline_access` scope to your authorization request. Without it, Scalekit does not issue a refresh token alongside the access token. Include it with your other scopes: ```plaintext 1 openid profile email offline_access ``` Once added, subsequent logins will return both an access token and a refresh token. *** ### My MCP server is not connecting to the MCP Inspector [Section titled “My MCP server is not connecting to the MCP Inspector”](#my-mcp-server-is-not-connecting-to-the-mcp-inspector) When your MCP server fails to connect to the MCP Inspector, this typically indicates a problem with the authentication handshake or metadata configuration. Follow these diagnosis steps to identify the issue. **Verify the MCP server is responding correctly:** 1. Open your browser’s developer tools (Network tab) 2. Navigate to your MCP server URL (e.g., `http://localhost:3002/`) 3. Confirm the response returns a `401` status code 4. Check the response headers for `www-authenticate` containing `resource_metadata=""` **Validate the metadata:** 1. Copy the metadata URL from the `www-authenticate` header 2. Open it in your browser 3. Confirm the JSON structure matches what you see in your Scalekit dashboard ### I’m getting a redirect\_uri mismatch error during authorization [Section titled “I’m getting a redirect\_uri mismatch error during authorization”](#im-getting-a-redirect_uri-mismatch-error-during-authorization) This error typically occurs when your MCP client has cached an old MCP server domain after you’ve changed it. The client continues sending requests to the old URL, which doesn’t match your current Scalekit configuration. **Clear cached authentication by client type:** **MCP-Remote:** 1. Delete the cached configuration folder: `~/.mcp-auth/mcp-remote-` 2. Reconnect to your MCP server **VS Code:** 1. Open the Command Palette (Cmd/Ctrl + Shift + P) 2. Search for **Authentication: Remove Dynamic Authentication Provider** 3. Select and remove the cached entry 4. Reconnect to your MCP server **Claude Desktop:** Caution Claude Desktop does not currently support clearing cached authentication data. As a workaround, use a different domain or subdomain for your MCP server, or contact Claude support for assistance. ### GitHub Copilot CLI: stale cached credentials after environment switch [Section titled “GitHub Copilot CLI: stale cached credentials after environment switch”](#github-copilot-cli-stale-cached-credentials-after-environment-switch) GitHub Copilot CLI caches OAuth client credentials locally. If you switch your Scalekit environment (for example, from US to EU), the cached `client_id` no longer matches the new environment and login fails with `unable to retrieve client by id`. **Resolution:** 1. Locate and delete the cached OAuth config files: ```sh 1 rm -rf ~/.copilot/mcp-oauth-config ``` 2. Reconnect your MCP server in GitHub Copilot CLI — it will register a fresh client against the correct environment. *** ## CORS & Network Issues [Section titled “CORS & Network Issues”](#cors--network-issues) ### I see CORS errors in the network logs when using MCP Inspector [Section titled “I see CORS errors in the network logs when using MCP Inspector”](#i-see-cors-errors-in-the-network-logs-when-using-mcp-inspector) CORS errors occur when your MCP client cannot make cross-origin requests to your Scalekit environment during the authentication handshake. This prevents the authentication flow from completing successfully. **Resolution:** 1. Navigate to **Dashboard > Authentication > Redirect URLs > Allowed Callback URLs** 2. Add your MCP Inspector URL to the allowed list: `http://localhost:6274/` 3. Retry the connection ### Calls from the MCP client are not reaching my MCP server [Section titled “Calls from the MCP client are not reaching my MCP server”](#calls-from-the-mcp-client-are-not-reaching-my-mcp-server) If requests from your MCP client silently fail to reach your server, a proxy or firewall may be blocking them. This often happens in corporate environments or when using CDN services. **Troubleshooting steps:** 1. Check if you’re using a proxy (e.g., Cloudflare, AWS WAF, corporate proxy) 2. Configure your proxy to allow or exempt requests from your MCP client to your server domain 3. Review proxy logs to confirm whether requests are being blocked 4. Test direct connectivity from your client machine to your MCP server (without proxy, if possible) *** ## Client-Specific Issues [Section titled “Client-Specific Issues”](#client-specific-issues) ### Claude Desktop ignores custom ports when connecting to MCP servers [Section titled “Claude Desktop ignores custom ports when connecting to MCP servers”](#claude-desktop-ignores-custom-ports-when-connecting-to-mcp-servers) Claude Desktop currently only supports standard HTTPS traffic on port `443`. If your MCP server runs on a custom port (e.g., `https://mymcp.internal:8443/`), Claude Desktop will still attempt to connect to port `443`, causing the connection to fail. **Workaround options:** 1. Expose your MCP server on port `443` (requires a proxy or load balancer) 2. Use a reverse proxy that listens on `443` and forwards requests to your custom port ### Multiple authentication tabs open when using both MCP-Remote and Claude Desktop [Section titled “Multiple authentication tabs open when using both MCP-Remote and Claude Desktop”](#multiple-authentication-tabs-open-when-using-both-mcp-remote-and-claude-desktop) Recent versions of Claude Desktop have introduced Connectors functionality, eliminating the need to run MCP-Remote separately. Claude Desktop includes a **Custom Connector** feature that allows you to configure MCP servers directly without additional tools. **Recommendation:** * Use Claude Desktop’s built-in Custom Connector feature for MCP server management * Disable or stop MCP-Remote if you’re only using Claude Desktop * If you have a specific use case requiring both, contact Claude’s official support ### My browser is not getting invoked during authentication [Section titled “My browser is not getting invoked during authentication”](#my-browser-is-not-getting-invoked-during-authentication) Some MCP clients require permission to open your default browser during the authentication flow. If your browser doesn’t launch, the authentication handshake may timeout, preventing successful authentication. **Resolution by operating system:** **macOS:** 1. Open **System Preferences > Security & Privacy > App Management** 2. Ensure the MCP client has permission to open applications 3. Restart your MCP client **Windows:** 1. Navigate to **Settings > Privacy > App permissions** 2. Enable **Allow apps to manage your default app settings** 3. Restart your MCP client **Linux:** 1. Ensure `xdg-open` or your default browser opener is installed: `which xdg-open` 2. Verify the command is accessible from your terminal 3. Restart your MCP client *** ## Best practices [Section titled “Best practices”](#best-practices) Follow these best practices to avoid common issues and maintain a robust MCP authentication setup: 1. **Use separate Scalekit environments** for development and production to prevent configuration conflicts 2. **Register MCP servers with environment-specific domains:** * Development: `https://mcp-dev.yourdomain.com/` * Production: `https://mcp.yourdomain.com/` 3. **Update your MCP client configuration** to point to the correct Scalekit environment for each deployment 4. **Test authentication independently** in each environment before deploying to production 5. **Monitor authentication logs** in **Dashboard > Authentication > Logs** to identify and resolve issues quickly 6. **Keep callback URLs updated** whenever you change domains or ports