Skip to content

MCP 2b-3: GitHub App credential backend#31

Merged
chaodu-agent merged 1 commit into
mainfrom
mcp-app-backend
Jul 13, 2026
Merged

MCP 2b-3: GitHub App credential backend#31
chaodu-agent merged 1 commit into
mainfrom
mcp-app-backend

Conversation

@chaodu-agent

Copy link
Copy Markdown
Contributor

Summary

Third slice of #17's [2b] gate items: the GitHub App credential backend — the Revision 2 architecture's recommended credential for the MCP path, resolving the PAT-pooling compliance concern. Closes gate items [2b] GitHub App credential backend and the session-credential-lifetime half of [2b] Session-to-agent binding.

Design

  • src/app_token.rs (new): AppTokenProvider — RS256 JWT (60s clock-skew backdate, 9-minute validity) signed with the App private key → POST /app/installations/{id}/access_tokens. Tokens cached and lazily refreshed 5 minutes before expiry. Installation id set explicitly or discovered from owner (org endpoint, then user) on first mint.
  • [mcp.github_app] config: app_id, private_key (secret-ref resolved; \n-escapes normalized for env/JSON sources), installation_id/owner. When present, the MCP path injects installation tokens; the PAT pool is untouched for REST/GraphQL and remains the MCP fallback when the section is absent.
  • Sessions cannot outlive their credential: pins now record the exact credential (PinnedCred::Pat by identity / ::App by token value + expiry). An expired App-token pin terminates the session (404 → client re-initializes per MCP spec). A provider refresh does not disturb in-flight sessions — the pinned token remains valid upstream until its own expiry, so sessions drain naturally across rotations (conservative choice given MCP proxy Phase 0: compliance & compatibility spike (hosted endpoint contract, App tokens) #22's untested token-switch-mid-session behavior).
  • Audit label for App-served traffic: [via github-app] ([agent=… via github-app] in agent mode).
  • New dependency: jsonwebtoken 9.3 (pinned minor; RS256). Test PEM in testdata/ is a throwaway generated for JWT-shape tests — not a credential.

Tested

  • 67 tests (8 new): JWT shape + bad-PEM rejection + installation/owner requirement; token caching + margin-triggered refresh against a mock GitHub mint API; App-mode integration through the mock MCP upstream — mints, injects Bearer ghs_mock_token, pins by value, reuses the pinned token on follow-ups, and terminates expired-credential sessions (404, pin removed, upstream never called). Clippy -D warnings clean.
  • Live in CI (dispatch run): the e2e's new optional App-backend section boots ghpool with [mcp.github_app] (repo's APP_ID/APP_PRIVATE_KEY secrets), and ghpool signed a real JWT, discovered the openabdev installation, minted a real installation token, and served issue_read via the hosted MCP server18/18 checks (13 PAT-mode + 5 App-mode). Section skips cleanly when App credentials are absent (verified locally).

Remaining 2b

2b-4 durable fail-closed audit → 2b-5 enable writes (+ pin required App permission set docs).

Third slice of #17's [2b] items:

- src/app_token.rs: AppTokenProvider — RS256 JWT (60s backdate, 9min
  validity) signed with the App private key, exchanged for an installation
  token; cached and lazily refreshed 5 min before expiry. Installation id
  explicit or discovered from owner (org, then user) on first mint.
- [mcp.github_app] config: app_id + private_key (secret-ref resolved,
  \n-escape normalized) + installation_id/owner. When present, the MCP
  path injects installation tokens; the PAT pool remains for REST/GraphQL
  and as the MCP fallback.
- Session pins now carry the exact credential (PinnedCred::Pat by identity
  / ::App by token value + expiry). Sessions cannot outlive their
  credential: an expired App-token pin terminates the session (404 →
  client re-initializes) — completing the session-credential lifetime half
  of the [2b] binding gate. Provider refresh does not disturb in-flight
  sessions (pinned token stays valid upstream until its own expiry).
- e2e: optional App-backend section (runs when APP_ID/APP_PRIVATE_KEY are
  present — CI passes the repo secrets): boots a second ghpool in App
  mode, asserts mint + pin-to-github-app + issue_read.
- New dep: jsonwebtoken 9.3 (RS256).

67 tests (8 new: JWT shape/PEM validation/caching+refresh via mock GitHub
API; App-mode pinning, pinned-token reuse, expiry termination via mock MCP
upstream).
@chaodu-agent chaodu-agent merged commit 27d9665 into main Jul 13, 2026
2 checks passed
@chaodu-agent chaodu-agent deleted the mcp-app-backend branch July 13, 2026 04:40
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