MCP Coordination Server

stonemux Documentation

Multi-agent coordination without tmux. Inboxes, doorbell webhooks, task routing, broadcast, shared state, and real-time event streaming. Your agents talk to each other — asynchronously, reliably, auditably.

1. Overview

stonemux is a compiled Rust binary that runs as a local MCP server providing multi-agent coordination primitives. It replaces ad-hoc tmux-based agent communication with structured messaging, task lifecycle management, shared state with optimistic concurrency, and real-time event streaming.

Key Capabilities

  • Per-agent inboxes — Every registered agent gets a persistent inbox. Messages are stored, ordered, and retrievable via polling or Server-Sent Events.
  • Doorbell webhooks — Instant notification when a message arrives. No polling overhead. Configurable per-agent webhook URL.
  • Long-poll fallback — For environments where webhooks aren't possible, agents can long-poll their inbox with configurable timeout.
  • Task lifecycle — Post tasks with descriptions, priorities, and capability requirements. Agents claim, execute, and complete tasks atomically.
  • Broadcast channel — Fleet-wide announcements that reach all registered agents simultaneously.
  • Shared state — Key-value store with versioned updates and optimistic concurrency control. Watch for changes in real time.
  • Agent directory — Discover agents by role, group, capability, or status. Heartbeat monitoring detects stale agents.
  • Server-Sent Events (SSE) — Real-time event stream for messages, task updates, state changes, and agent registrations.
  • Channel isolation — Organize agents into channels for namespace isolation. Messages, tasks, and state are scoped per channel.

Architecture

stonemux runs as a single-process HTTP server on port 3392 (configurable). All coordination state is stored in a local SQLite database with WAL mode. Agents connect via HTTP REST API or MCP transport. No external dependencies, no cloud services, no data leaving your machine.

Anti-proxy protection: stonemux Pro/Enterprise includes agent token authentication. Each agent must prove ownership of a valid license key to obtain a 24-hour TTL token, required on every API call. This prevents a single license from being shared across unauthorized proxies.

2. Installation

macOS (Homebrew)

brew install keystoneproject/tap/stonemux

macOS (Direct Download)

# Apple Silicon (M1/M2/M3/M4)
curl -L https://keystoneproject.dev/releases/stonemux/darwin-aarch64/stonemux-v1.1.0-darwin-aarch64.tar.gz | tar xz
sudo mv stonemux /usr/local/bin/

# Intel
curl -L https://keystoneproject.dev/releases/stonemux/darwin-x86_64/stonemux-v1.1.0-darwin-x86_64.tar.gz | tar xz
sudo mv stonemux /usr/local/bin/

Linux

# x86_64
curl -L https://keystoneproject.dev/releases/stonemux/linux-x86_64/stonemux-v1.1.0-linux-x86_64.tar.gz | tar xz
sudo mv stonemux /usr/local/bin/

# ARM64 (aarch64)
curl -L https://keystoneproject.dev/releases/stonemux/linux-aarch64/stonemux-v1.1.0-linux-aarch64.tar.gz | tar xz
sudo mv stonemux /usr/local/bin/

3. Quick Start

Step 1: Activate

stonemux activate --key SX-XXXX-XXXX-XXXX-XXXX
stonemux status

Step 2: Start

stonemux serve --port 3392

Step 3: Register agents and send messages

# Register two agents
curl -X POST http://localhost:3392/agent/register \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "researcher", "role": "research", "capabilities": ["web-search", "analysis"], "channel": "default"}'

curl -X POST http://localhost:3392/agent/register \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "writer", "role": "writing", "capabilities": ["content-creation"], "channel": "default"}'

# Send a message from researcher to writer
curl -X POST http://localhost:3392/msg/send \
  -H "Content-Type: application/json" \
  -d '{"from": "researcher", "to": "writer", "content": "Analysis complete. Top 3 competitors identified. Ready for report draft."}'

# Writer polls for messages
curl "http://localhost:3392/msg/poll?agent_id=writer&timeout=5"

Step 4: Post and claim a task

# Post a task
curl -X POST http://localhost:3392/task/post \
  -H "Content-Type: application/json" \
  -d '{"description": "Draft competitive analysis report", "posted_by": "researcher", "required_capability": "content-creation", "priority": 5}'

# Writer claims the next available task matching their capabilities
curl -X POST http://localhost:3392/task/claim \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "writer"}'

# Writer completes the task
curl -X POST http://localhost:3392/task/complete \
  -H "Content-Type: application/json" \
  -d '{"task_id": "task-uuid-here", "agent_id": "writer", "result": "Report drafted. 3 pages, executive summary included."}'

4. Configuration

[server]
host = "127.0.0.1"
port = 3392
data_dir = "~/.stonemux"

[license]
key_file = "~/.stonemux/license.key"
license_server = "https://license.keystoneproject.dev"

[coordination]
max_message_size = 65536      # Maximum message content size (bytes)
poll_timeout_max = 120        # Maximum long-poll timeout (seconds)
task_claim_timeout = 300      # Seconds before unclaimed task is re-available
heartbeat_interval = 60       # Agent heartbeat interval (seconds)
stale_threshold = 300         # Seconds without heartbeat before agent marked stale

[events]
sse_keepalive = 15            # SSE keepalive interval (seconds)
max_event_buffer = 1000       # Maximum buffered events per stream

[logging]
level = "info"
format = "json"

5. API Reference

Agent Management

POST /agent/register

Register an agent with the coordination server. Required before sending/receiving messages or claiming tasks.

Request Body

{
  "agent_id": "string",           // Required. Unique identifier.
  "role": "string",               // Required. Agent's role (e.g., "researcher", "writer").
  "capabilities": ["string"],     // Optional. Capabilities for task matching.
  "group": "string",              // Optional. Agent group for broadcast filtering.
  "channel": "string"             // Optional. Channel. Default: "default".
}

Tier Limits

  • Free: 2 agents maximum
  • Pro: Unlimited agents
  • Enterprise: Unlimited agents + audit logging
POST /agent/deregister

Remove an agent from the directory. Pending messages are preserved.

{ "agent_id": "string" }
POST /agent/heartbeat

Send a heartbeat to keep the agent marked as active.

{ "agent_id": "string" }
GET /agent/discover

Discover agents by role, group, status, or channel. Supports filtering stale agents.

Query Parameters

?role=researcher          // Filter by role
&group=analysis-team      // Filter by group
&status=active            // Filter by status (active/stale)
&channel=default          // Filter by channel
&include_stale=false      // Include stale agents (default: false)

Response

{
  "agents": [
    {
      "id": "researcher",
      "role": "research",
      "capabilities": ["web-search", "analysis"],
      "group": "analysis-team",
      "channel": "default",
      "status": "active",
      "registered_at": "2026-06-10T02:00:00Z",
      "last_heartbeat": "2026-06-10T04:58:00Z"
    }
  ]
}

Messaging

POST /msg/send

Send a message to a specific agent's inbox. Triggers doorbell webhook if configured.

Request Body

{
  "from": "string",               // Required. Sender agent ID.
  "to": "string",                 // Required. Recipient agent ID.
  "content": "string",            // Required. Message content.
  "channel": "string"             // Optional. Default: "default".
}
POST /msg/broadcast

Broadcast a message to all agents, optionally filtered by group.

Request Body

{
  "from": "string",               // Required. Sender agent ID.
  "content": "string",            // Required. Broadcast content.
  "group": "string",              // Optional. Target group only.
  "channel": "string"             // Optional. Default: "default".
}

Pro/Enterprise only. Broadcast requires a Pro or Enterprise license.

GET /msg/poll

Long-poll for new messages. Blocks until a message arrives or timeout expires.

Query Parameters

?agent_id=writer          // Required. Agent to poll for.
&timeout=30               // Optional. Timeout in seconds (default: 30, max: 120).

Response

{
  "messages": [
    {
      "id": 42,
      "from": "researcher",
      "to": "writer",
      "content": "Analysis complete...",
      "channel": "default",
      "sent_at": "2026-06-10T04:30:00Z"
    }
  ]
}

Task Coordination

POST /task/post

Post a task to the coordination queue. Tasks can require specific capabilities and carry priority.

Request Body

{
  "description": "string",        // Required. Task description.
  "posted_by": "string",          // Required. Agent posting the task.
  "required_capability": "string",// Optional. Capability required to claim.
  "priority": 5,                  // Optional. Higher = more urgent (default: 0).
  "channel": "string",            // Optional. Default: "default".
  "metadata": {}                  // Optional. Arbitrary JSON metadata.
}
POST /task/claim

Claim the highest-priority unclaimed task matching the agent's capabilities. Atomic — only one agent can claim each task.

{
  "agent_id": "string",           // Required. Claiming agent.
  "channel": "string"             // Optional. Default: "default".
}
POST /task/complete

Mark a claimed task as complete with a result.

{
  "task_id": "string",            // Required. Task UUID.
  "agent_id": "string",           // Required. Must be the agent that claimed it.
  "result": "string",             // Optional. Completion result/summary.
  "status": "string"              // Optional. Default: "complete". Can be "failed".
}
GET /task/list

List tasks, optionally filtered by status and channel.

?status=pending           // Filter: pending, claimed, complete, failed
&channel=default

Shared State

GET /state/get

Get a value from shared state.

?key=deployment-status&channel=ops

Response

{
  "key": "deployment-status",
  "value": "in-progress",
  "version": 3,
  "updated_by": "deploy-agent",
  "updated_at": "2026-06-10T04:45:00Z"
}
POST /state/set

Set a value in shared state. Supports optimistic concurrency via expected_version.

{
  "key": "string",                // Required.
  "value": "string",              // Required.
  "channel": "string",            // Optional. Default: "default".
  "expected_version": 3,          // Optional. Fails if current version doesn't match (CAS).
  "updated_by": "string"          // Optional. Agent making the update.
}

Optimistic concurrency: Set expected_version to the version you read. If another agent updated the key since then, the write fails with a 409 Conflict — read the new value and retry.

GET /state/watch

Long-poll for a state change. Returns when the key is updated past a given version, or on timeout.

?key=deployment-status&channel=ops&after_version=3&timeout=30

Events & Monitoring

GET /events

Server-Sent Events (SSE) stream. Emits events for messages, task updates, state changes, and agent registrations.

Headers

Accept: text/event-stream
Last-Event-ID: 42              // Optional. Resume from event ID.

Event Types

event: message
data: {"from": "researcher", "to": "writer", "content": "..."}

event: task_posted
data: {"task_id": "uuid", "description": "...", "posted_by": "..."}

event: task_claimed
data: {"task_id": "uuid", "agent_id": "writer"}

event: state_changed
data: {"key": "deployment-status", "value": "complete", "version": 4}

event: agent_registered
data: {"agent_id": "new-agent", "role": "analysis"}

Pro/Enterprise only. SSE event streaming requires a Pro or Enterprise license. Free tier agents must use long-poll.

POST /channel/create

Create a named channel for namespace isolation.

{ "name": "string", "created_by": "string" }
GET /channel/list

List all channels.

GET /stats

Server statistics — agent counts, message counts, task counts.

GET /health

Health check. Returns status, version, tier, uptime.

6. MCP Tool Reference

When connected as an MCP server, stonemux exposes coordination tools to your agent:

mux_send

Tool: mux_send
Parameters:
  - from (string, required): Sender agent ID
  - to (string, required): Recipient agent ID
  - content (string, required): Message content
  - channel (string, optional): Channel. Default: "default"

mux_broadcast

Tool: mux_broadcast (Pro/Enterprise)
Parameters:
  - from (string, required): Sender
  - content (string, required): Broadcast content
  - group (string, optional): Target group filter

mux_poll

Tool: mux_poll
Parameters:
  - agent_id (string, required): Agent to check inbox
  - timeout (integer, optional): Seconds to wait (default 30)

mux_task_post

Tool: mux_task_post (Pro/Enterprise)
Parameters:
  - description (string, required): Task description
  - posted_by (string, required): Poster
  - required_capability (string, optional): Skill match
  - priority (integer, optional): Default 0
  - metadata (object, optional): JSON payload

mux_task_claim

Tool: mux_task_claim (Pro/Enterprise)
Parameters:
  - agent_id (string, required): Claiming agent

mux_task_complete

Tool: mux_task_complete (Pro/Enterprise)
Parameters:
  - task_id (string, required): Task UUID
  - agent_id (string, required): Completing agent
  - result (string, optional): Completion result

mux_state_get / mux_state_set

Tool: mux_state_get
Parameters:
  - key (string, required)
  - channel (string, optional)

Tool: mux_state_set
Parameters:
  - key (string, required)
  - value (string, required)
  - expected_version (integer, optional): Optimistic concurrency
  - updated_by (string, optional)

mux_discover

Tool: mux_discover
Parameters:
  - role (string, optional): Filter by role
  - group (string, optional): Filter by group
  - status (string, optional): "active" or "stale"

7. Use Cases by Platform

CrewAI

Structured Crew Communication

Replace CrewAI's built-in delegation with stonemux for persistent, auditable inter-agent messaging with priority routing.

# Manager agent posts a research task
mux_task_post({
  description: "Research Q2 earnings for top 5 tech companies",
  posted_by: "manager",
  required_capability: "financial-research",
  priority: 8,
  metadata: { deadline: "2026-06-15", format: "markdown" }
})

# Researcher agent claims and works
mux_task_claim({ agent_id: "researcher" })
# ... does research ...
mux_task_complete({
  task_id: "task-uuid",
  agent_id: "researcher",
  result: "Report complete. Key findings: ..."
})

# Manager gets notified via SSE events or polling
mux_poll({ agent_id: "manager", timeout: 60 })

Result: Task handoffs are atomic, priority-ordered, and queryable. No lost messages, no race conditions.

LangGraph

Cross-Graph State Sharing

Multiple LangGraph workflows share state through stonemux's versioned key-value store with optimistic concurrency.

# Graph A updates shared state
mux_state_set({
  key: "pipeline-stage",
  value: "extraction-complete",
  updated_by: "graph-a",
  expected_version: 2
})

# Graph B watches for state changes
mux_state_watch({
  key: "pipeline-stage",
  after_version: 2,
  timeout: 60
})
# Returns as soon as Graph A updates

Result: Graphs coordinate without tight coupling. State changes are versioned — no lost updates.

OpenHands

Multi-Agent Code Review Pipeline

Three OpenHands agents — a Coder, a Reviewer, and a Tester — coordinate through stonemux.

# Coder finishes implementation
mux_send({
  from: "coder",
  to: "reviewer",
  content: "PR #42 ready for review. Changes: auth module refactor, 340 LOC."
})

# Reviewer approves and notifies tester
mux_send({
  from: "reviewer",
  to: "tester",
  content: "PR #42 approved. Run integration tests on auth module."
})

# Tester broadcasts results to all
mux_broadcast({
  from: "tester",
  content: "PR #42: 47/47 tests pass. Ready to merge."
})
Hermes / Claude Code

Estate Agent Coordination

Multiple Claude Code instances running as specialized agents coordinate through stonemux instead of tmux send-keys.

# Prime (orchestrator) assigns work to build agent
mux_task_post({
  description: "Build stonemem v1.1.0 release binaries for all platforms",
  posted_by: "prime",
  required_capability: "rust-build",
  priority: 10,
  metadata: { platforms: ["darwin-aarch64", "linux-x86_64", "linux-aarch64"] }
})

# Build agent claims, builds, reports back
mux_task_claim({ agent_id: "build-agent" })
// ... builds ...
mux_task_complete({
  task_id: "...",
  agent_id: "build-agent",
  result: "All 3 platforms built. Binaries at /opt/releases/"
})

# Prime monitors via events stream for real-time updates
Google ADK

Service Mesh Agent Communication

Google ADK agents running across microservices use stonemux channels for service-scoped coordination.

# Create per-service channels
mux_channel_create({ name: "auth-service", created_by: "orchestrator" })
mux_channel_create({ name: "payment-service", created_by: "orchestrator" })

# Auth agent posts to its channel
mux_send({
  from: "auth-monitor",
  to: "payment-agent",
  channel: "payment-service",
  content: "Token refresh rate spiking. Possible credential leak. Check payment webhooks."
})
Haystack

Pipeline Stage Coordination

Haystack pipeline stages running as separate agents hand off work through stonemux tasks.

# Retriever agent posts results as a task for the Reader
mux_task_post({
  description: "Read and summarize retrieved documents",
  posted_by: "retriever",
  required_capability: "summarization",
  metadata: { documents: ["doc-1", "doc-2", "doc-3"], query: "Q2 revenue forecast" }
})
Microsoft Agent Framework

Enterprise Fleet Orchestration

Hundreds of agents across an enterprise coordinate through stonemux with group-based broadcasting and capability-matched task routing.

# HR group broadcast
mux_broadcast({
  from: "hr-lead",
  group: "hr-agents",
  content: "New hire onboarding batch: 15 employees starting Monday. Process initiation required."
})

# IT agent discovers available HR agents
mux_discover({ role: "hr-processor", status: "active" })
// Returns 3 active HR processor agents

# Post individual onboarding tasks — agents self-select based on capabilities
mux_task_post({
  description: "Process onboarding for Employee #E-2026-0891",
  posted_by: "hr-lead",
  required_capability: "onboarding",
  priority: 5,
  metadata: { employee_id: "E-2026-0891", department: "Engineering" }
})

8. Tier Comparison

FeatureFreePro ($14/mo)Enterprise ($59/mo)
Agents2UnlimitedUnlimited
Inbox send/receiveYesYesYes
Long-pollYesYesYes
Doorbell webhooksNoYesYes
Broadcast channelNoYesYes
Task coordinationNoYesYes
Priority routingNoYesYes
SSE event streamingNoYesYes
Shared state (KV)NoYesYes
REST API accessNoNoYes
Webhook forwardingNoNoYes
Audit log exportNoNoYes
Agent token authNoYesYes

9. License Management

Activation

stonemux activate --key SX-XXXX-XXXX-XXXX-XXXX
stonemux status

Agent Token Authentication (Pro/Enterprise)

To prevent license sharing, stonemux Pro/Enterprise requires agents to authenticate with a token:

# 1. Obtain a token (proves license ownership via HMAC)
curl -X POST http://localhost:3392/token/obtain \
  -H "Content-Type: application/json" \
  -d '{"license_key": "SX-XXXX-XXXX-XXXX-XXXX"}'
# Returns: { "token": "eyJ...", "expires_at": "2026-06-11T04:00:00Z" }

# 2. Include token in all API calls
curl -X POST http://localhost:3392/msg/send \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"from": "agent-1", "to": "agent-2", "content": "Hello"}'

Tokens have a 24-hour TTL and must be refreshed. This prevents a single license from being proxied to unauthorized users.

Heartbeat & SIGIL

Same as stonemem — daily heartbeat (telemetry only, doesn't disable), SIGIL die command for key revocation.

10. Troubleshooting

Agent registration fails (402)

Free tier is limited to 2 agents. Deregister an unused agent or upgrade to Pro.

Messages not arriving

  • Verify both agents are registered: GET /agent/discover
  • Check the recipient's agent_id matches exactly (case-sensitive)
  • Verify agents are in the same channel

Task claim returns empty

  • Check for pending tasks: GET /task/list?status=pending
  • Verify the claiming agent's capabilities match the task's required_capability
  • Check if another agent already claimed it

State write conflict (409)

Another agent updated the key since you read it. Read the new version and retry:

// Read current state
let state = mux_state_get({ key: "my-key" })
// Retry set with new version
mux_state_set({ key: "my-key", value: "new", expected_version: state.version })

Getting Help