Architecture
High-level architecture
Section titled “High-level architecture” muxed call server/tool '{}' ──────────────────────────────────► ┌──────────────────────┐ (Unix socket: ~/.muxed/muxed.sock) │ muxed daemon │ │ │ muxed tools │ ServerManager(fs) │──► [stdio: filesystem] ──────────────────────────────────► │ ServerManager(pg) │──► [stdio: postgres] │ ServerManager(...) │──► [HTTP: remote] muxed servers │ │ ──────────────────────────────────► └──────────────────────┘ (auto-exits after idle)The CLI is a thin client. Every muxed command connects to the daemon over a Unix socket, sends a JSON-RPC request, prints the result, and disconnects. The daemon owns all MCP server connections, maintaining them across CLI invocations so servers stay warm and ready.
Daemon startup flow
Section titled “Daemon startup flow”- Load config from
muxed.config.json - Create ServerPool and connect to all configured MCP servers
- Auto-generate TypeScript types from tool schemas
- Create JSON-RPC server on Unix domain socket (
~/.muxed/muxed.sock) - Optionally start HTTP listener (if
daemon.http.enabled) - Write PID file
- Signal parent process that daemon is ready
Lock file mechanism
Section titled “Lock file mechanism”An atomic lock at ~/.config/muxed/muxed.lock prevents race conditions during startup. This is especially important when multiple terminal sessions or editors attempt to start the daemon simultaneously.
- Uses exclusive file creation (
wxflag) to guarantee atomicity - Validates the lock holder is alive and is a muxed/node process
- Stale locks are detected and cleaned up automatically
Stale daemon detection
Section titled “Stale daemon detection”When starting, muxed checks for stale state left behind by a previously crashed daemon:
- Reads PID file, verifies process exists via
/proc - Confirms process is actually muxed/node (not a recycled PID)
- Tests socket connection with 2s timeout
- If stale: cleans up PID file, socket file, and lock file
IPC protocol
Section titled “IPC protocol”- Transport: Unix domain socket at
~/.muxed/muxed.sock - Encoding: Newline-delimited JSON-RPC 2.0
- Connection model: One-shot connections — connect, send request, receive response, disconnect
- The daemon resets its idle timer on each request
JSON-RPC methods
Section titled “JSON-RPC methods”The daemon exposes 19 methods over the JSON-RPC interface:
| Method | Description |
|---|---|
servers/list | List all servers with status and capabilities |
tools/list | List tools (optionally filtered by server) |
tools/info | Get a specific tool’s schema |
tools/call | Invoke a tool (supports fields param for response filtering) |
tools/call-async | Invoke a tool asynchronously (returns task handle) |
tools/validate | Validate arguments against tool schema without executing |
tools/grep | Search tools by pattern |
resources/list | List resources |
resources/read | Read a resource |
prompts/list | List prompt templates |
prompts/get | Render a prompt |
completions/complete | Get argument completions |
tasks/list | List active tasks |
tasks/get | Get task status |
tasks/result | Get completed task result |
tasks/cancel | Cancel a running task |
auth/status | Get OAuth authentication status |
daemon/status | Daemon status (PID, uptime, servers) |
config/reload | Reload config, reconnect changed servers |
daemon/stop | Shut down the daemon |
Structured errors
Section titled “Structured errors”All JSON-RPC error responses include structured error data in the error.data field, designed to help agents self-correct:
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Tool not found: slack/search_msgs", "data": { "code": "TOOL_NOT_FOUND", "suggestion": "Did you mean: slack/search_messages? Run 'muxed grep <pattern>' to search available tools.", "context": { "similarTools": ["slack/search_messages", "slack/search_files"] } } }}Error codes: TOOL_NOT_FOUND, SERVER_NOT_FOUND, SERVER_NOT_CONNECTED, INVALID_FORMAT, MISSING_PARAMETER, INVALID_ARGUMENTS, TIMEOUT.
Fuzzy matching uses Levenshtein distance to suggest similar tool names when a tool is not found.
Tool validation (tools/validate)
Section titled “Tool validation (tools/validate)”The tools/validate method validates arguments against a tool’s inputSchema without executing the call. This enables dry-run validation for agents to check their arguments before spending tokens on a real call.
Request:
{ "method": "tools/validate", "params": { "name": "server/tool", "arguments": { "key": "value" } } }Response:
{ "result": { "valid": true, "errors": [], "warnings": ["Tool is marked as destructive."], "tool": { ... } } }Validation checks: required fields, unknown fields, type mismatches, enum values. Warnings include tool annotation hints (destructive, not idempotent, not read-only).
Response field filtering
Section titled “Response field filtering”The tools/call method accepts an optional fields parameter — an array of dot-notation paths to extract from the response. This reduces response size for agents with limited context budgets.
{ "method": "tools/call", "params": { "name": "db/query", "arguments": { "sql": "SELECT * FROM users" }, "fields": ["rows[].name", "rows[].email"] } }Filtering only applies to JSON-parseable content (structuredContent or text blocks containing valid JSON). Non-JSON text, images, and other content types are returned unchanged.
Optional HTTP listener
Section titled “Optional HTTP listener”The daemon can optionally expose the same JSON-RPC interface over HTTP, useful for remote access, container deployments, or multi-editor setups.
- Plain
http.createServeron configurable port (default 3100) - POST-only, same JSON-RPC handler as the Unix socket
- Origin validation: rejects requests from non-localhost origins (DNS rebinding protection)
ServerPool
Section titled “ServerPool”The ServerPool manages all ServerManager instances and provides an aggregated view of every connected MCP server.
- Each ServerManager wraps one MCP server connection using the
@modelcontextprotocol/sdkClient - Per-server connection states:
connecting,connected,error,closed - ServerPool provides aggregation methods:
listAllTools(),findTool(),grepTools(), etc.
Health checks
Section titled “Health checks”- Periodic
ping()calls at configurable interval (default 30s) - Tracks consecutive failures per server
- Marks server as
errorafter threshold is exceeded - Health status visible via
muxed serversandmuxed daemon status
Auto-reconnect
Section titled “Auto-reconnect”When a server connection drops, the daemon automatically attempts to reconnect using exponential backoff:
- Backoff schedule: 1s, 2s, 4s, 8s, 16s… up to 60s max
- Max restart attempts: configurable (default: unlimited)
- Backoff reset: resets on successful reconnection
- Handles both stdio server crashes and HTTP connection drops
Graceful shutdown
Section titled “Graceful shutdown”When the daemon receives SIGTERM or SIGINT, it follows an ordered shutdown sequence:
- SIGTERM or SIGINT received
- Stop accepting new connections on socket
- Wait for in-flight requests to complete (up to
shutdownTimeout, default 10s) - Force-close remaining connections if timeout exceeded
- Disconnect all MCP servers gracefully
- Kill child processes
- Remove socket file and PID file
- Exit
Transport types
Section titled “Transport types”stdio — for local MCP servers
Section titled “stdio — for local MCP servers”- Server runs as a child process
- Communication via stdin/stdout
- Process lifecycle managed by muxed
Streamable HTTP — for remote MCP servers
Section titled “Streamable HTTP — for remote MCP servers”- Uses
StreamableHTTPClientTransportfrom the MCP SDK - POST for all client-to-server messages
- GET to open SSE stream for server-to-client messages
MCP-Session-Idheader: auto-detected from server, stored, sent on subsequent requestsMCP-Protocol-Versionheader: sent after initialization- SSE reconnection with
Last-Event-IDfor resumability
SSE (legacy) — for older servers
Section titled “SSE (legacy) — for older servers”- Uses
SSEClientTransportfrom the SDK - Selected with
transport: "sse"in config
Protocol version negotiation
Section titled “Protocol version negotiation”- muxed requests protocol version
2025-11-25during initialize - The SDK handles version negotiation — if server only supports older versions, SDK negotiates downgrade
- Negotiated version stored per server, visible in
muxed serversoutput
MCP capability support
Section titled “MCP capability support”- Tools: title, annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint), outputSchema, structuredContent
- Resources: text and blob content types
- Prompts: argument rendering
- Completions: argument auto-complete
- Tasks: async tool execution with progress tracking (experimental)
- Content types: text, image, audio, resource links, structured content
Task system
Section titled “Task system”The task system enables asynchronous tool execution with progress tracking:
callAsyncinvokes a tool and returns aTaskHandlewithtaskId- Daemon tracks active tasks per server
- On server disconnect: tasks marked as
unreachable - On reconnect: task status re-queried if server maintains state
- Configurable expiry timeout (default 1 hour) for stale task cleanup
Idle shutdown
Section titled “Idle shutdown”The daemon automatically exits after a period of inactivity to conserve resources:
- Configurable timeout (default 5 minutes)
- Timer resets on each incoming request
- Set
idleTimeout: 0to disable (daemon runs indefinitely) - On timeout: triggers graceful shutdown sequence