Context
Early standalone graduation from Discussion #15595 (Local Runtime Parity), declared in its Graduation Criteria as a do-now decoupler: independently valuable, low-blast, shrinks the parity epic's diff. The production cloud deployment authenticates MCP clients via gitlab-pat; GitHub-based deployments (and any future local-docker parity stack per D#15595) have no equivalent — the only bearer modes today are oidc (full OAuth 2.1 infrastructure), gitlab-pat, and local-bearer (possession-only, no identity). A github-pat mode is the missing mirror: validate a client's GitHub Personal Access Token against GitHub's /user endpoint, no OAuth app required.
Live latest-open sweep: checked latest 20 open issues at 2026-07-20T11:45Z — no equivalent. A2A in-flight claim sweep (last 30, all read-states): no competing claim. KB semantic sweep (github-pat auth mode, type=ticket): no existing ticket; sibling precedent gitlab-pat confirmed as #12377/#12378 (closed, shipped). Exact sweep: #11404 (GitLab workflow parity — unrelated), #15347 (credential redaction — unrelated).
The Problem
Operators deploying the Agent OS MCP servers (KB + MC) against a GitHub-centered identity plane must either stand up a full OIDC provider or fall back to possession-only local-bearer (which carries no identity — every caller shares one anonymous principal, breaking per-user memory scoping and AgentIdentity binding). The gitlab-pat mode proved the lightweight pattern: client mints a PAT, server validates it against the provider's user endpoint, the resolved username becomes the caller identity, and #14388's auto-provision path binds an AgentIdentity. GitHub is the more common identity plane for this repository's ecosystem; the asymmetry is accidental, not designed.
The Architectural Reality
ai/mcp/server/shared/services/AuthService.mjs:74 — the gitlab-pat branch in the auth-mode dispatch.
ai/mcp/server/shared/services/AuthService.mjs:354-404 — the gitlab-pat verifier: GET {gitlabApiBaseUrl}/api/v4/user with the incoming bearer; 200 resolves {userId: username, source: 'gitlab-pat', authProvider: 'gitlab', authSource: 'gitlab-pat'}; non-200 → InvalidTokenError; server-side validation cache.
ai/configBase.mjs:177-229 — the auth config section: mode selector leaf ('oidc' | 'gitlab-pat' | 'local-bearer'), per-mode readiness metadata, gitlabApiBaseUrl leaf (NEO_AUTH_GITLAB_API_BASE_URL), optional OAuth-app gate, optional username allowlist, and autoProvisionIdentitySources (default ['gitlab-pat'], NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES).
- The PRM/discovery contrast: gitlab-pat mode advertises a "naked 401" (no OAuth Protected-Resource Metadata) so PAT clients are not pushed into OAuth DCR — the github-pat mode must replicate this exact 401 shape.
- Identity provisioning:
#14388 (closed) — auth success without a bound AgentIdentity left graph-gated tools failing; the auto-provision sources list is the seam.
The Fix
- Config leaves (
ai/configBase.mjs auth section, mirroring the gitlab-pat leaves):
- Extend the
auth.mode selector + readiness metadata with 'github-pat'.
githubApiBaseUrl leaf, default https://api.github.com, env NEO_AUTH_GITHUB_API_BASE_URL (configurable so GHES / self-managed GitHub works — the same self-managed rationale as gitlabApiBaseUrl).
- Optional username allowlist leaf (env-driven), empty = any resolved GitHub user.
- Verifier (
AuthService.mjs, sibling method to the gitlab-pat verifier): GET {githubApiBaseUrl}/user with Authorization: Bearer <token>, Accept: application/vnd.github+json, a User-Agent header (GitHub rejects UA-less requests), and the X-GitHub-Api-Version header. 200 → resolve {userId: login, source: 'github-pat', authProvider: 'github', authSource: 'github-pat'} in the identical AuthInfo shape; 401/403 → InvalidTokenError; ride the same server-side validation cache as gitlab-pat. Naked-401 PRM shape identical to gitlab-pat.
- Auto-provision: make
'github-pat' an accepted value of autoProvisionIdentitySources (default list unchanged — deployers opt in explicitly).
- Tests: unit specs mirroring the gitlab-pat verifier specs (200→identity, 401→InvalidTokenError, allowlist accept/reject, GHES base-URL override, cache behavior); an integration
AuthRejection-class case if the pattern extends cheaply.
- Docs: update the auth-mode references in
learn/agentos/cloud-deployment/Security.md + Configuration.md (mode matrix + one connect recipe paragraph).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
AiConfig.auth.mode |
ai/configBase.mjs:196 |
Accept 'github-pat' |
Missing config leaves → readiness error at boot, never silent |
Configuration.md |
unit: config readiness spec |
AiConfig.auth.githubApiBaseUrl |
new leaf, NEO_AUTH_GITHUB_API_BASE_URL |
Default https://api.github.com; GHES override |
Trailing-slash normalization like gitlab |
Configuration.md |
unit: base-url resolution spec |
| Bearer validation |
AuthService.mjs new verifier |
GET /user → GitHub login as userId |
401/403/network → InvalidTokenError |
Security.md |
unit: verifier specs |
| 401 response shape |
gitlab-pat PRM behavior |
Naked 401, no OAuth DCR advertisement |
n/a |
Security.md |
unit: 401-shape spec |
autoProvisionIdentitySources |
ai/configBase.mjs:229 |
'github-pat' accepted as a source |
Default list unchanged |
Security.md |
unit: provisioning-source spec |
Decision Record impact
aligned-with ADR 0014 (cloud deployment topology) — additive auth mode behind config; no ADR conflict. Discussion-origin classification: Not needed (low-blast early graduation per D#15595 Graduation Criteria).
Discussion Criteria Mapping
- D#15595 Graduation Criteria: "Early standalone graduations: … the
github-pat auth mode may graduate as [GRADUATED_TO_TICKET] items ahead of the main matrix" → this ticket.
- D#15595 divergence #5 (Public surface: "A
github-pat auth mode (mirror of gitlab-pat) does not exist yet and is needed regardless") → AC 1–5 below.
Acceptance Criteria
Out of Scope
- OAuth 2.1 / OIDC changes (the
oidc mode is untouched)
- GitHub App installation tokens or JWT app auth (PAT-class tokens only)
- Fine-grained PAT resource scoping enforcement beyond what
/user returns (the server validates identity, not repo permissions)
- Local-docker parity wiring (D#15595's epic owns that; this ticket only unblocks it)
Avoided Traps / Gold Standards Rejected
- Full OAuth app + web flow — the entire point of the PAT modes is zero OAuth infrastructure; rejected as deployment-weight creep.
- Hardcoding
api.github.com — rejected: GHES/self-managed parity with gitlabApiBaseUrl costs one config leaf.
- Token introspection via GraphQL — rejected: REST
/user is the minimal, cache-friendly identity call; GraphQL adds a schema dependency for zero gain.
Related
- Discussion: #15595 (source; this is an early standalone graduation)
- Sibling precedent:
#12377 / #12378 (gitlab-pat mode, shipped)
- Identity provisioning:
#14388 (auto-provision AgentIdentity for PAT users)
- Forward consumer: D#15595's parity epic (local-docker stack auth)
Origin Session ID: 8d4ce1c3-0bf2-4bb0-bad9-e49836248afe
Retrieval Hint: "gitlab-pat AuthService verifier github-pat mirror auth mode"
Context
Early standalone graduation from Discussion #15595 (Local Runtime Parity), declared in its Graduation Criteria as a do-now decoupler: independently valuable, low-blast, shrinks the parity epic's diff. The production cloud deployment authenticates MCP clients via
gitlab-pat; GitHub-based deployments (and any future local-docker parity stack per D#15595) have no equivalent — the only bearer modes today areoidc(full OAuth 2.1 infrastructure),gitlab-pat, andlocal-bearer(possession-only, no identity). Agithub-patmode is the missing mirror: validate a client's GitHub Personal Access Token against GitHub's/userendpoint, no OAuth app required.Live latest-open sweep: checked latest 20 open issues at 2026-07-20T11:45Z — no equivalent. A2A in-flight claim sweep (last 30, all read-states): no competing claim. KB semantic sweep (
github-pat auth mode, type=ticket): no existing ticket; sibling precedentgitlab-patconfirmed as#12377/#12378(closed, shipped). Exact sweep:#11404(GitLab workflow parity — unrelated),#15347(credential redaction — unrelated).The Problem
Operators deploying the Agent OS MCP servers (KB + MC) against a GitHub-centered identity plane must either stand up a full OIDC provider or fall back to possession-only
local-bearer(which carries no identity — every caller shares one anonymous principal, breaking per-user memory scoping and AgentIdentity binding). Thegitlab-patmode proved the lightweight pattern: client mints a PAT, server validates it against the provider's user endpoint, the resolved username becomes the caller identity, and#14388's auto-provision path binds an AgentIdentity. GitHub is the more common identity plane for this repository's ecosystem; the asymmetry is accidental, not designed.The Architectural Reality
ai/mcp/server/shared/services/AuthService.mjs:74— thegitlab-patbranch in the auth-mode dispatch.ai/mcp/server/shared/services/AuthService.mjs:354-404— the gitlab-pat verifier:GET {gitlabApiBaseUrl}/api/v4/userwith the incoming bearer; 200 resolves{userId: username, source: 'gitlab-pat', authProvider: 'gitlab', authSource: 'gitlab-pat'}; non-200 →InvalidTokenError; server-side validation cache.ai/configBase.mjs:177-229— the auth config section:modeselector leaf ('oidc' | 'gitlab-pat' | 'local-bearer'), per-mode readiness metadata,gitlabApiBaseUrlleaf (NEO_AUTH_GITLAB_API_BASE_URL), optional OAuth-app gate, optional username allowlist, andautoProvisionIdentitySources(default['gitlab-pat'],NEO_AUTH_AUTO_PROVISION_IDENTITY_SOURCES).#14388(closed) — auth success without a bound AgentIdentity left graph-gated tools failing; the auto-provision sources list is the seam.The Fix
ai/configBase.mjsauth section, mirroring the gitlab-pat leaves):auth.modeselector + readiness metadata with'github-pat'.githubApiBaseUrlleaf, defaulthttps://api.github.com, envNEO_AUTH_GITHUB_API_BASE_URL(configurable so GHES / self-managed GitHub works — the same self-managed rationale asgitlabApiBaseUrl).AuthService.mjs, sibling method to the gitlab-pat verifier):GET {githubApiBaseUrl}/userwithAuthorization: Bearer <token>,Accept: application/vnd.github+json, aUser-Agentheader (GitHub rejects UA-less requests), and theX-GitHub-Api-Versionheader. 200 → resolve{userId: login, source: 'github-pat', authProvider: 'github', authSource: 'github-pat'}in the identical AuthInfo shape; 401/403 →InvalidTokenError; ride the same server-side validation cache as gitlab-pat. Naked-401 PRM shape identical to gitlab-pat.'github-pat'an accepted value ofautoProvisionIdentitySources(default list unchanged — deployers opt in explicitly).AuthRejection-class case if the pattern extends cheaply.learn/agentos/cloud-deployment/Security.md+Configuration.md(mode matrix + one connect recipe paragraph).Contract Ledger Matrix
AiConfig.auth.modeai/configBase.mjs:196'github-pat'AiConfig.auth.githubApiBaseUrlNEO_AUTH_GITHUB_API_BASE_URLhttps://api.github.com; GHES overrideAuthService.mjsnew verifierGET /user→ GitHub login asuserIdInvalidTokenErrorautoProvisionIdentitySourcesai/configBase.mjs:229'github-pat'accepted as a sourceDecision Record impact
aligned-with ADR 0014(cloud deployment topology) — additive auth mode behind config; no ADR conflict. Discussion-origin classification:Not needed(low-blast early graduation per D#15595 Graduation Criteria).Discussion Criteria Mapping
github-patauth mode may graduate as[GRADUATED_TO_TICKET]items ahead of the main matrix" → this ticket.github-patauth mode (mirror ofgitlab-pat) does not exist yet and is needed regardless") → AC 1–5 below.Acceptance Criteria
NEO_AUTH_MODE=github-patboots kb-server and mc-server with bearer validation againstGET {githubApiBaseUrl}/useruserId= GitHub login,authSource: 'github-pat', in the same AuthInfo shape as gitlab-patNEO_AUTH_GITHUB_API_BASE_URLoverride works (GHES-style base)'github-pat'accepted inautoProvisionIdentitySources; default list unchangedOut of Scope
oidcmode is untouched)/userreturns (the server validates identity, not repo permissions)Avoided Traps / Gold Standards Rejected
api.github.com— rejected: GHES/self-managed parity withgitlabApiBaseUrlcosts one config leaf./useris the minimal, cache-friendly identity call; GraphQL adds a schema dependency for zero gain.Related
#12377/#12378(gitlab-pat mode, shipped)#14388(auto-provision AgentIdentity for PAT users)Origin Session ID: 8d4ce1c3-0bf2-4bb0-bad9-e49836248afe
Retrieval Hint: "gitlab-pat AuthService verifier github-pat mirror auth mode"