Skip to content

feat(github): synthetic OAuth refresh flow for MINT_REPO_TOKEN#449

Merged
tlgimenes merged 21 commits into
mainfrom
tlgimenes/pasted-text-content
Jun 10, 2026
Merged

feat(github): synthetic OAuth refresh flow for MINT_REPO_TOKEN#449
tlgimenes merged 21 commits into
mainfrom
tlgimenes/pasted-text-content

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

MINT_REPO_TOKEN now mints the short-lived ghs_ token and issues a durable, revocable synthetic refresh token (ghr_<grantId>.<secret>, an MCP-issued repo grant — not a GitHub refresh token), returning refreshToken/tokenEndpoint/clientId while keeping all legacy output fields. Two new unauthenticated endpoints — POST /repo-grant/token (re-mints a repo-scoped token via a GitHub App JWT; permanent 400 invalid_grant vs transient 503 so the mesh never discards a valid grant) and POST /repo-grant/revoke (RFC 7009) — redeem/revoke grants using only App credentials, with no user-to-server token at refresh time. Grants persist in a new REPO_GRANTS Cloudflare KV namespace storing only sha256(secret) (constant-time verified) with a sliding 90-day TTL, namespaced under /repo-grant/* to avoid the runtime's /oauth/* routes. The change is fully backward compatible and covered by 86 passing tests with a clean typecheck and dry-run build. ⚠️ Before this deploys, create the real namespace (cd github && bunx wrangler kv namespace create REPO_GRANTS) and replace the placeholder id in wrangler.toml, or the post-merge wrangler deploy will fail.

🤖 Generated with Claude Code


Summary by cubic

Adds a synthetic OAuth-style refresh flow to MINT_REPO_TOKEN. We still mint the short-lived GitHub ghs_ token, and now also return a durable, revocable repo grant (ghr_<grantId>.<secret>) with a tokenEndpoint and clientId so refresh needs only the GitHub App, not a user token.

  • New Features

    • MINT_REPO_TOKEN now returns refreshToken, tokenEndpoint, clientId, refreshTokenExpiresAt, expiresIn, tokenType, and repository.id (keeps all legacy fields).
    • New endpoints: POST /repo-grant/token (redeems refresh_token) and POST /repo-grant/revoke (RFC 7009). Unauthenticated, size-limited, and map GitHub 422/404 → 400 invalid_grant and 401/5xx → 503 temporarily_unavailable.
    • Grants persist in REPO_GRANTS KV with SHA-256 secret hashing, constant-time verify, and a sliding 90-day TTL. PUBLIC_BASE_URL sets the absolute tokenEndpoint.
    • mintRepoScopedToken accepts optional repositoryId, cross-checks it, and returns repositoryId.
  • Migration

    • Create and bind the KV namespace: bunx wrangler kv namespace create REPO_GRANTS, then replace the placeholder id in wrangler.toml.
    • Optionally set PUBLIC_BASE_URL to your public origin; defaults to https://github-mcp.decocms.com.

Written for commit 4770d2e. Summary will update on new commits.

Review in cubic

tlgimenes and others added 21 commits June 10, 2026 16:46
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ping

Implements Task 7: redeems a synthetic refresh token for a fresh ghs_ token
using only GitHub App credentials. Maps 422/404 → 400 invalid_grant (grant
deleted) and 401/403/5xx/JWT-config/storage errors → 503 temporarily_unavailable
(grant kept). Includes sliding TTL on success.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements revokeRepoGrant (RFC 7009: always 200, 503 on storage failure),
handleRepoGrantTokenRequest, handleRepoGrantRevokeRequest, and helper
exports repoGrantBaseUrl/repoGrantClientId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the thin mintRepoScopedToken adapter with mintRepoTokenWithGrant,
exposing refreshToken, tokenEndpoint, clientId, refreshTokenExpiresAt,
expiresIn, tokenType, and repository.id in the tool response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire REPO_GRANTS KV binding into handle() via setRepoGrantKV(), route
POST /repo-grant/token and POST /repo-grant/revoke to their HTTP adapters
before the MCP proxy, add a one-time warn if the binding is missing, and
register the REPO_GRANTS kv_namespaces entry in wrangler.toml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tlgimenes tlgimenes merged commit 72474c5 into main Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant