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>
6.1 KiB
MCP Servers
Administrators can register external MCP servers that provide additional tools for agent chat sessions. Configured servers are injected into or offered to users during chat depending on the availability policy.
This is an admin-only feature accessible at Agents > Settings > MCP Servers.
Add an MCP server
- Navigate to Agents > Settings > MCP Servers.
- Click Add.
- Fill in the configuration fields described below.
- Click Save.
Identity
| Field | Required | Description |
|---|---|---|
display_name |
Yes | Human-readable name shown to users in chat. |
slug |
Yes | URL-safe unique identifier, auto-generated from display name. |
description |
No | Brief summary of what the server provides. |
icon_url |
No | Emoji or image URL displayed alongside the server name. |
Connection
| Field | Required | Description |
|---|---|---|
url |
Yes | The MCP server endpoint URL. |
transport |
Yes | Transport protocol. streamable_http or sse. |
Availability
| Field | Required | Description |
|---|---|---|
enabled |
No | Master toggle. Disabled servers are hidden from non-admin users. |
availability |
Yes | Controls how the server appears in chat sessions. See Availability policies. |
model_intent |
No | When enabled, requires the model to describe each tool call's purpose in natural language, shown as a status label in the UI. |
Availability policies
| Policy | Behavior |
|---|---|
force_on |
Always injected into every chat. Users cannot opt out. |
default_on |
Pre-selected in new chats. Users can opt out. |
default_off |
Available in the server list but users must opt in. |
Authentication
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 a value is set.
None
No credentials are sent. Use this for servers that do not require authentication.
OAuth2
Per-user authorization. The administrator configures the OAuth2 provider, and each user independently completes the authorization flow.
Manual configuration — provide all three fields together:
| Field | Description |
|---|---|
oauth2_client_id |
OAuth2 client ID. |
oauth2_auth_url |
Authorization endpoint URL. |
oauth2_token_url |
Token endpoint URL. |
Optional fields:
| Field | Description |
|---|---|
oauth2_client_secret |
OAuth2 client secret. |
oauth2_scopes |
Space-separated list of scopes. |
Auto-discovery — leave oauth2_client_id, oauth2_auth_url, and
oauth2_token_url empty. The server attempts discovery in this order:
- RFC 9728 — Protected Resource Metadata
- RFC 8414 — Authorization Server Metadata
- RFC 7591 — Dynamic Client Registration
Users connect through a popup that redirects through the OAuth2 provider. Tokens are stored per-user and refreshed automatically. Users can disconnect via the UI or API to remove stored tokens.
API key
A static key sent as a header on every request.
| Field | Required | Description |
|---|---|---|
api_key_header |
Yes | Header name (e.g., Authorization). |
api_key_value |
Yes | Secret value sent in the header. |
Custom headers
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:
| Field | Description |
|---|---|
tool_allow_list |
If non-empty, only the listed tool names are exposed. An empty list allows all tools. |
tool_deny_list |
Listed tool names are always blocked, even if they appear in the allow list. |
Permissions
| Action | Required role |
|---|---|
| Create, update, or delete | Admin (deployment config) |
| View enabled servers | Any authenticated user |
| OAuth2 connect and disconnect | Any authenticated user |
Non-admin users only see enabled servers. Sensitive fields such as API keys and client secrets are redacted in API responses.