Skip to content

Add shared multi-client Streamable HTTP transport for MCP #6258

Description

@Glucksberg

What problem are you trying to solve?

Open Design currently exposes its MCP server through stdio only.

In environments with multiple concurrent agent conversations, each conversation starts a separate od mcp Node.js process. These adapters already share the same Open Design daemon and persistent state, but each one still duplicates the MCP adapter runtime and consumes additional memory.

This becomes particularly expensive on constrained or long-running servers where Codex, OpenClaw, or another MCP host keeps several sessions active. The desired behavior is one long-running Open Design MCP adapter process serving multiple independent client sessions.

Describe the solution you'd like

Add an opt-in, multi-client [Streamable HTTP] transport to od mcp, while preserving stdio as the default for full backward compatibility.

Proposed CLI:

od mcp \
  --transport http \
  --host 127.0.0.1 \
  --port 7457

Proposed endpoint:

http://127.0.0.1:7457/mcp

Expected behavior:

  • od mcp continues to use stdio by default.
  • One HTTP adapter process accepts multiple concurrent MCP clients.
  • Each client receives an isolated MCP session ID and protocol state.
  • All sessions share the same Open Design daemon, projects, memory, and connector state.
  • Existing Open Design state and authentication remain unchanged; switching transport must not require reauthentication.
  • The server binds to loopback by default and does not expose connector credentials.
  • Abandoned sessions are expired and a configurable maximum prevents unbounded session growth.
  • The adapter gracefully closes sessions on shutdown.
  • When daemon discovery is implicit, a sidecar restart or port change invalidates the cached daemon URL, rediscovers it through IPC, and retries the failed request once.

Suggested CLI options:

--transport <stdio|http>   default: stdio
--host <address>           default: 127.0.0.1
--port <number>            default: 7457
--max-sessions <number>
--session-idle-timeout <duration>

Settings → MCP Server could also show the selected transport, endpoint reachability, and copyable URL-based client snippets. Existing stdio configurations should not be migrated automatically.

Alternatives you've considered

  • Keep spawning one stdio adapter per agent session. This works, but duplicates Node.js processes and memory even though every adapter targets the same daemon.
  • Mount MCP directly on the daemon's current HTTP port. The sidecar port can be ephemeral, which makes stable client configuration difficult.
  • Make the Open Design daemon manage a second fixed MCP listener immediately. This may create lifecycle and namespace/port-conflict concerns. A standalone long-running HTTP adapter is a smaller first step; service-manager integration can follow separately.
  • Use a stateful HTTP server without per-client MCP sessions. This risks routing protocol state across clients and should be avoided.

Additional context

PR hint

A possible implementation path:

  1. Extract MCP construction and handler registration from runMcpStdio() into a transport-independent factory such as createOpenDesignMcpServer(options).

  2. Keep StdioServerTransport on the existing default path.

  3. Add runMcpHttp() using the MCP SDK's StreamableHTTPServerTransport and the existing Express dependency.

  4. Maintain a session registry:

    Map<string, {
      server: Server
      transport: StreamableHTTPServerTransport
      lastSeenAt: number
    }>
  5. Route POST/GET/DELETE requests by MCP session ID; remove sessions on DELETE, transport close, idle timeout, and graceful shutdown.

  6. Use createMcpExpressApp({ host }) or equivalent Host validation to mitigate DNS rebinding. Bind to 127.0.0.1 by default, avoid permissive CORS, and require explicit authenticated configuration before allowing a non-loopback bind.

  7. Extend install-info/manual snippets with URL-based Codex and JSON configurations without removing command-based stdio snippets.

  8. Add daemon URL cache invalidation and a single rediscovery/retry when implicit IPC discovery points to a restarted daemon.

Relevant implementation areas:

  • apps/daemon/src/mcp.ts
  • apps/daemon/src/cli.ts
  • apps/daemon/src/mcp-agent-install.ts
  • apps/daemon/src/mcp-install-info.ts
  • apps/daemon/src/mcp-routes.ts
  • Settings → MCP Server UI and locale files
  • daemon tests under apps/daemon/tests

Suggested acceptance tests:

  • Existing od mcp still starts stdio.
  • Two or more clients initialize concurrently and receive distinct session IDs.
  • Five clients use one adapter OS process.
  • Requests and shutdown from one session do not affect another.
  • Unknown, deleted, and expired sessions return protocol-appropriate errors.
  • Idle and maximum-session cleanup works.
  • SIGINT/SIGTERM close every session and the listener.
  • Loopback works by default; unsafe non-loopback binding is rejected.
  • Credentials and authorization headers never appear in responses or logs.
  • The adapter recovers after an implicitly discovered Open Design sidecar restarts.
  • stdio and HTTP expose the same tools and resources.

Contribution timing

I am willing to contribute the implementation. To leave room for maintainer guidance or an existing effort, I will wait until July 31, 2026. If nobody signals that they are taking this on or points to work already in progress by then, I can open a draft PR following the approach above.

Would you be willing to contribute a PR?

Yes, I can take this on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions