Skip to content

🤖 feat: apply the CoderMCPServer spec and access list to the created config - #307

Merged
ThomasK33 merged 5 commits into
mainfrom
mcp-apply
Oct 14, 2026
Merged

ThomasK33 merged 5 commits into
mainfrom
mcp-apply

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

The dormant CoderMCPServer reconciler now applies the spec to the config that it created. It removes the access list entries that the spec does not list, enables the config with every spec value, then adds the missing groups, and reports Ready. The kind is not registered, so users see no change.

Base branch: mcp-create (#304). Review only the last commit. After #304 lands, I retarget this PR to main.

Background

#155 adds CoderMCPServer. #304 creates the config disabled, with the nonce as its display name, and records its ID. This PR is plan step 4.5 create steps 5 and 6. It makes the ACL helper from #294 live, and adds a remove-only pass to it.

How an apply works

An object with status.configID runs these steps after the checks of #274, unless its generation is applied and every credential's change token equals the one in status.credentials. A Secret change moves the token but not the generation, so a rotated credential is sent again. The in-memory hash that skips the resend after a metadata-only Secret change comes with M6.

  1. GET the config by ID. A 404 gives DeletedExternally with no write. Creating it again after a spec change comes with the update path (M6). An answer for another ID, organization or slug gives CoderAnswerMismatch.
  2. Remove every ACL entry that spec.access does not list, and add nothing yet (narrowMCPServerACL, a remove-only pass of the 🤖 feat: add the CoderMCPServer ACL helper #294 helper). The helper resolves every name first, so GroupNotFound stops the reconcile before any write, and a new config stays disabled with the nonce.
  3. Send one PATCH with every spec field: display name, description, icon, transport, URL, auth type, availability, enabled (default true), the three flags, and the fields and credentials of the auth type.
    • The PATCH always sets url, so it always carries every credential of the auth type (plan 4.6 item 5). Each credential was read in this reconcile for every origin that receives it. It is revealed only into this request body.
    • Fields of other auth types stay unset. A changed auth type makes Coder clear them.
    • A 400 gives CoderRejected. Other errors wait 30 s, because the PATCH sets absolute values and sending it again is safe. That includes a 404: a proxy can put one in place of a committed answer, so the GET of the next reconcile decides whether the config is gone.
    • The answer must match the request in every field that Coder returns: ID, display name, description, icon, transport, URL, auth type, availability, api_key_header, the OAuth2 fields, enabled, the three flags, and the has_* flag of each credential sent. When the PATCH changes the auth type, Coder must also report the other types' credentials as cleared (plan F9). Anything else gives CoderAnswerMismatch.
  4. Add the groups that spec.access lists and the ACL lacks (the full 🤖 feat: add the CoderMCPServer ACL helper #294 helper).
  5. Record the change tokens and status.appliedGeneration, and report Ready with reason Applied. Later reconciles of the same generation send no MCP request. Reading Coder for drift, a sparse PATCH, and the kill switches come with M6.

Why this order: while the PATCH changes or enables the config, its ACL holds only groups that both the old and the new access list allow. A new config is therefore never enabled with a group that the spec does not list (plan S20). A PATCH that fails leaves the ACL narrower than both lists, never broader. A group added in a new generation never gets the old configuration, for example when the same generation disables the server.

The ID is written to status before step 1. The PATCH replaces the nonce, so after it only the stored ID proves that this object created the config. If the status write after the PATCH is lost, the next reconcile applies again instead of reporting its own config as a SlugConflict.

Access list doc (follow-up from #294)

The spec.access field doc now says that every sync removes each user entry, for example a user that an admin added in the Coder dashboard, because access lists groups only. It also says that Coder does not show the entry of a deleted group, so the sync cannot remove it, but it grants nothing. Coder deletes groups for real (coderd/database/queries/groups.sql:190-194). Deleted users stay in the user table (users.sql:422-428) and still appear in the ACL, so the sync removes their entries. The dormant CRD carries the new text. The kind has no user docs yet. The plan's M9 row now requires the same statement in docs/how-to/manage-mcp-servers.md.

Validation

internal/controller/codermcpserver_apply_test.go (envtest and the fake Coder, with the leak scan on every reconcile):

  • TestCoderMCPServerApplyOrder (plan S20): with access.groups: [data], the writes are the create, an ACL PATCH that only removes Everyone, the enabling PATCH, and an ACL PATCH that only adds data. Coder holds the config enabled with the spec display name and an ACL of exactly data, and the PATCH carries the API key. The next reconcile sends nothing.
  • TestCoderMCPServerApplyNeverBroadensBeforeTheChange: an applied config readable by Everyone and data gets a new generation with data and ops, plus enabled: false (rejected with a 400) or a URL change (failing with a 502). After the failure the ACL holds only data: Everyone is gone and ops is not added. The next reconcile applies.
  • TestCoderMCPServerApplyAccess (plan S15): access: {groups: []} with enabled: false gives an empty ACL on a disabled config. Without access, no ACL route is called, and Coder's Everyone entry stays.
  • TestCoderMCPServerApplyGroupNotFound: the config stays disabled with the nonce, with no PATCH. Once the group exists, the next reconcile applies.
  • TestCoderMCPServerApplyFailures: a 404 or 503 on the read, an answer for another slug, a 500 or 400 on the PATCH whose detail holds the key, a 404 on the PATCH (a wait, then applied), and PATCH answers with another enabled, description, availability, flag, API key header, or no key. Each leaves the generation unapplied. The retryable ones apply on the next reconcile.
  • TestCoderMCPServerApplySpecChange: a URL and description change sends one PATCH with the new values and the key.
  • TestCoderMCPServerApplySecretRotation: a new Secret value with no spec change gives one PATCH with the new value and the new change token. The next reconcile sends nothing.
  • TestCoderMCPServerApplyAuthTypeChange: api_key to none applies when Coder clears the key, and stalls with CoderAnswerMismatch when the answer still has has_api_key: true.
  • TestCoderMCPServerApplyLostStatusWrite: the status write after the PATCH fails. The next reconcile applies again with no second create.
  • The 🤖 feat: add the CoderMCPServer reconciler skeleton #274 and 🤖 feat: create the Coder MCP server config for a CoderMCPServer #304 tests now expect Applied where they expected Pending.
  • Mutation checks: each of these 19 changes makes a test fail:
    • a 404 on the read not treated as gone
    • no organization or slug check on the read
    • the PATCH sent although the ACL step stopped
    • a 400 on the PATCH treated as a wait
    • no change tokens recorded
    • enabled: false ignored
    • the API key left out of the PATCH
    • the nonce kept as display name
    • the URL left out of the PATCH
    • the description left out
    • Ready for any applied generation instead of the current one
    • no status write of the ID before the apply
    • the full ACL sync before the PATCH instead of the remove-only pass
    • no ACL sync after the PATCH
    • additions in the remove-only pass
    • no check of the answered strings, of enabled, or of has_api_key
    • Ready without comparing the change tokens
    • no check of the old type's has_* flag after an auth type change
  • Local gates: make verify-vendor, make test, make test-integration, make build, make lint, make docs-check, GOFLAGS=-mod=vendor go tool govulncheck ./... and go test -race ./internal/controller/. make codegen, make manifests, make build-installer and make docs-reference leave no diff.

Size

646 lines added and 44 removed, 631 of them hand-written: 181 in codermcpserver_apply.go, 23 changed lines in the #294 ACL helper (the remove-only pass), 21 in the reconciler, 382 in the apply tests, 65 changed lines in the existing tests, and 9 lines of field doc. The regenerated dormant CRD has 9 changed lines. The two Codex review rounds added 233 of these lines. Before them this PR was 413 lines. It stays one PR because the review fixes change the apply order and the Ready rule that every apply test checks.

Review record and deferred findings

Codex reviewed three heads. Two fix rounds (the cap) fixed five findings. The third review (normal and security, on db5b3e0) found three more, each deferred with an issue, after an independent advisor recommended "ready with tracked follow-ups":

The kind is not registered, so none of them can affect an install before M9.

Refs #155


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $83.70

Change-Id: I6a1b09dd1e5811eaa57977b70199a075cc4b2b35
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…config

Change-Id: I51f32054c2be80a04ccb71cd66155c257d695749
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T04:22:40.481886Z 8d732d8 Manual request
🔒 Security Review ✅ Completed 2026-10-14T04:23:08.209773Z 8d732d8 Manual request

Security findings

Advisory findings (2)

ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d83d2f30b

ℹ️ 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".

Comment thread internal/controller/codermcpserver_apply.go Outdated
Comment thread internal/controller/codermcpserver_apply.go Outdated
Comment thread internal/controller/codermcpserver_apply.go Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 6d83d2f30b

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…heck every answered field

Change-Id: I33c42b0d71af811d67f331d775c0a0ecb171b35d
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dc8a590f7

ℹ️ 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".

Comment thread internal/controller/codermcpserver_controller.go Outdated
Comment thread internal/controller/codermcpserver_apply.go
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 0dc8a590f7

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…eared the old credential

Change-Id: Ia71b65e0fb3a49afd17495c7d8bd91edf329a119
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db5b3e0d9e

ℹ️ 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".

Comment thread internal/controller/codermcpserver_acl.go
Comment thread internal/controller/codermcpserver_controller.go
Comment thread internal/controller/codermcpserver_apply.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: db5b3e0d9e

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread internal/controller/codermcpserver_apply.go
Comment thread internal/controller/codermcpserver_apply.go
Change-Id: Ia2a200bdcfb547524c4b82def948c23d45c311d6
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 8d732d8fb8

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 8d732d8fb8

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit af5ba24 Oct 14, 2026
10 checks passed
@ThomasK33
ThomasK33 deleted the mcp-apply branch October 14, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant