Skip to content

🤖 feat: create the Coder MCP server config for a CoderMCPServer - #304

Merged
ThomasK33 merged 1 commit into
mainfrom
mcp-create
Oct 14, 2026
Merged

ThomasK33 merged 1 commit into
mainfrom
mcp-create

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

The dormant CoderMCPServer reconciler now creates the Coder MCP server config, exactly once. It creates the config disabled, with the nonce coder-k8s:<UID> as its display name, and records its ID in status.configID. Applying the spec (display name, enabled, the ACL) comes in the next PR, so this version stops at Pending with 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 on main. This PR follows the CoderAIProvider create (#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.configID runs these steps:

  1. List the organization's configs. Coder has no lookup by slug, and the list shows every config to the site owner.

    • A config with the slug and this object's nonce: this object created it. Record its ID, clear the marker, and write status at once.
    • A config with the slug and any other display name: SlugConflict, rechecked every 10 minutes. The controller never changes it. Slugs are case-sensitive, so GITHUB does not block github.
    • A list entry of another organization, or a matching entry without an ID: CoderAnswerMismatch.
  2. 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.

  3. 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.

  4. 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.

  5. Handle the answer:

    Answer Result
    201 that matches the request Record the ID and clear the marker
    201 for another slug, organization, display name, or without an ID Keep the marker. ConfirmingCreate, read again in 5 s.
    Dial or TLS failure The request was never sent. Clear the marker. CreateRetrying in 30 s.
    Any 4xx A proxy can replace a committed answer, so read first (🤖 feat: create the Coder provider for a CoderAIProvider #253 review round 3). Ours: record. Someone else's: SlugConflict. Nothing, as a site owner: clear the marker. Then 401, 403, 409 and 429 give CreateRetrying, and any other 4xx gives CoderRejected. If the read fails, keep the marker and read again.
    5xx, reset, timeout, HTML page, other 2xx Uncertain. Keep the marker. ConfirmingCreate, read again in 5 s.
  6. A spec that Coder rejected is not sent again. A stalled CoderRejected condition of the current generation stops the create, and a failed confirming read keeps that memory in the wait reason ConfirmingRejectedCreate. 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:

  • readMCPServerCredentials returns the values by status field (apiKey, header:<Name>, oauth2ClientSecret), so the request and the change tokens use them.
  • The read-only slug lookup of 🤖 feat: add the CoderMCPServer reconciler skeleton #274 is now 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 by GITHUB. The request carries enabled: false and 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 with CoderRejected and 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:

    • no rejected-spec memory, either form
    • no site-owner check, or a check that accepts an organization role named owner
    • every slug match treated as ours, or a foreign one that keeps the marker
    • no marker write before the create
    • a wrong 201 accepted
    • 4xx without a confirming read, or a confirming read that ignores a found config
    • retry and rejection swapped, or 401 not retried
    • created enabled, or with the spec display name instead of the nonce
    • scopes joined with commas, a missing description, a missing model_intent
    • a change token without the resourceVersion
    • a marker kept after a 201
    • a create on every reconcile

    One 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 TestCoderMCPServerApplyLostStatusWrite kills it.

  • 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

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

Change-Id: I6a1b09dd1e5811eaa57977b70199a075cc4b2b35
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-14T02:04:16.592930Z 48ea538 PR opened
🔒 Security Review ✅ Completed 2026-10-14T02:07:53.786169Z 48ea538 Manual request
ℹ️ 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

Copy link
Copy Markdown

🛡️ Codex Security Review

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

Reviewed commit: 48ea538db6

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 849a07e Oct 14, 2026
10 checks passed
@ThomasK33
ThomasK33 deleted the mcp-create branch October 14, 2026 04:03
ThomasK33 added a commit that referenced this pull request Oct 14, 2026
Change-Id: Ia2a200bdcfb547524c4b82def948c23d45c311d6
Signed-off-by: Thomas Kosiewski <tk@coder.com>
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