feat!: org-scope MCP server configs with RBAC - #27942
Conversation
Docs previewCheck off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 151915c50b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: badeb1520f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c334dc1a17
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0cc80dd1b4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0cc80dd1b4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4d4cc2f1b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1319b0876e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 750a7d4861
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
f6d4a2d to
665eda4
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
665eda4 to
cb91848
Compare
|
@codex review |
…zation only The organization scoping migration no longer copies existing MCP server configs into every live organization. The deployment-wide originals move to the default organization and other organizations start with no MCP servers. Chat references outside the default organization are dropped, and the rolling-upgrade trigger now drops cross-organization config IDs instead of remapping them to same-slug copies.
Simplify the compatibility trigger's aggregate FILTER to a WHERE clause, rename the stale remapTriggerExists test helper, and cover the trigger's cross-organization drop on the update path.
Upgrades run in scheduled maintenance downtime with the database locked during migration, so no pre-scoping replica writes chat MCP selections after the migration commits.
… on read Replace the eager migrateLegacyMCPSelection/useLegacyMCPSelectionMigration effect pair with a self-healing read: when getSavedMCPSelection falls back to the legacy unscoped localStorage key (default organization only), the parsed selection is rewritten under the organization-scoped key and the legacy entry is removed. The healing is skipped while the server list is empty, since saved IDs cannot be validated yet, and never runs for non-default organizations. Callers no longer need migration wiring.
…ployment permission
This reverts commit 0181a29.
c93f896 to
14fd3a2
Compare
Moves
mcp_server_configsfrom deployment scope to organization scope so each organization fully controls the MCP servers its members can use with Coder Agents.Summary
Migration: adds
organization_id(NOT NULL, FK) and keeps existing rows as the default organization's originals with credentials intact. Other organizations start with no MCP servers and configure their own; nothing is copied across organizations. Chats outside the default organization keep any now-cross-organizationmcp_server_idsentries; the runtime already ignores IDs that do not resolve in the chat's organization, so no data rewrite is needed. Slug uniqueness becomes(organization_id, slug).RBAC: new org-scoped
ResourceMCPServerConfigwith regosql converter andGetAuthorizedMCPServerConfigs; org admins get in-org CRUD, org members get read (replaced by ACL evaluation in the follow-up ACL PR in this stack).API: all config routes nest under the organization, matching templates:
POST|GET /api/experimental/organizations/{organization}/mcp-serversandGET|PATCH|DELETE .../mcp-servers/{mcpserverconfig}(plusoauth2/connect), resolved by a read-only param middleware that conceals read-denied and cross-organization access as 404. Two routes stay on the frozen/api/experimental/mcp/servers/{mcpServer}block: the OAuth2 callback (the redirect URI baked into existing AS-side client registrations) andoauth2/disconnect, which must remain reachable by users removed from the organization so they can still revoke their token grant.Chat runtime: selection validation and generation resolve configs strictly by IDs, enabled state, and the chat's organization in SQL; requested duplicates are normalized; invalid or cross-org IDs are rejected with the precise ID list. IDs already persisted on a chat are exempt from message-time rejection so disabling a selected server never blocks sends; generation skips servers that are no longer usable.
Frontend: API layer and admin settings pages target the new endpoints. The admin page manages the default organization's servers; the org picker is tracked separately (CODAGT-714).
Security hardening from review: OAuth user grants are additionally bound to
oauth2_revocation_url(changing it invalidates grants, and a racing OAuth callback gets 409 instead of recreating a grant). Stack-wide SSRF protection for MCP config-directed traffic was split into its own PR at the top of this stack (feat: enforce SSRF protection for MCP config-directed traffic #28242) to keep this diff reviewable; this PR keeps main's existing discovery IP-range guard. OAuth2 auto-discovery now completes before the config row is inserted: a failed discovery persists nothing, and there is no provisional row that concurrent updates could race against.Two follow-up PRs in this stack were split out to keep this diff reviewable: #28064 completes the swagger annotations for the moved routes (main already ships these experimental MCP handlers unannotated), and #28065 carries hardening fixes and regression pins on top of the cutover.
GetForcedMCPServerConfigsByOrganization), so another organization'sforce_onserver never attaches to a chat.Breaking changes (experimental API)
The MCP server config endpoints move from the deployment-scoped
/api/experimental/mcp/serversblock to organization-nested paths:POST|GET /api/experimental/organizations/{organization}/mcp-serversandGET|PATCH|DELETE /api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}(plusoauth2/connect). The old paths are removed, so API consumers must supply an organization. Two routes intentionally stay on the frozen/api/experimental/mcp/servers/{mcpServer}block: the OAuth2 callback (its redirect URI is baked into existing AS-side client registrations) andoauth2/disconnect(must remain reachable by users removed from the organization). These endpoints are under/api/experimental, so no deprecation window is provided.Rolling upgrades
During a rolling deploy, an old replica creating an MCP config can fail the new
NOT NULL organization_idconstraint until it is upgraded (reads are unaffected: old binaries' generated queries select their own column lists). This matches the repo's existing precedent for additive NOT NULL migrations (000562) and affects only the admin config-create path in the upgrade window.Upgrades are expected to run in scheduled maintenance downtime with the database locked during migration, so the migration ships no rolling-upgrade compatibility machinery. The down migration deletes organization-created configs (their chat references are cleaned by the 000510 delete trigger) and restores deployment-wide slug uniqueness.
Part of the MCP org-separation stack (CODAGT-711 -> CODAGT-717 audit -> CODAGT-712 ACLs -> CODAGT-806 token RBAC).
Closes https://linear.app/codercom/issue/CODAGT-711
UAT: validated end to end on a two-org dogfood deployment, including a real pre-migration to post-migration upgrade, cross-org isolation (404s), same-slug-two-orgs, chat selection gating, and a live MCP tool call through the org-scoped generation path. The migration was later revised to keep existing rows in the default organization only (no per-organization copies); that revision is covered by the migration test suite.