Skip to content

Quickstart

Build an MCP tool that calls GitHub as the signed-in User, call it from Claude Code or Cursor, and trace the call in the Audit Log. About 20 minutes.

In this quickstart you build a small MCP server, My GitHub Tools, with one tool, list_my_repos, that calls the GitHub API as whoever is signed in. Keycard gives your server an identity of its own and hands it the signed-in User’s GitHub token on each call, so your code stores no User tokens and never sees a password.

  1. Your coding agent Calls list_my_repos on your server with a token Keycard issued to it for you (step 5).
  2. My GitHub Tools Sends that token to Keycard with its own credentials and asks for your GitHub token (steps 2 to 4).
  3. Keycard Checks that the server may act for you, returns your GitHub token, and logs the exchange (step 7).
  4. GitHub API Answers as you, because the token is yours (step 1).

By the end you’ll ask Claude Code or Cursor to list your repositories, then find the call in the Keycard Audit Log with both you and your server named on it. Plan on about 20 minutes. Most of it is setup in Keycard Console and GitHub; the server itself is about 45 lines of Python. Already have an MCP server or API to protect? Go to Protect MCP servers and APIs instead.

  • A Keycard account with the Admin Role in your organization, so you can add Resources and Applications. Sign up at console.keycard.ai. Keycard is in Early Access; you can request access here. See Roles & Permissions.
  • A GitHub account.
  • Python 3.10 or later. Check with ; stock macOS ships 3.9, so install a newer Python first and use it in step 4.
  • Claude Code or Cursor.

This page builds the server in Python with FastMCP. Protect MCP servers and APIs shows the same auth layer in TypeScript, Go, and Ruby.

  1. Connect GitHub to Keycard

    In Keycard Console, open Resources → Add Resource → Explore Resources, search for GitHub, and choose GitHub API. Don’t pick GitHub MCP: that’s GitHub’s hosted MCP server, and your tool calls the API directly.

    The setup screen offers two ways to connect GitHub: a personal access token or an OAuth App. Use an OAuth App, so each User who signs in connects their own GitHub account and your tool calls GitHub as them. With a token, every call acts as the GitHub user who created the token, no matter who is signed in.

    The screen shows the Keycard callback URL for your organization (it’s also under Settings → Connection → Redirect URL). Use it to create a GitHub OAuth App:

    1. In GitHub Developer Settings, click New OAuth App (Register a new application if you have no OAuth Apps yet) and fill in:

      • Application name: something your Users will recognize, such as My GitHub Tools. GitHub shows it when they authorize the app.
      • Homepage URL:
      • Redirect URI: the Keycard callback URL

      Leave the rest as is, or feel free to add a helpful description, and click Register application.

    2. On the new app’s page, copy the Client ID, then generate a client secret and copy it too. GitHub shows the secret only once.

    3. Back in Console, paste them into the matching fields:

      • Client ID: the GitHub app’s Client ID
      • Client Secret: the GitHub app’s client secret

      Leave Personal Access Token empty and click Protect GitHub API.

    Keycard creates a GitHub Provider and a GitHub API Resource with the identifier https://api.github.com, then opens the new Resource. Your tool asks for access by that identifier.

  2. Give your server an identity

    Your server is an Application in Keycard. When it asks for a User’s GitHub token, it proves who it is with its own credentials.

    1. Open Applications → Add Application and fill in:

      • Name:
      • Identifier: . Keycard puts it in the tokens it issues for this Application. Any unique value works.

      Leave the rest as is and click Create application.

    2. Console opens the Application on its Dependencies tab. Click Add dependency, select GitHub API, and click Connect. This tells Keycard your server needs GitHub, so people who sign in to it are asked to connect their GitHub account.

    3. Open the Application Credentials tab, click Add credential, and choose Client ID & Secret. Save both values from the dialog before you click Done, because Console won’t show the secret again:

      • Client ID: your server’s in step 4
      • Client Secret: your server’s in step 4

      These are your server’s Keycard credentials, not the GitHub OAuth App’s from step 1.

  3. Register your server’s address

    Your server’s MCP endpoint is a Resource: the thing your coding agent asks permission to call. Open Resources → Add Resource → Add Manually, set these fields, leave the rest as is, and click Protect My GitHub Tools:

    Keycard Console
    Location: Resources → Add Resource → Add Manually

    What do you want to protect?

    Name *
    Identifier *
    MCP Server
    This resource implements the MCP protocol
    Off
    Credential Provider Optional
    Zone ProviderKeycard STS
    Provided by Application Optional
    My GitHub Tools
    The Identifier must equal mcp_server_url in step 4, http://localhost:8000/mcp. Leave MCP Server off: turning it on adds a Use Gateway option that can hide Provided by Application. Zone Provider issues the tokens your agent presents to your server; Keycard ID signs you in, and GitHub issues GitHub API tokens.

    Set Provided by Application even though it’s marked Optional: without it, your server can’t exchange tokens for this Resource. To check it later, open Applications → My GitHub Tools → Provides, which should list this Resource.

  4. Write and start the server

    To hand this step to your coding agent, send it this prompt followed by the server.py code below, in one message:

    Prompt for your coding agent
    Set up the Keycard Quickstart server for me:
    1. Create a folder named my-github-tools with a Python 3.10+ virtual environment in .venv.
    2. In that venv, run exactly:
    pip install keycardai-fastmcp "fastmcp<4" httpx
    Keep the fastmcp<4 pin, even if FastMCP suggests an upgrade.
    3. Save the server.py code that follows this prompt in that folder, unchanged. If it's missing, ask me for it; don't write your own.
    4. Then show me the line that asks Keycard for my GitHub token on every call, and say in one sentence what it does.
    Don't start the server, and don't ask me for credentials. I'll put them in a .env file and run it myself.

    Or set it up yourself. Create a project and install the Keycard FastMCP package:

    Terminal window
    mkdir my-github-tools && cd my-github-tools
    python3 -m venv .venv && source .venv/bin/activate
    pip install keycardai-fastmcp "fastmcp<4" httpx

    The fastmcp<4 pin keeps FastMCP on 3.x, which keycardai-fastmcp is tested with; skip the 4.x upgrade FastMCP’s startup banner may offer.

    Save this as server.py:

    import os
    import httpx
    from fastmcp import FastMCP
    from keycardai.fastmcp import AccessContext, AuthProvider, ClientSecret
    GITHUB_API = "https://api.github.com"
    auth_provider = AuthProvider(
    zone_url=os.environ["KEYCARD_ISSUER"],
    mcp_server_name="My GitHub Tools",
    mcp_server_url="http://localhost:8000/mcp", # must equal the My GitHub Tools Resource Identifier
    application_credential=ClientSecret(
    (os.environ["KEYCARD_CLIENT_ID"], os.environ["KEYCARD_CLIENT_SECRET"])
    ),
    )
    mcp = FastMCP("My GitHub Tools", auth=auth_provider.get_remote_auth_provider())
    @mcp.tool()
    async def list_my_repos(
    access: AccessContext = auth_provider.grant(GITHUB_API),
    ) -> dict:
    """List up to 10 GitHub repositories the signed-in user owns, most recently updated first."""
    if access.has_errors():
    return {"error": "Keycard could not get a GitHub token", "details": access.get_errors()}
    token = access.access(GITHUB_API).access_token
    headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}
    async with httpx.AsyncClient(base_url=GITHUB_API, headers=headers) as client:
    user = await client.get("/user")
    user.raise_for_status()
    repos = await client.get(
    "/user/repos", params={"affiliation": "owner", "sort": "updated", "per_page": 10}
    )
    repos.raise_for_status()
    return {
    "github_user": user.json()["login"],
    "repositories": [repo["html_url"] for repo in repos.json()],
    }
    if __name__ == "__main__":
    mcp.run(transport="streamable-http")

    The Keycard parts:

    • AuthProvider(...) points the server at your organization’s Issuer URL (zone_url) and carries the Application’s credentials.
    • get_remote_auth_provider() makes FastMCP reject any request that lacks a valid token from your organization, and tells MCP clients where to sign in.
    • auth_provider.grant(GITHUB_API) exchanges the caller’s token for their GitHub token before the tool body runs. The SDK fills in access; it doesn’t appear in the tool’s inputs. If the exchange fails, the tool returns Keycard’s error instead of calling GitHub.

    Back in Console, copy the Issuer URL from Settings → Connection. Save it and the Application credentials from step 2 in my-github-tools/.env:

    .env
    KEYCARD_ISSUER=<keycard-issuer-url>
    KEYCARD_CLIENT_ID=<client-id>
    KEYCARD_CLIENT_SECRET=<client-secret>

    Then, from the my-github-tools directory, start the server:

    Terminal window
    source .venv/bin/activate
    set -a; source .env; set +a # export the values in .env
    python3 server.py

    FastMCP prints http://127.0.0.1:8000/mcp, the same server as localhost. It contacts Keycard at the Issuer URL as it starts, so a wrong URL stops it immediately. Leave it running.

  5. Connect your coding agent

    Go back to the Resource in Console (Resources → My GitHub Tools) and click Add to Coding Agent.

    Choose Claude Code, pick a scope, and run the command Console shows. User makes the server available in every folder; Project and Local, only in the folder where you run the command. With User it’s:

    Terminal window
    claude mcp add --transport http --scope user My-GitHub-Tools http://localhost:8000/mcp

    Start claude, type /mcp, select My-GitHub-Tools, and authenticate, or sign in first with .

    A browser window opens on Keycard. If it asks you to sign in, use the account you use for Keycard Console. Keycard’s consent screen shows the chain: your agent, under the name it registered with (such as Claude Code (My-GitHub-Tools)), is requesting access, and Will access lists My GitHub Tools, then GitHub API via My GitHub Tools. Click Allow access. GitHub then asks you to authorize your OAuth App; approve it.

  6. Call the tool

    Ask your agent:

    List my GitHub repositories.

    The agent calls list_my_repos, which returns your GitHub username and links to up to 10 repositories you own, most recently updated first. The username shows that GitHub answered as you. If a teammate signed in, the same code would return their username, not yours. The GitHub API catalog entry requests no GitHub scopes, so the token can only see public repositories.

  7. Follow the call in the Audit Log

    In Console, open Audit Log (under Activity in the left nav). Look for the credentials:issue events: one for your agent, from when you signed in, and one for GitHub API each time the tool runs. Call the tool again and a second GitHub API event appears. Expand each to see its Actor Details. The one whose Entity is My GitHub Tools was issued to your coding agent (a User_agent, such as Claude Code) for you. The one whose Entity is GitHub API was issued to the My GitHub Tools Application, also for you (for example, troy.barnes@greendale.edu).

    Each event’s Raw Data also carries a policy_set_id: the Policy Set Keycard checked before allowing that hop.

Six Keycard objects did the work:

  • Organization: where people sign in and where tokens come from, at the Issuer URL your server trusts. Console and the SDKs call this your organization’s Zone, which is why you saw Zone Provider and zone_url.
  • User: a person signed in to your organization. Here, that was you.
  • Resource: anything that gets called. You protected two: your server at http://localhost:8000/mcp and the GitHub API at https://api.github.com.
  • Provider: a service Keycard trusts to sign people in or to issue credentials. Keycard ID signed you in. Each Resource names the Provider that issues its credentials: Zone Provider, your organization’s own issuer, for your server; GitHub for the GitHub API.
  • Application: software with its own credentials. My GitHub Tools provides your server’s Resource and depends on the GitHub API. Because of the dependency, Keycard asked you to connect GitHub when you signed in. Because the Application provides the Resource, Keycard lets it exchange tokens that were issued for your server.
  • User agent: your coding agent, acting for you. It registered itself with your organization when you authenticated it in step 5, under the name on the consent screen. The Audit Log names it, but it isn’t listed under your Applications.

When you asked for your repositories, your agent called the server with a token Keycard had issued for http://localhost:8000/mcp on your behalf. The tool sent that token to Keycard along with its own Client ID and Secret, and got your GitHub token back for that call. Every tool you build follows the same shape: the agent proves who the User is, your server proves who it is, and Keycard decides what the pair can reach and logs each hop.

GitHub shows a 404 page instead of asking you to authorize

The Client ID saved in Keycard doesn’t match your GitHub OAuth App. Correct it under Providers → GitHub → Configuration, click Save changes, and authenticate again.

Client is not allowed to exchange token for this resource

Your server’s Resource has no Provided by Application. Open Resources → My GitHub Tools, click the settings icon (Resource Settings), set Provided by Application to My GitHub Tools, and click Save Changes. Then call the tool again.

  • Revoke access: Keycard issues the credentials, so revoking your GitHub API grant under People makes the tool’s next call fail.
  • Access Policies: Keycard enforces your policies. See why the default Policy Set allowed both hops, then write your own.
  • Audit Log & Sessions: Keycard logs every session. Follow this run in Sessions, and see how a refused call is recorded.
  • Authenticate deployed workloads: when you deploy the server, replace its client secret with the platform’s own identity token.
  • Connect coding agents: put Linear behind a Keycard gateway and watch a policy refuse a tool call.