feat: add user_oidc auth type for MCP servers (#24793)

Adds a 5th MCP server authentication mode, `user_oidc` ("User OIDC
Identity"), that forwards the calling user's OIDC access token from
`user_links.oauth_access_token` to the upstream MCP server as
`Authorization: Bearer <token>`.

The token is read from `user_links` and refreshed transparently via
`oauth2.TokenSource` before each MCP request. No new per-MCP-server
secret storage and no per-user connect/disconnect step.

**Limitation**: only users who logged in via OIDC have a forwardable
token. Users authenticated via password or GitHub will see requests sent
without an `Authorization` header, and the upstream MCP server is
expected to respond with 401. A pluggable token source (e.g. CLI-minted
E2E tokens) is left as future work.

<details>
<summary>Implementation notes</summary>

- Schema: new
`coderd/database/migrations/000481_mcp_user_oidc_auth.{up,down}.sql`
relaxes the `mcp_server_configs.auth_type` CHECK constraint to include
`user_oidc`. Down migration deletes affected rows before restoring the
old constraint.
- SDK validation: `codersdk/mcp.go` extends `oneof` for
`CreateMCPServerConfigRequest` and `UpdateMCPServerConfigRequest`.
- Handler: `coderd/mcp.go` adds `case "user_oidc":` to the
field-clearing switch on update. The existing list and detail handlers
already report `auth_connected = true` for any non-`oauth2` auth type.
- Header construction: `coderd/x/chatd/mcpclient/mcpclient.go`
introduces a `UserOIDCTokenSource` interface and adds the `user_oidc`
case to `buildAuthHeaders`. `ConnectAll` / `connectOne` /
`buildAuthHeaders` gain `userID uuid.UUID, oidcSrc UserOIDCTokenSource`
parameters.
- Wiring: `coderd/x/chatd/chatd.go` adds `OIDCTokenSource` to `Config` /
`Server` and passes `chat.OwnerID` plus the source through `ConnectAll`.
`coderd/coderd.go` constructs the source next to the `chatd.New` call
when `options.OIDCConfig` is non-nil.
- Token source: `oidcMCPTokenSource` lives in `coderd/mcp.go`. It reads
the user's OIDC link, refreshes via `oauth2.TokenSource`, and writes the
refreshed token back to `user_links`. Logic is duplicated from
`provisionerdserver.ObtainOIDCAccessToken` to avoid an MCP ->
provisionerdserver dependency. The two copies must be kept in sync; a
comment on `oidcMCPTokenSource` records this.
- Frontend: `MCPServerAdminPanel.tsx` adds the new dropdown option, an
explanatory helper block (no admin-configurable fields), and a Storybook
story (`CreateServerUserOIDC`).
- Tests:
- `mcpclient_test.go`: `TestConnectAll_UserOIDCAuth`,
`TestConnectAll_UserOIDCAuth_NoLink`,
`TestConnectAll_UserOIDCAuth_NilSource`. All existing tests updated for
the new signature.
- `mcp_test.go`: extends `TestMCPServerConfigsAuthConnected` to assert
`auth_connected=true` for `user_oidc`; adds
`TestMCPServerConfigsUserOIDCClearsFields` and
`TestMCPServerConfigsUserOIDCDirect`.
- Docs: `docs/ai-coder/agents/platform-controls/mcp-servers.md`
describes the new mode and its OIDC-only limitation.

</details>

This PR was created by Coder Agents.

---------

Co-authored-by: Coder Agents <agents@coder.com>
This commit is contained in:
Kyle Carberry
2026-05-03 11:31:48 -04:00
committed by GitHub
parent 6b9637d85a
commit d889ba1842
15 changed files with 802 additions and 36 deletions
@@ -48,7 +48,7 @@ This is an admin-only feature accessible at **Agents** > **Settings** >
## Authentication
Each MCP server uses one of four authentication modes. When you change the
Each MCP server uses one of five authentication modes. When you change the
auth type, fields from the previous type are automatically cleared.
Secrets are never returned in API responses — boolean flags indicate whether
@@ -104,6 +104,21 @@ A static key sent as a header on every request.
Arbitrary key-value header pairs sent on every request. At least one header
is required when this mode is selected.
### User OIDC Identity
Forwards the calling user's OIDC access token (stored in
`user_links.oauth_access_token`) to the MCP server as an
`Authorization: Bearer <token>` header. The token is refreshed
transparently before each request if it has expired or is close to
expiring.
No admin-configurable fields. No per-user connect step.
**Limitation**: this auth mode only works for users who authenticated to
Coder via OIDC. Users who logged in with password or GitHub will see
requests sent without an authorization header, and the upstream MCP
server is expected to respond with 401.
## Tool governance
Control which tools from a server are available in chat: