initialize handshake is gone, requests are self-contained, and the protocol becomes request/response. We adopted it alongside the TypeScript SDK v2 that implements it. The upgrade ships in DBHub 1.0.0 — 2026-07-28 clients get the new protocol on the same /mcp endpoint that keeps serving 2025-era clients.
This post shows each change with before/after code, and what we evaluated but did not build.
1. The stateless core
DBHub’s HTTP transport has always been stateless — no session affinity, safe behind a round-robin load balancer. The 2025-era protocol assumed a session, so we faked statelessness: a fresh transport and server instance per request, session management disabled by hand. Before:createMcpHandler — one factory, both protocol eras):
_meta — no initialize exchange, no Mcp-Session-Id. The per-request model we hand-rolled is now the native serving model; 2025-era clients are handled by the SDK’s built-in stateless fallback. stdio got the same treatment: server.connect(new StdioServerTransport()) became serveStdio(createServer).
Result: ~25 lines of session-faking deleted. Running N replicas behind any load balancer is now spec-guaranteed instead of workaround-dependent.
2. Cacheable tools/list
DBHub’s tool list is fixed per configuration, yet every 2025-era client re-fetches it each session — the protocol had no way to say “this doesn’t change.” The 2026-07-28 revision adds ttlMs / cacheScope fields on cacheable results. Our entire change:
Before:
3. Header-based routing
2026-07-28 clients sendMcp-Method and Mcp-Name headers on every Streamable HTTP request, so a gateway in front of DBHub can tell a write-capable execute_sql call from a search_objects call without parsing JSON bodies. Our change was one CORS line (the headers come from the SDK):
Before:
4. Evaluated and skipped: MRTR write confirmation
Multi-round-trip requests (MRTR) replace server-initiated elicitation: a tool returnsresultType: "input_required" and the client retries with answers — which finally works over stateless HTTP. The obvious application for a database tool: a confirmation prompt before writes.
We designed it and didn’t ship it:
- Confirmation is the host’s job. DBHub already annotates write-capable tools with
destructiveHint: true, and hosts like Claude Code gate destructive calls behind their own approval UI. A server-side prompt double-confirms. - The fallback is weak. Clients without elicitation degrade to a
confirm: trueargument, which models learn to set preemptively. - It required new config. Every design added TOML knobs on top of the existing
readonlyboolean, creating conflicting semantics.
readonly boolean in four places. It now flows through one pipeline: every statement is classified (read | dml | ddl | admin | unknown, dialect-aware), and the config compiles into a class→verdict policy.
Before (one of four scattered interpretations):
confirm member later — when elicitation-capable clients are the norm, MRTR write confirmation slots into the two gate sites without touching config. Until then, readonly = true/false remains the entire configuration vocabulary.
5. Compatibility
- 2025-era clients are unaffected. Same
/mcpendpoint, sameinitializehandshake, served by the SDK’s stateless fallback. stdio clients see no difference. - One caveat for hand-rolled HTTP clients: the legacy path now answers with spec-standard SSE framing (
event: message/data: {...}) instead of plain JSON bodies. MCP clients already accept both; a rawcurlscript needs to read thedata:line. - No config migration. No TOML key is added or changed.
Takeaway
For DBHub, adoption was mostly deletion — the spec absorbed our workaround — plus two small opt-ins (cache hints, CORS headers). Everything here ships in DBHub 1.0.0. The source is at github.com/bytebase/dbhub; the adoption is a readable series of commits onmain.