Repository navigation
🤖 feat: create the Coder MCP server config for a CoderMCPServer - #304
Merged
Merged
Conversation
Change-Id: I6a1b09dd1e5811eaa57977b70199a075cc4b2b35 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Member
Author
|
@codex security review |
🛡️ Codex Security ReviewSecurity review completed. No security issues were found in this pull request. Reviewed commit: Only the user who started this review can view the report in Codex. ℹ️ About Codex security reviews in GitHubThis is an experimental Codex feature. Security reviews are triggered when:
Once complete, Codex will leave suggestions, or a comment if no findings are found. |
ThomasK33
added a commit
that referenced
this pull request
Oct 14, 2026
Change-Id: Ia2a200bdcfb547524c4b82def948c23d45c311d6 Signed-off-by: Thomas Kosiewski <tk@coder.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The dormant
CoderMCPServerreconciler now creates the Coder MCP server config, exactly once. It creates the config disabled, with the noncecoder-k8s:<UID>as its display name, and records its ID instatus.configID. Applying the spec (display name,enabled, the ACL) comes in the next PR, so this version stops atPendingwith the config disabled. The kind is not registered, so users see no change.Background
#155 adds
CoderMCPServer. The reconciler skeleton (#274) and the ACL helper (#294) are onmain. This PR follows theCoderAIProvidercreate (#253): a create marker before the request, a read before any second request, and no adoption of a config that this object did not create.How a create works
Citations name files at Coder tag v2.37.3. Every reconcile of an object without
status.configIDruns these steps:List the organization's configs. Coder has no lookup by slug, and the list shows every config to the site owner.
SlugConflict, rechecked every 10 minutes. The controller never changes it. Slugs are case-sensitive, soGITHUBdoes not blockgithub.CoderAnswerMismatch.If the marker is set and no config has the slug, check that the operator user is a site owner. Only then does a missing config prove that nothing committed. Otherwise wait with
ConfirmingCreate.Write the marker (
status.createAttemptTime) and the change token of every credential. The write uses the object's resourceVersion, so a conflict ends the reconcile before the create.Send the create: every spec field, the credentials of the auth type,
enabled: false, and the nonce. Each credential was read for every origin that receives it (🤖 feat: add the CoderMCPServer reconciler skeleton #274). It is revealed only into this request body.Handle the answer:
ConfirmingCreate, read again in 5 s.CreateRetryingin 30 s.SlugConflict. Nothing, as a site owner: clear the marker. Then 401, 403, 409 and 429 giveCreateRetrying, and any other 4xx givesCoderRejected. If the read fails, keep the marker and read again.ConfirmingCreate, read again in 5 s.A spec that Coder rejected is not sent again. A stalled
CoderRejectedcondition of the current generation stops the create, and a failed confirming read keeps that memory in the wait reasonConfirmingRejectedCreate. A spec change creates again.Coder's unique slug index (
coderd/database/dump.sql:4519-4520) makes a second create of the slug answer 409. A create whose first answer was lost but that committed later is therefore found by the confirming read, and one config remains in every order.Smaller changes in
codermcpserver_controller.go:readMCPServerCredentialsreturns the values by status field (apiKey,header:<Name>,oauth2ClientSecret), so the request and the change tokens use them.findMCPServerSlug. It returns the matching config.Validation
internal/controller/codermcpserver_create_test.go(envtest and the fake Coder, with the leak scan of all three synthetic credentials on every reconcile):TestCoderMCPServerCreate: one create for each auth type (API key, two custom headers, OAuth2 with revocation URL and scopes, none with every optional field), plus a slug taken byGITHUB. The request carriesenabled: falseand the nonce, Coder stores every field and credential, and status records the ID and each change token. The next reconcile sends no MCP request.TestCoderMCPServerCreateAnswers: reset before commit, reset after commit, 504 after commit, an HTML page after commit, a 201 for another slug, 429, 401, and a 400 after commit. Each case ends with exactly one config with the slug, and a second create is sent only when the read proved that the first one did not commit.TestCoderMCPServerCreateRejected: a 400 whose detail holds the key, then a failed confirming read. The next reconcile stalls withCoderRejectedand sends nothing more for that spec. A spec change creates.TestCoderMCPServerCreateSlugRace: a config created between the list and the create answers 409. The read finds it, and the controller sends no PATCH or DELETE.TestCoderMCPServerCreateRecovery: a marker left by an earlier run, with a new reconciler. A committed create is recorded with zero creates. No committed create gives one create. An operator with only an organization role named owner waits with zero creates.TestCoderMCPServerMarkerConflictSendsNoCreate: a conflict on the marker write sends zero creates.Existing 🤖 feat: add the CoderMCPServer reconciler skeleton #274 tests now expect the create.
Mutation checks: each of these 20 changes makes a test fail:
model_intentOne more mutant survives on purpose: removing the immediate status write after the ID is recorded. In this PR nothing follows that write, so the final write stores the same status. The next PR's
TestCoderMCPServerApplyLostStatusWritekills it.Local gates:
make verify-vendor,make test,make test-integration,make build,make lint,make docs-check,GOFLAGS=-mod=vendor go tool govulncheck ./...andgo test -race ./internal/controller/.make codegen,make manifests,make build-installerandmake docs-referenceleave no diff.Size
640 lines added and 39 removed: 246 in
codermcpserver_create.go, 339 in its tests, and 94 changed lines in the #274 files (78 in the reconciler, 16 in its tests). No generated files. That is above the ~550 target. The marker, the answer classes and the recovery read are one exactly-once unit. A split between them would leave a PR whose marker nothing can resume, or a third stacked layer. The apply step is split off into the next PR.Refs #155
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$83.70