Skip to content
API Reference

Third-Party Tools & APIs

Build MCP servers that access GitHub, Google Workspace, and other APIs on behalf of users

Your AI agents need to do more than just answer questions — they need to take action. Read a user’s calendar, list their repos, create issues, search their Drive. But accessing APIs on behalf of a user means managing OAuth flows, token exchange, credential storage, and refresh logic across every provider.

Keycard handles all of that. You write MCP tools that call APIs; Keycard manages the authentication plumbing, exchanges tokens per-user, and gives you an audit trail of every action taken.

This guide walks through building GitHub and Google Workspace MCP servers with delegated access. Full working examples are available in the SDK repos.

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
  • Completed Build an MCP Server guide
  • Python 3.10+, Node.js 18+, or Go 1.21+
  • GitHub account (for GitHub section)
  • Google Cloud account (for Google section)

Build an MCP server with repository-focused tools that let AI agents interact with GitHub on behalf of users.

  1. Add GitHub from Resource Catalog

    In Keycard Console, navigate to Resource Catalog and add GitHub. This automatically creates the GitHub OAuth provider and GitHub API resource.

  2. Set up the GitHub OAuth App callback

    1. Go to Zone Settings in Keycard Console and copy the OAuth2 Redirect URL
    2. In GitHub Developer Settings, create or edit your OAuth App
    3. Set the Authorization callback URL to the Keycard redirect URL
  3. Register your MCP server resource

    Navigate to ResourcesCreate Resource:

    FieldValue
    Resource NameGitHub MCP Server
    Resource Identifierhttp://localhost:8000/mcp
    Credential ProviderZone Provider
  4. Create an Application

    Navigate to ApplicationsCreate Application:

    FieldValue
    Provided ResourceGitHub MCP Server (your MCP server)
    DependencyGitHub API

    After creating the application, generate client credentials (Client ID + Client Secret) and save them.

The server is split into two files: setup and tools. The setup file configures Keycard authentication; the tools file contains your API wrappers.

Install dependencies:

Terminal window
pip install keycardai-mcp-fastmcp fastmcp httpx

Create a .env file:

Terminal window
KEYCARD_ZONE_ID=your-zone-id
MCP_SERVER_URL=http://localhost:8000
KEYCARD_CLIENT_ID=your-client-id
KEYCARD_CLIENT_SECRET=your-client-secret

server.py — Server setup and entry point:

"""GitHub MCP Server with Keycard Delegated Access."""
import os
from fastmcp import FastMCP
from keycardai.mcp.integrations.fastmcp import AuthProvider, ClientSecret
auth_provider = AuthProvider(
zone_id=os.getenv("KEYCARD_ZONE_ID"),
mcp_server_name="GitHub MCP Server",
mcp_base_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000"),
application_credential=ClientSecret((
os.getenv("KEYCARD_CLIENT_ID"),
os.getenv("KEYCARD_CLIENT_SECRET"),
)),
)
auth = auth_provider.get_remote_auth_provider()
mcp = FastMCP("GitHub MCP Server", auth=auth)
from tools import register_tools # noqa: E402
register_tools(mcp, auth_provider)
def main():
mcp.run(transport="streamable-http")
if __name__ == "__main__":
main()

tools.py — Each tool uses the grant decorator and extracts the exchanged token:

"""GitHub tools — thin wrappers around the GitHub REST API."""
import httpx
from fastmcp import Context, FastMCP
from keycardai.mcp.integrations.fastmcp import AccessContext, AuthProvider
GITHUB_API = "https://api.github.com"
async def github_request(access_context: AccessContext, method: str, path: str, **kwargs) -> dict:
"""Make an authenticated request to the GitHub API."""
token = access_context.access(GITHUB_API).access_token
async with httpx.AsyncClient() as client:
response = await client.request(
method,
f"{GITHUB_API}{path}",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
},
**kwargs,
)
response.raise_for_status()
return response.json()
def register_tools(mcp: FastMCP, auth_provider: AuthProvider):
@mcp.tool()
@auth_provider.grant(GITHUB_API)
async def list_repos(ctx: Context, per_page: int = 30, sort: str = "updated") -> dict:
"""List the authenticated user's repositories."""
access_context: AccessContext = ctx.get_state("keycardai")
if access_context.has_errors():
return {"error": "Token exchange failed", "details": access_context.get_errors()}
repos = await github_request(
access_context, "GET", "/user/repos", params={"per_page": per_page, "sort": sort}
)
return {
"count": len(repos),
"repositories": [
{"name": r["name"], "full_name": r["full_name"], "html_url": r["html_url"]}
for r in repos
],
}
# Additional tools follow the same pattern: grant → get_state → check errors → call API
# See full examples: https://github.com/keycardai/python-sdk/tree/main/packages/mcp-fastmcp/examples
  1. Start your server

  2. Connect from Cursor — add the MCP server URL to your Cursor MCP settings and complete the OAuth flow

  3. Try these prompts:

    • “List my GitHub repos”
    • “Show open PRs on owner/repo
    • “Create an issue on owner/repo titled ‘Bug: login broken’”
  4. Verify in Audit Logs

    In Keycard Console, navigate to Audit Logs to see the full flow:

    EventDescription
    users:authenticateUser logged in via Keycard
    users:authorizeUser authorized access to the MCP server
    credentials:issueAccess token issued — shows the identity chain (user + application)

    Each credentials:issue event includes an identity chain showing both the user identity (e.g., alice@acme.com) and the application identity (e.g., GitHub), so you can trace exactly which user triggered which API call through which application.


Build an MCP server with Calendar and Drive tools that let AI agents manage a user’s Google Workspace on their behalf.

  1. Add Google from Resource Catalog

    In Keycard Console, navigate to Resource Catalog and add Google Calendar and Google Drive. This automatically creates the Google OAuth provider and API resources.

  2. Set up the Google OAuth App callback

    1. Go to Zone Settings in Keycard Console and copy the OAuth2 Redirect URL
    2. In Google Cloud ConsoleAPIs & ServicesCredentials, create or edit your OAuth 2.0 Client ID
    3. Add the Keycard redirect URL to Authorized redirect URIs
    4. Ensure the Calendar API and Drive API are enabled under APIs & ServicesEnabled APIs
  3. Register your MCP server resource

    Navigate to ResourcesCreate Resource:

    FieldValue
    Resource NameGoogle Workspace MCP Server
    Resource Identifierhttp://localhost:8000/mcp
    Credential ProviderZone Provider
  4. Create an Application

    Navigate to ApplicationsCreate Application:

    FieldValue
    Provided ResourceGoogle Workspace MCP Server (your MCP server)
    DependenciesGoogle Calendar API, Google Drive API

    After creating the application, generate client credentials and save them.

The implementation follows the same pattern as the GitHub server — the only difference is the API URL you pass to grant() and the API calls you make with the token.

Terminal window
pip install keycardai-mcp-fastmcp fastmcp httpx

The server setup is identical to GitHub — just change the server name. The tools file uses the same grant pattern with https://www.googleapis.com:

GOOGLE_API = "https://www.googleapis.com"
def register_tools(mcp: FastMCP, auth_provider: AuthProvider):
@mcp.tool()
@auth_provider.grant(GOOGLE_API)
async def list_calendar_events(ctx: Context, calendar_id: str = "primary") -> dict:
"""List events from a Google Calendar."""
access_context: AccessContext = ctx.get_state("keycardai")
token = access_context.access(GOOGLE_API).access_token
# Use token to call Google Calendar API
async with httpx.AsyncClient() as client:
response = await client.get(
f"{GOOGLE_API}/calendar/v3/calendars/{calendar_id}/events",
headers={"Authorization": f"Bearer {token}"},
params={"singleEvents": "true", "orderBy": "startTime"},
)
# ... process response
@mcp.tool()
@auth_provider.grant(GOOGLE_API)
async def list_drive_files(ctx: Context, q: str | None = None) -> dict:
"""List or search files in Google Drive."""
access_context: AccessContext = ctx.get_state("keycardai")
token = access_context.access(GOOGLE_API).access_token
# Use token to call Google Drive API

See the full example with all tools, error handling, and Google Workspace file export logic: Python SDK examples

  1. Start your server

  2. Connect from Cursor and complete the OAuth flow — you will be prompted to authorize Google Calendar and Drive access

  3. Try these prompts:

    • “Show my calendar events for this week”
    • “List my recent Drive files”
    • “Get the content of the file named ‘Meeting Notes’”
  4. Verify in Audit Logs

    Check Keycard Console Audit Logs — you should see the same users:authenticate, users:authorize, and credentials:issue events, with the identity chain showing your user and the Google application.


Every third-party integration follows the same steps:

  1. Add from Resource Catalog (or manually create 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:
@auth_provider.grant("https://api.example.com")
async def my_tool(ctx: Context):
access_context: AccessContext = ctx.get_state("keycardai")
token = access_context.access("https://api.example.com").access_token
# Call API with token

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

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

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

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

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