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.
- Your coding agent Calls
list_my_reposon your server with a token Keycard issued to it for you (step 5). - My GitHub Tools Sends that token to Keycard with its own credentials and asks for your GitHub token (steps 2 to 4).
- Keycard Checks that the server may act for you, returns your GitHub token, and logs the exchange (step 7).
- 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.
Prerequisites
Section titled “Prerequisites”- 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.
-
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:
-
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.
- Application name: something your Users will recognize, such as
-
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.
-
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. -
-
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.
-
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.
- Name:
-
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.
-
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.
- Client ID: your server’s
-
-
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 ConsoleLocation: Resources → Add Resource → Add ManuallyWhat 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_urlin 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.
-
Write and start the server
To hand this step to your coding agent, send it this prompt followed by the
server.pycode 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" httpxKeep 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-toolspython3 -m venv .venv && source .venv/bin/activatepip install keycardai-fastmcp "fastmcp<4" httpxThe
fastmcp<4pin keeps FastMCP on 3.x, whichkeycardai-fastmcpis tested with; skip the 4.x upgrade FastMCP’s startup banner may offer.Save this as
server.py:import osimport httpxfrom fastmcp import FastMCPfrom keycardai.fastmcp import AccessContext, AuthProvider, ClientSecretGITHUB_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 Identifierapplication_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_tokenheaders = {"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 inaccess; 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-toolsdirectory, start the server:Terminal window source .venv/bin/activateset -a; source .env; set +a # export the values in .envpython3 server.pyFastMCP prints
http://127.0.0.1:8000/mcp, the same server aslocalhost. It contacts Keycard at the Issuer URL as it starts, so a wrong URL stops it immediately. Leave it running. -
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/mcpStart
claude, type/mcp, select My-GitHub-Tools, and authenticate, or sign in first with.Choose Cursor. Cursor opens and offers to add the server. Accept, then sign in when Cursor shows the server needs authentication.
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.
-
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. -
Follow the call in the Audit Log
In Console, open Audit Log (under Activity in the left nav). Look for the
credentials:issueevents: 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.
What just happened
Section titled “What just happened”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/mcpand the GitHub API athttps://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 Toolsprovides 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.
Troubleshoot
Section titled “Troubleshoot”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.