feat: add group and user ACLs to MCP server configs - #27944
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: f646cb2a18
ℹ️ 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".
f646cb2 to
7ae5275
Compare
049edea to
d34f578
Compare
d34f578 to
ae152bb
Compare
ae152bb to
2582868
Compare
2582868 to
35e7987
Compare
35e7987 to
be9dc1f
Compare
be9dc1f to
81335a8
Compare
Documentation CheckUpdates Needed
Automated review via Coder Agents |
81335a8 to
02b5fe3
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. 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". |
02b5fe3 to
dc8c6e3
Compare
The auditor roles' wildcard MCPServerConfig read overrode per-server ACLs in every row-level read: OAuth2 connect and chat server selection admitted auditors on servers their ACL withheld. Keep the auditors' full management listing by fetching with system access behind the existing update-or-audit gate, and let row-level reads follow the ACL alone.
Consolidate the duplicated ACL role-update loops into one helper that owns UUID key canonicalization, and tighten two review-round comments.
Two accepted spellings of one UUID collapse to a single canonical key, so conflicting roles were resolved by map iteration order. Rekey the request maps canonically during validation and return 400 when two keys collapse to the same principal.
The ACL PATCH handler mapped every non-authorization transaction error to 500, so a config deleted between the middleware fetch and the locked re-fetch surfaced as an internal error. Map the not-found case to the same concealed 404 the update and delete handlers return.
Add godoc to the new exported ACL types and client methods, note that the down migration cannot remove the share scope enum value, and regenerate the TypeScript types that carry the comments.
Main claimed 000567, shifting the stack's migrations by one.
Main claimed 000568, shifting the audit migration to 000570.
Force On previously loaded the forced set with daemon scope, so a member the server's ACL denies still received its tools in every chat. The forced query now runs as the chat owner and the dbauthz post-filter drops servers the owner cannot read.
… from pre-ACL replicas
During a rolling upgrade a pre-ACL replica's insert omits both ACL
columns, so the '{}' defaults would hide the new server from every
ordinary member. A BEFORE INSERT trigger backfills the same Everyone
grant the application applies when both ACLs are empty.
Follows the audit migration's move to 000571 after main took 000569.
…ure doc Chatd architecture prose must be written by the human PR author, so leave a scoped TODO listing the owner ACL evaluation points and invariants the new section must cover.
The full-view list fetch bypasses row authorization with system access, so a key scoped only to mcp_server_config:update or audit_log:read could read config data its scope does not cover. Keep roles deciding full-view eligibility, but require the API key scope itself to permit reading MCP server configs before bypassing row authorization.
Upgrades run in scheduled maintenance downtime with the database locked during migration, so no pre-ACL replica inserts configs after the migration commits. The API sets the Everyone read grant explicitly on every insert.
Delete-authorized callers without update or audit grants could not see or fetch disabled configs: the list filtered them out and the detail endpoint concealed them with 404, so a delete-only custom role could never remove a disabled server. Keep disabled configs in the redacted list and detail responses when the caller may delete them.
httpapi.Is404Error already returns true for dbauthz.IsNotAuthorizedError, so the Forbidden branch after it could never execute. Concealing authorization failures as 404 is the documented intent here, matching the update and delete handlers.

Adds group and user ACLs to org-scoped MCP server configs so organizations can restrict specific MCP servers to subsets of members, mirroring the template ACL pattern.
Summary
group_acl/user_aclJSONB columns (nested{"<id>": {"permissions": [...]}}shape) and seeds every existing config with its organization's Everyone group read entry (the Everyone group ID equals the org ID), so member access is unchanged by default. Creation seeds the same entry.GetAuthorizedMCPServerConfigscompiles ACL-aware SQL filters.ActionShare(org admins) gatesGET|PATCH /api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}/acl(nested under the organization like the rest of the config surface). PATCH validates principals against the config's organization, merges sparse updates under a row lock, stampsupdated_by/updated_at, and is audited as a Write withOldcaptured before authorization. A config deleted concurrently between the middleware fetch and the locked re-fetch is concealed as 404, matching the update and delete handlers. ACL columns are tracked in the audit table.force_onserver whose ACL denies the owner never attaches at create, send, or generation time.Stacked on #27943. Part of the MCP org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-712
UAT: verified on a dogfood instance with two members and a custom group: Everyone-seed default visibility, group grant with Everyone removal (non-member loses list/fetch/selection), foreign-principal rejection, non-admin share denial (audited 403 / concealed 404), user_acl restore, and audited ACL diffs.