Your MCP tools often need to call external APIs as the authenticated user: GitHub for issues and pull requests, Google for calendar or Drive, Slack for messages, or your own internal APIs. Delegated access is the pattern that lets the tool do that without shared service accounts, copied API keys, or blanket OAuth grants.
Keycard handles the token exchange: you write tools, Keycard manages OAuth flows, credential exchange, policy checks, audit, and per-user refresh.
How delegated access works
Section titled “How delegated access works”Token exchange lets your MCP server act as both a protected resource (receiving authenticated requests from AI agents) and an API client (calling upstream APIs on behalf of the authenticated user).
- User authenticates to your MCP server with a Keycard token
- Your MCP server exchanges that token for an external API token scoped to the user
- Your MCP server calls the external API with the exchanged token
- The user’s data is accessed with their own permissions, not a shared service account
Keycard setup
Section titled “Keycard setup”These steps are the same regardless of which external API you’re integrating. Complete them before following a provider-specific guide.
-
Copy your Keycard redirect URL
In Keycard Console, navigate to Zone Settings and copy the OAuth2 Redirect URL. You will need this when creating the OAuth App at your provider.
-
Create an OAuth App at your provider
In your provider’s developer console (e.g., GitHub Developer Settings, Google Cloud Console), create an OAuth App. Set the authorization callback / redirect URI to the URL you copied in step 1. Note the Client ID and Client Secret. You will need them in the next step.
-
Add the API from the Catalog
In Keycard Console, navigate to Applications -> Add Application -> Explore API Servers and add the API you want to integrate. Enter the Client ID and Client Secret from step 2. For per-vendor setup instructions, see the API server catalog - we have ready-to-use entries for GitHub, Gmail, Google Calendar, Google Drive, Jira, Confluence, Linear, Sentry, Slack, and Attio.
-
Register your MCP server Resource
Navigate to Resources → Create Resource:
Field Value Resource Name Your MCP Server Resource Identifier http://localhost:8000/mcpCredential Provider Zone Provider -
Create an Application
Navigate to Applications → Create Application:
Field Value Provided Resource Your MCP Server Dependency The external API Resource(s) After creating the Application, generate client credentials (Client ID + Client Secret) and save them. These go into your environment as
KEYCARD_CLIENT_IDandKEYCARD_CLIENT_SECRET. Your MCP server passes them toAuthProviderso it can exchange tokens on behalf of users.
The Grant Pattern
Section titled “The Grant Pattern”Every third-party integration follows the same steps:
- Add from Resource Catalog (or manually create the provider and resource for any OAuth 2.0 API)
- Register your MCP server Resource: set Credential Provider to Zone Provider
- Create an Application: set your MCP server as the Provided Resource and external APIs as Dependencies
- Generate client credentials: Client ID + Client Secret for your Application
- Use the grant pattern in code:
@auth_provider.grant("https://api.example.com")async def my_tool(ctx: Context): access_context: AccessContext = await ctx.get_state("keycardai") token = access_context.access("https://api.example.com").access_token # Call API with tokenapp.get("/api/endpoint", authProvider.grant("https://api.example.com"), async (req, res) => { const { accessContext } = req as DelegatedRequest; const token = accessContext.access("https://api.example.com").accessToken; // Call API with token});This works for any OAuth 2.0 provider: Slack, Linear, Notion, and more.
Provider Guides
Section titled “Provider Guides”Follow a provider-specific guide to see the full implementation:
Production Notes
Section titled “Production Notes”When deploying to production:
- Update Resource Identifiers in Keycard Console to your production URLs (must use HTTPS)
- Update OAuth redirect URIs in GitHub/Google to match your production Keycard zone
- Set environment variables securely: never commit client secrets to source control
- Token caching and refresh is handled automatically by Keycard
Troubleshooting
Section titled “Troubleshooting”Token Exchange Fails
Section titled “Token Exchange Fails”Symptom: access_context.has_errors() returns True (Python) / accessContext.hasErrors() returns true (TypeScript)
- Verify the external API Resource is added as a Dependency on your Application
- Check that the OAuth provider credentials (in Resource Catalog) are correct
- Ensure the user has completed the OAuth consent flow for the external API
- Confirm your MCP server is reachable at the registered Resource Identifier URL
Invalid or Expired Token
Section titled “Invalid or Expired Token”Symptom: External API returns 401 Unauthorized
- Token refresh is automatic. If this persists, check provider configuration
- Verify the scopes in Keycard Console match what the API requires
- The user may need to re-authorize if they revoked access
Scope Mismatch
Section titled “Scope Mismatch”Symptom: External API returns 403 Forbidden or missing data
- Ensure scopes in Keycard Console match what the API requires
- For Google: verify the required APIs are enabled in Google Cloud Console
- User may need to disconnect and reconnect to pick up new scopes