The Daemon supports two independent transport modes for external MCP clients. Separately, each host listens on two pipes that must not be mixed.
flowchart LR
subgraph Stdio Mode
Client1["MCP client<br/>Claude Desktop / Cursor"]
Daemon1["DevTools.Daemon --stdio<br/>headless MCP host"]
Client1 <-->|stdin/stdout| Daemon1
end
subgraph Gateway Mode
Client2["AI client<br/>ChatGPT / Perplexity"]
Gateway["McpGateway<br/>Cloudflare Workers"]
Daemon2["DevTools.Daemon (tray)<br/>WebSocket tunnel"]
Client2 -->|"POST /mcp<br/>Streamable HTTP"| Gateway
Gateway <-->|"WebSocket<br/>NDJSON frames"| Daemon2
end
subgraph HostPipes
Daemon1 --> McpPipe["DevToolsMcp_* SDK NDJSON"]
Daemon2 --> McpPipe
PytestClient["pytest and test runner"] --> PytestPipe["DevTools_* test pipe"]
McpPipe --> HostWire["McpPipeServer + SDK McpServer"]
PytestPipe --> PytestServer["DevToolsPipeServer"]
end
| Mode | Trigger | Transport | Use case |
|---|---|---|---|
| Stdio | --stdio arg |
Direct StreamServerTransport on process stdin/stdout |
Local MCP clients |
| Gateway | Auto on sign-in (tray host only) | Outbound WebSocket to Cloudflare relay | Remote AI clients |
| Pipe | Message format | Owner |
|---|---|---|
DevTools_{Host}_{Version}_{PID} |
Length-prefixed BridgeMessage |
DevToolsPipeServer |
DevToolsMcp_{Host}_{Version}_{PID} |
Newline-delimited JSON-RPC, MCP 2026-07-28 |
McpPipeServer + per-connection McpServer |
The daemon control pipe is a third listener in the Daemon process. It has no reply. See 0043.
Do not multiplex SDK frames onto the test pipe (or the reverse).
When an AI client spawns DevTools.Daemon.exe --stdio, a new process runs a self-contained MCP server on stdin/stdout. It boots its own McpEngine, HostConnections, and DiscoveryHostedService independently.
Key properties:
- Custom process entrypoint handles
--stdiobefore the WPF tray starts. - Bypasses the
SingleInstanceGuardmutex. - Discovers
DevToolsMcp_*pipes independently. - Auth tokens are read from the shared DPAPI file.
- Process exits when the MCP client disconnects.
- No control pipe, gateway tunnel, or tray icon in this mode.
- External tool/prompt surface is fixed (
ListChanged = false).
Outbound WebSocket connection to the McpGateway (Cloudflare Workers + Durable Objects):
TunnelClientconnects towss://<gateway>/tunnelwith Bearer token- Sends
registerframe withmachine_id,machine_name,host_apps - Wraps WebSocket frames as NDJSON streams via custom adapters
- Runs full MCP server over that transport
- Auto-reconnects with exponential backoff on failure
- Sends periodic
heartbeatframes with updatedhost_apps
One user can have Daemons on multiple machines connected to the same Gateway. The Gateway's Durable Object maintains a Map<machine_id, WebSocket>:
- Register → Daemon sends
machine_idandhost_apps. Every machine publishes the same tools, prompts, and resources - Connection requests (
initialize, notifications,tools/list) → any live machine. The gateway addslist_machinesand amachineIdargument to thetools/listresult. Daemon tool methods do not takemachineId list_machines→ answered by the gateway from its live tunnelstools/call→ gateway readsarguments.machineId, then removes it and forwards the call. One live machine needs no id. A missing or unknown id returns a tool result withisError: trueand the live machines- Dynamic invoke →
processIdinsidecode_modebelongs to the machine named bymachineId