---
title: Add Delegated Access | Keycard
description: Let your MCP tools call GitHub, Google, and other APIs on behalf of authenticated users
---

Your MCP tools need to call external APIs as the user, not a shared service account. Keycard’s token exchange handles this — you write tools, Keycard manages OAuth flows, credential exchange, and refresh per-user.

Tip

Complete the [Build Your Own MCP Server](/guides/mcp-server/index.md) guide first. This guide builds on that pattern.

## How Token Exchange 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).

```
flowchart LR
    A[User] -->|Token A| B[AI Agent]
    B -->|Token A| C[Your MCP Server]
    C -->|Exchange A → B| D[Keycard]
    D -->|Token B| C
    C -->|Token B| E[External API]
```

1. User authenticates to your MCP server with a Keycard token (Token A)
2. Your MCP server exchanges Token A for an external API token (Token B)
3. Your MCP server calls the external API with the exchanged token
4. The user’s data is accessed with their own permissions — not a shared service account

## Keycard Setup

These steps are the same regardless of which external API you’re integrating. Complete them before following a provider-specific guide.

1. **Add the API from Resource Catalog**

   In [Keycard Console](https://console.keycard.ai), navigate to **Resource Catalog** and add the API you want to integrate (e.g., GitHub, Google Calendar, Google Drive). This automatically creates the OAuth provider and API resource.

   Note

   The Resource Catalog provides one-click setup for popular APIs. For APIs not in the catalog, you can [manually create providers and resources](/getting-started/core-concepts#resources/index.md) in Keycard Console — any OAuth 2.0 provider works.

2. **Set up the OAuth App callback**

   1. Go to **Zone Settings** in Keycard Console and copy the **OAuth2 Redirect URL**
   2. In the external provider’s developer console, create or edit your OAuth App
   3. Set the authorization callback / redirect URI to the Keycard redirect URL

3. **Register your MCP server resource**

   Navigate to **Resources** → **Create Resource**:

   | Field                   | Value                       |
   | ----------------------- | --------------------------- |
   | **Resource Name**       | Your MCP Server             |
   | **Resource Identifier** | `http://localhost:8000/mcp` |
   | **Credential Provider** | Zone Provider               |

   Note

   The resource identifier must match the URL where your MCP server is reachable.

4. **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.

## The Grant Pattern

Every third-party integration follows the same steps:

1. **Add from Resource Catalog** (or [manually create](/getting-started/core-concepts#resources/index.md) the provider and resource for any OAuth 2.0 API)
2. **Register your MCP server** — set Credential Provider to **Zone Provider**
3. **Create an Application** — set your MCP server as the **Provided Resource** and external APIs as **Dependencies**
4. **Generate client credentials** — Client ID + Client Secret for your application
5. **Use the grant pattern** in code:

- [Python](#tab-panel-61)
- [TypeScript](#tab-panel-62)
- [Go](#tab-panel-63)

```
@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 token
```

```
app.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
});
```

```
handler := authProvider.Grant("https://api.example.com")(
  http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ac := mcp.AccessContextFromRequest(r)
    token, _ := ac.Access("https://api.example.com")
    // Call API with token.AccessToken
  }),
)
```

This works for any OAuth 2.0 provider — Slack, Linear, Notion, and more.

## Provider Guides

Follow a provider-specific guide to see the full implementation:

[GitHub ](/guides/delegated-access/github/index.md)Build an MCP server with GitHub API tools

[Google Workspace ](/guides/delegated-access/google/index.md)Build an MCP server with Google Calendar and Drive tools

## 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

Tip

For infrastructure-as-code deployments, use the [Keycard Terraform Provider](https://registry.terraform.io/providers/keycardai/keycard/latest/docs) to programmatically configure providers, resources, and applications.

## Troubleshooting

### Token Exchange Fails

**Symptom**: `access_context.has_errors()` returns `True` (Python) / `accessContext.hasErrors()` returns `true` (TypeScript) / `ac.HasErrors()` returns `true` (Go)

- 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

**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

**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
