git-repo-auth-mcp

Credential issuance for AI agents

Stop pasting tokens into chat.
Mint ones that die.

Connect GitHub once. After that, your agent mints its own scoped tokens that expire in one hour — bound to your account, down-scoped per request, attributed to a bot identity in every audit log. Expiry replaces rotation.

https://gitauth.klappy.dev/mcp
Installation Token git-repo-auth-mcp
contents: write
klappy.dev
oddkit-steward[bot]
Time to live60:00
NO ROTATION REQUIRED EXPIRY IS THE ROTATION

Live demonstration at 60× speed — a real token lives one hour.

The problem, on the record

A personal access token is a key you can lose. This is a key that loses itself.

Hand-minted PAT Status quo

  • Lives for months. Pasted into chat transcripts, logs, and clipboards.
  • One scope for everything — the agent fixing a typo holds the same key as the deploy.
  • Rotation is a chore a human remembers to do. Or doesn't.
  • Commits show up as you. The audit log can't tell your hands from the agent's.

Minted on demand This worker

  • Lives one hour, maximum. A leaked transcript holds a corpse, not a key.
  • Down-scoped per request — name the repos, name the permissions.
  • Rotation ceases to be a task. The token rotates by dying.
  • Every commit is attributed to the App's bot identity. Provenance is built in.

How it works

Three hops. Your key never moves.

STEP 1

You connect, once

Add the MCP server to your client and log in with GitHub. The connection binds to your installation of the app — you choose which repos it covers. Then your agent calls github_token at need.

STEP 2

The worker signs

Your Cloudflare Worker signs a 10-minute JWT with your App's private key — which lives in worker secrets and never transits chat, disk, or transcript.

STEP 3

GitHub issues the pass

GitHub exchanges the JWT for a one-hour installation token, scoped exactly as requested. The agent uses it, and the clock does the rest.

Read this before you trust it

The security model, stated plainly.

Most credential tools bury their threat model. This one leads with it — because the people who should deploy this are exactly the people who will ask.

One boundary, not two

The MCP endpoint's bearer token is the entire security boundary. Anyone holding it can mint write tokens for every installed repo. The worker fails closed — no bearer configured, nothing served. Guard MCP_AUTH_TOKEN like the private key itself.

"No admin" ≠ "can't escalate"

The recommended grant excludes Administration — but Workflows write means a token holder can modify CI, and CI runs with the repo's own credentials. That path is accepted, not eliminated: every CI change lands in PRs and audit logs under the bot identity. Don't want the trade? Drop the Workflows permission.

Blast radius, stated

You never hand over a key — connecting means installing a GitHub App on repos you choose. If this bridge were compromised, exposure is minting over installed scopes, within the app's ceiling — never your account or credentials. Your kill switch is unilateral: uninstall the app and minting ends instantly. Want zero shared trust? Self-host the same code.

$ Reversal is two clicks. The riskiest credential in this system is the one it replaces.

Setup

Connect in a minute. Or run your own.

Use it: add https://gitauth.klappy.dev/mcp to your MCP client and follow the GitHub login. Operate it: the script here is the self-host runbook — same code, your app, your worker. Details in the README.

Keep your existing PAT until github_token mints its first real token. Retiring the fallback before validating the replacement is how lockouts happen — the README sequences this for you.

setup.sh
# 1 — Create the GitHub App (Contents RW, PRs RW, Metadata R.
#     Workflows RW optional — see security model. No Administration.)

# 2 — GitHub ships PKCS#1; Workers WebCrypto wants PKCS#8:
openssl pkcs8 -topk8 -inform PEM \
  -in your-app.private-key.pem -nocrypt -out app-pkcs8.pem

# 3 — Enable user OAuth on the app: callback =
#     https://<worker>/callback, generate a client secret.

# 4 — Secrets (never committed, never pasted in chat):
wrangler secret put GH_APP_ID
wrangler secret put GH_APP_PRIVATE_KEY
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET

# 5 — Ship it:
npm install && npm run deploy

# 6 — Point any MCP client at https://<worker>/mcp —
#     OAuth + GitHub login happen in the browser.