feat: migrate coderd MCP server to official MCP Go SDK by ibetitsmike · Pull Request #28056 · coder/coder · GitHub
Skip to content

feat: migrate coderd MCP server to official MCP Go SDK - #28056

Merged
ibetitsmike merged 1 commit into
mainfrom
mike/mcp-go-sdk/coderd-http-server
Aug 13, 2026
Merged

feat: migrate coderd MCP server to official MCP Go SDK#28056
ibetitsmike merged 1 commit into
mainfrom
mike/mcp-go-sdk/coderd-http-server

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Stack Context

PR 1 of 6 in a stack that migrates every Coder MCP surface from the archived github.com/mark3labs/mcp-go library to the official github.com/modelcontextprotocol/go-sdk v1.7.0, adding MCP 2026-07-28 support while keeping compatibility with clients speaking 2024-11-05 through 2025-06-18.

Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061

Why

The coderd Streamable HTTP MCP server (/api/experimental/mcp/http) is the foundation layer: it introduces the official SDK dependency and the shared RegisterSDKTool helper the CLI server reuses.

  • The server runs the SDK handler in stateless mode with JSONResponse: true, preserving the previous application/json POST wire format. GET and DELETE return 405, and no Mcp-Session-Id is issued, both permitted by the Streamable HTTP spec.
  • DisableLocalhostProtection is set because coderd commonly listens on loopback behind a reverse proxy with a public Host header; the endpoint's bearer authentication is the relevant access control.
  • Tool registration builds raw JSON object schemas and omits empty required, keeping tools/list output byte-identical to the previous server (verified with a golden comparison).
  • SDK logs are adapted to cdr.dev/slog/v3; only warnings and errors are forwarded because the SDK logs several INFO lines per stateless request.
  • Tests cover the modern 2026-07-28 flow, legacy 2025-06-18 initialize, unsupported protocol version rejection (-32022), and non-POST method behavior.

Known behavior deltas vs the old endpoint

Both deltas come from the SDK enforcing the Streamable HTTP spec where mark3labs was lenient, on an experimental endpoint:

  • POST requests whose Accept header lists application/json without text/event-stream are now rejected with 400 (the spec requires clients to list both; a missing Accept header is still tolerated). mark3labs did not validate Accept at all.
  • The old server generated an unvalidated Mcp-Session-Id response header; the stateless SDK handler issues none. Clients that merely echo the header back are unaffected.

Validation

Beyond unit/integration tests, a remote dogfood UAT ran protocol conformance against a live dev server built from the stack tip: version negotiation matrix (2024-11-05 through bogus/omitted values), auth, session/method semantics, tool schema sanity, tools/call happy and error paths (unknown tool, schema-violating args, malformed JSON, jsonrpc "1.0"), and a concurrency smoke test. No 500s or connection drops; error shapes are clean JSON-RPC/HTTP errors.

Mux created this PR on Mike's behalf.

Replace mark3labs/mcp-go with modelcontextprotocol/go-sdk v1.7.0 for
the /api/experimental/mcp/http endpoint. The server now speaks MCP
2026-07-28 (stateless lifecycle, server/discover, -32022 rejection)
while still negotiating down to 2024-11-05 for legacy clients.

The endpoint was already effectively stateless (a fresh server per
request, format-only session validation), so the SDK's stateless mode
changes only spec-permitted surface behavior: no Mcp-Session-Id
header and 405 for GET/DELETE. Tool schemas, annotations, and text
result content are wire-identical (golden-diffed against the old
server). mark3labs remains in go.mod for the not-yet-migrated
surfaces and as the legacy test client.
@ibetitsmike
ibetitsmike merged commit 08a1525 into main Aug 13, 2026
62 of 64 checks passed
@ibetitsmike
ibetitsmike deleted the mike/mcp-go-sdk/coderd-http-server branch August 13, 2026 09:50
ibetitsmike added a commit that referenced this pull request Aug 13, 2026
## Stack Context

PR 2 of 6 in a stack that migrates every Coder MCP surface from the
archived `github.com/mark3labs/mcp-go` library to the official
`github.com/modelcontextprotocol/go-sdk` v1.7.0.

Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061

## Why

`coder exp mcp server` (stdio) now uses the official SDK server with
`mcp.IOTransport` over the invocation's stdin/stdout, and reuses the
shared `coderd/mcp.RegisterSDKTool` helper from PR #28056 so both
servers register tools identically.

- A `nopWriteCloser` prevents the SDK from closing the invocation's
stdout.
- Tests send spec-compliant initialize params and
`notifications/initialized` before `tools/list` because the official SDK
enforces the protocol lifecycle.

> Mux created this PR on Mike's behalf.
ibetitsmike added a commit that referenced this pull request Aug 13, 2026
…al Go SDK (#28058)

## Stack Context

PR 3 of 6 in a stack that migrates every Coder MCP surface from the
archived `github.com/mark3labs/mcp-go` library to the official
`github.com/modelcontextprotocol/go-sdk` v1.7.0.

Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061

## Why

The chatd external MCP client (admin-configured MCP servers used by
Agent chat) now holds `*mcp.ClientSession` connections created via
`mcp.NewClient` and `Client.Connect`, with `StreamableClientTransport`
or `SSEClientTransport` per server config.

- Auth and identity headers are injected through a custom
`http.RoundTripper` because the official SDK has no per-header transport
options.
- Tool input schemas are extracted from the SDK's `map[string]any`
decoding.
- Content conversion handles the official pointer content types; the SDK
decodes blob resources into raw bytes, so binary content is handled
without an extra base64 round trip.
- Test fixtures are official stateless Streamable HTTP servers.

> Mux created this PR on Mike's behalf.
ibetitsmike added a commit that referenced this pull request Aug 13, 2026
…l Go SDK (#28059)

## Stack Context

PR 4 of 6 in a stack that migrates every Coder MCP surface from the
archived `github.com/mark3labs/mcp-go` library to the official
`github.com/modelcontextprotocol/go-sdk` v1.7.0.

Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061

## Why

The workspace agent MCP manager now stores `*mcp.ClientSession` per
configured server.

- stdio servers use `mcp.CommandTransport` with an `exec.Cmd` built from
Coder's `agentexec.Execer`, preserving environment enrichment; the
command uses the manager's parent context so a stdio subprocess outlives
the connect handshake and stops when the session closes.
- HTTP and SSE servers use header-injecting HTTP clients.
- Binary tool content is re-encoded to base64 for the agent API because
the official SDK decodes it into raw bytes.
- The reload test now triggers config diffs via an environment variable
because the official SDK drops connections on non-protocol stdout output
(flags like `-test.v` made the fake server chatty).

> Mux created this PR on Mike's behalf.
ibetitsmike added a commit that referenced this pull request Aug 13, 2026
## Stack Context

PR 6 of 6 in a stack that migrates every Coder MCP surface from the
archived `github.com/mark3labs/mcp-go` library to the official
`github.com/modelcontextprotocol/go-sdk` v1.7.0.

Stack: #28056 -> #28057 -> #28058 -> #28059 -> #28060 -> #28061

## Why

With every production surface migrated, this PR removes the mark3labs
dependency entirely and converts the remaining test fixtures.

- Migrates the remaining mark3labs test fixtures (coderd MCP e2e tests,
chatd fixtures, mcpclient fixtures, and the Force On MCP policy tests)
to official stateless SDK servers.
- Removes `github.com/mark3labs/mcp-go` from `go.mod` and drops the
corresponding dependabot ignore entry. Zero references remain repo-wide.
- Updates the MCP docs for the 2026-07-28 protocol: stateless Streamable
HTTP behavior, the supported 2024-11-05 through 2026-07-28 protocol
range, and explicit non-features (resources, prompts, structured output,
elicitation, MCP Tasks).
- The e2e ping assertion is removed because MCP 2026-07-28 removed the
ping method.

> Mux created this PR on Mike's behalf.
pull Bot pushed a commit to annihilatorrrr/coder that referenced this pull request Aug 13, 2026
…er#28060)

## Stack Context

PR 5 of 6 in a stack that migrates every Coder MCP surface from the
archived `github.com/mark3labs/mcp-go` library to the official
`github.com/modelcontextprotocol/go-sdk` v1.7.0.

Stack: coder#28056 -> coder#28057 -> coder#28058 -> coder#28059 -> coder#28060 -> coder#28061

## Why

The aibridge injected-MCP proxy now owns an official `*mcp.Client`,
`*mcp.StreamableClientTransport`, and `*mcp.ClientSession`.

- The proxy constructor accepts an optional `*http.Client` instead of
mark3labs options; the header-injecting wrapper shallow-copies a
supplied client so its Timeout, Jar, and redirect policy survive.
- Manual protocol version negotiation and the mark3labs five-second
close workaround are removed; the SDK negotiates during `Connect` and
fails when no mutually supported version exists.
- Repeated `Init` closes the previous session, and a failed tool fetch
closes the just-created session so transports do not leak.
- Tool and intercept types use the official pointer content types;
embedded resource blobs are re-encoded to base64 for model-facing text
because the SDK decodes them into raw bytes.
- `aibridge/mcpmock` is regenerated, and its stale `go:generate` source
path is corrected.

> Mux created this PR on Mike's behalf.
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.

2 participants