Sub of #12377 (foundation). Owner: @neo-opus-ada.
Goal
Add a GitLab Personal Access Token Bearer auth mode to ai/mcp/server/shared/services/AuthService.mjs. A user/agent sends Authorization: Bearer <GitLab-PAT>; the server validates the PAT against GitLab's REST API (GET <gitlabBase>/api/v4/user, which a read_user-scoped PAT can hit) and uses the returned username as the MCP identity (userId). No browser OAuth, no cookie.
Why (V-B-A)
AuthService today does OIDC introspection + requireBearerAuth + enforces aud == MCP-server URL (rejects a raw PAT, which has no such audience) + advertises OAuth PRM via mcpAuthMetadataRouter (which triggers MCP Inspector v0.21+'s OAuth-DCR → fails, since GitLab has no DCR). This mode bypasses both: validate via the GitLab API directly, advertise nothing.
Contract Ledger
| Target Surface |
Source of Authority |
Behavior |
Fallback / Edge |
Evidence |
GitLab-PAT Bearer mode (AuthService) |
this sub |
Bearer <PAT> → GET /api/v4/user → userId = username; no aud check |
invalid/expired PAT → 401; missing header → naked 401, no PRM/WWW-Authenticate |
unit test on the verifier + a live /user probe |
config gate (e.g. NEO_AUTH_GITLAB_PAT + GitLab API base URL) |
this sub |
enables the mode; GitLab base configurable |
unset → existing OIDC + trustProxyIdentity paths unchanged |
config-shape test |
Acceptance Criteria
- With the mode enabled,
Authorization: Bearer <valid GitLab PAT> authenticates; identity = the GitLab username from /api/v4/user.
- Invalid/expired PAT →
401.
- No
aud requirement in this mode (PATs aren't audience-scoped); the OIDC aud enforcement is not applied.
- Unauthenticated request → naked
401 with no PRM advertisement / WWW-Authenticate — so MCP Inspector v0.21+ does not trigger OAuth-DCR.
- Config-gated (new mode flag + configurable GitLab API base); existing OIDC-introspection and
trustProxyIdentity modes are unaffected.
- Unit test covers the PAT verifier (success / failure / identity extraction).
- Docs note the required PAT scope (
read_user).
This sub's auth-mode contract (the verifier + config shape) must be visible before sub #2 (Inspector compat) starts — foundation-first per the epic DAG.
Sub of #12377 (foundation). Owner: @neo-opus-ada.
Goal
Add a GitLab Personal Access Token Bearer auth mode to
ai/mcp/server/shared/services/AuthService.mjs. A user/agent sendsAuthorization: Bearer <GitLab-PAT>; the server validates the PAT against GitLab's REST API (GET <gitlabBase>/api/v4/user, which aread_user-scoped PAT can hit) and uses the returnedusernameas the MCP identity (userId). No browser OAuth, no cookie.Why (V-B-A)
AuthServicetoday does OIDC introspection +requireBearerAuth+ enforcesaud== MCP-server URL (rejects a raw PAT, which has no such audience) + advertises OAuth PRM viamcpAuthMetadataRouter(which triggers MCP Inspector v0.21+'s OAuth-DCR → fails, since GitLab has no DCR). This mode bypasses both: validate via the GitLab API directly, advertise nothing.Contract Ledger
AuthService)Bearer <PAT>→GET /api/v4/user→userId = username; noaudcheck401; missing header → naked 401, no PRM/WWW-Authenticate/userprobeNEO_AUTH_GITLAB_PAT+ GitLab API base URL)trustProxyIdentitypaths unchangedAcceptance Criteria
Authorization: Bearer <valid GitLab PAT>authenticates; identity = the GitLab username from/api/v4/user.401.audrequirement in this mode (PATs aren't audience-scoped); the OIDCaudenforcement is not applied.401with no PRM advertisement /WWW-Authenticate— so MCP Inspector v0.21+ does not trigger OAuth-DCR.trustProxyIdentitymodes are unaffected.read_user).This sub's auth-mode contract (the verifier + config shape) must be visible before sub #2 (Inspector compat) starts — foundation-first per the epic DAG.