mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
tl;dr is this should fix the storybook mcp server in Coder Agents. the storybook mcp never responds to the SSE GET, so it never connects, and reload fails. this disables that SSE GET and should let storybook reconnect on config reload --- HTTP workspace MCP servers whose `/mcp` endpoint never answers the standalone SSE GET (for example, the Storybook dev server) hang the agent's MCP reload indefinitely. The go-sdk opens that GET inside `Connect` on a context the agent's 30s connect timeout does not bound, and because reloads are singleflighted, every later `.mcp.json` change waits behind the hung one. The server never shows up as connected or failed, and no MCP config change is picked up until the agent restarts. Set `DisableStandaloneSSE: true` on the streamable HTTP transport. The agent's client registers no handlers for server-initiated messages, so the stream is unused; tool call responses and in-request notifications still arrive on the POST. <details> <summary>Investigation and decision log</summary> **Symptom:** a `storybook` HTTP server in `.mcp.json` never produced `storybook__*` tools in chats, while stdio servers worked. **Findings:** - Agent log showed the startup connect failing with `connection refused` (Storybook was not up yet), and a later reload failing with `standalone SSE request failed (session ID: ...)`, meaning `initialize` had succeeded and the GET stream was the failure point. - After touching `.mcp.json` with Storybook running, no reload result was ever logged (success or warning). - A standalone probe using go-sdk v1.8.0 and the same transport config hung in `Connect` past its 30s timeout. Goroutine dump: `Client.Connect -> sessionUpdated -> connectStandaloneSSE -> connectSSE`, which uses the connection's lifetime context, not the connect context. - `curl` against Storybook: `POST initialize` returns a session ID; `GET /mcp` with that session sends no response headers at all. The spec requires either `text/event-stream` or 405, so Storybook is non-compliant, but the agent should not wedge on it. - With `DisableStandaloneSSE: true`, the same probe connected in ~7ms and listed all 8 Storybook tools. **Why disable instead of alternatives:** - `mcp.NewClient(..., nil)` in `manager.go` registers no `tools/list_changed`, sampling, elicitation, roots, or logging handlers, so nothing on the standalone stream is consumed today. - The spec makes the standalone GET optional for clients; responses to client requests are never sent on it. - Bounding `Connect` with our own timeout would require abandoning a stuck goroutine and session per bad server, since the hang is inside the SDK call on a context we do not control. - Tradeoff: live `tools/list_changed` refresh would need the stream back, opened after `Connect` and off the reload path. **Out of scope, noted for follow-up:** - A server that is down when the agent first connects is not retried until `.mcp.json` changes. - A server that restarts invalidates the session ID, so the next POST fails and nothing reconnects the session. Disabling the standalone stream does not change this. </details> --- Generated by Coder Agents on behalf of @jeremyruppel.