Skip to content

MCP Tools Reference

CommHub Server provides 17 MCP Tools, called via the POST /mcp (Streamable HTTP) endpoint.

Tool Categories

GroupCountPurpose
Agent-side tools4Status reporting, message retrieval
Task management tools7Send tasks, reply, retry, cancel, reassign
Query tools5Query task detail, task list, status, completions
Broadcast tools1Broadcast to all agents

Agent-Side Tools

report_status

View source ↗

Report agent status. Also serves as a heartbeat (recommended every 3 minutes).

Parameters:

ParameterTypeRequiredDescription
resume_idstringSession unique identifier (max 200 chars)
aliasstringDisplay name (max 200 chars)
statusenumworking / idle / blocked / error / waiting_input / offline
taskstringCurrent task description (max 10000 chars)
outputstringRecent output (max 50000 chars, storage truncated to 4000)
scorenumberSelf-rating 0-10 (doc previously said 1-10; schema is actually .min(0).max(10))
progressnumberProgress 0-100
serverstringServer identifier
hostnamestringHostname
agentstringAgent type (free-form string for audit; agent-node actually sends agent-node:<runtime> — e.g. agent-node:claude-agent-sdk / agent-node:codex-sdk / agent-node:claude-code-cli; the Claude Code MCP wrapper sends claude-code; other clients fill freely)
project_dirstringWorking directory
versionstringAgent version
tmux_namestringtmux session name
node_idstringStable node identifier. Note: passing node_id is required to upsert model / node_name / runtime (parsed from the agent field) into the nodes table (tools.ts:168-188). The model parameter itself does not depend on node_idreport_status's sessions upsert unconditionally writes sessions.model = COALESCE(model, old) (tools.ts:129 INSERT + tools.ts:141 ON CONFLICT); only node_name has no sessions column and must go through the nodes table via node_id.
session_idstringRuntime session/thread ID
config_pathstringConfig file path
channelsstringChannel list (JSON array string)
modelstringAI model name (written to nodes.model only when node_id is also passed)
node_namestringNode display name (written to nodes.node_name only when node_id is also passed)
network_idstringNetwork ID

Response:

json
{
  "ok": true,
  "resume_id": "sdk-n_a1b2c3d4",
  "alias": "coder-1",
  "inbox_count": 3
}

Example:

typescript
report_status({
  resume_id: "sdk-n_a1b2c3d4",
  alias: "coder-1",
  status: "working",
  task: "Writing sorting algorithm",
  progress: 50,
  model: "your-model-id",
  agent: "agent-node:codex"
})

Authentication required

This tool only accepts a ntok_ (network-scoped) token. Calling with utok_ (user-scoped) returns {ok: false, error: "network_token_required"} (tools.ts:116-118). This is a hard constraint after RFC-001 in v0.8 — agent heartbeats must be bound to a network.

Side effects beyond the sessions table:

  • Automatically DELETEs any older session row with the same network + alias + a different resume_id (tools.ts:127; cleans up orphans across agent restarts)
  • When status="working" with a task, transitions the corresponding tasks row from delivered/acked to running (tools.ts:150-153; see Task lifecycle)
  • When node_id is passed, upserts the nodes table (including model / node_name / runtime; see the node_id row above)

report_completion

View source ↗

Report task completion. Automatically updates session status to idle.

Parameters:

ParameterTypeRequiredDescription
aliasstringSession alias
taskstringCompleted task description
resultstringResult summary (max 50000 chars)
artifactsstring[]Output file paths or URLs (max 50)
scorenumberSelf-rating 0-10
duration_minutesnumberDuration (minutes)
network_idstringNetwork ID

Response:

json
{
  "ok": true,
  "completion_id": "uuid-xxx"
}

Example:

typescript
report_completion({
  alias: "coder-1",
  task: "Write sorting algorithm",
  result: "Implemented with quicksort, O(n log n) time complexity",
  artifacts: ["/tmp/sort.py"],
  score: 8,
  duration_minutes: 2
})

Side effects (beyond the completions INSERT)

  • Session state flip: tools.ts:239-242 UPDATE sessions SET status='idle', task=NULL, progress=0 (matched by alias)
  • Task state transition: tools.ts:244-266 moves the tasks row from delivered/acked/running to replied. First tries task_id = <task param>; on miss it falls back to to_name=<alias> AND content=<task param> — so the task parameter can be either the real task_id (preferred) or the task description string (fallback)
  • result truncation: only the first 4000 chars are written to tasks.result (tools.ts:246); the full result still lands in completions.result
  • chained_reply auto-propagation: if the task has a parent_task_id, the parent's originator gets a chained_reply SSE event (tools.ts:271-291; used so subtask replies bubble up to the parent — see task-lifecycle dual-write)
  • task_events log: a replied event is logged (tools.ts:270)

Compared to send_reply: send_reply is a hub tool that requires an explicit task_id; report_completion is an agent tool that can fall back by content match.


get_inbox

View source ↗

Fetch pending messages.

Parameters:

ParameterTypeRequiredDescription
aliasstringSession alias
limitnumberMax items (default 10, max 100)

Response:

json
{
  "ok": true,
  "messages": [
    {
      "id": "uuid-xxx",
      "type": "task",
      "priority": "high",
      "content": "Write sorting algorithm",
      "context": null,
      "from_session": "commander",
      "created_at": "2026-04-12 10:00:00",
      "network_id": "net_xxx"
    }
  ]
}

Messages are sorted by priority: high > normal > low, then by time within the same priority.


ack_inbox

View source ↗

Acknowledge message receipt. After ACK, the message won't be returned by get_inbox.

Parameters:

ParameterTypeRequiredDescription
aliasstringSession alias
message_idstringMessage ID
responsestringCurrently a no-op: the handler accepts this parameter but never writes it to the database (tools.ts:337-360 never references response). The schema is kept for forward-compat / to avoid breaking existing callers; if you want to actually reply, use send_reply.

Response:

json
{ "ok": true }

Error: message_id not found or not owned by this alias → {ok: false, error: "message not found or not yours"}.

Side effect: tasks-table state machine

A successful ack also UPDATEs the tasks row where task_id = message_id from status='delivered' to 'acked' (tools.ts:354; only transitions from delivered, unlike the hub-side send_ack which also accepts created — see Task lifecycle — the created state).


Task Management Tools

send_task

View source ↗

Dispatch a task to a specified agent's inbox. send_task triggers AI processing on the receiver (same as broadcast; send_message / send_reply / send_ack do not — see Task lifecycle — Message types).

Parameters:

ParameterTypeRequiredDescription
aliasstringTarget agent alias
taskstringTask content (max 10000 chars)
priorityenumhigh / normal (default) / low
contextstringContext information (max 10000 chars)
from_sessionstringSender identifier (default "hub")
ttl_secondsnumberExpiration time (default 3600, max 86400)
network_idstringNetwork ID
parent_task_idstringParent task ID; child replies are auto-chained back to the parent task originator

Response:

json
{
  "ok": true,
  "message_id": "uuid-xxx",
  "session_status": "idle"
}

Example:

typescript
send_task({
  alias: "coder-1",
  task: "Write a Python quicksort algorithm with comments",
  priority: "high",
  from_session: "commander",
  ttl_seconds: 7200
})

Permission Requirements

  • viewer role cannot send tasks
  • Cannot send tasks after trial expires

send_message

View source ↗

Send a message (does not trigger AI processing, display only).

Parameters:

ParameterTypeRequiredDescription
aliasstringTarget agent alias
messagestringMessage content (max 10000 chars)
from_sessionstringSender identifier (default "hub")

Response:

json
{
  "ok": true,
  "message_id": "uuid-xxx",
  "session_status": "idle"
}

send_reply

View source ↗

Reply to a task. Links to the original task_id and does not trigger the recipient's AI processing.

Parameters:

ParameterTypeRequiredDescription
aliasstringTarget agent alias
textstringReply content (max 10000 chars)
in_reply_tostringOriginal task/message ID
statusenumreplied (default) / failed / cancelled
from_sessionstringSender identifier (default "hub")

Response:

json
{
  "ok": true,
  "message_id": "uuid-xxx",
  "session_status": "idle"
}

send_ack

View source ↗

Acknowledge task receipt (lightweight, does not enter inbox).

Parameters:

ParameterTypeRequiredDescription
task_idstringTask ID
from_sessionstringSender identifier (default "hub")

Response:

json
{
  "ok": true,
  "task_id": "uuid-xxx",
  "updated": 1
}

retry_task

View source ↗

Retry a failed/cancelled/expired task.

Parameters:

ParameterTypeRequiredDescription
task_idstringTask ID
from_sessionstringSender identifier

Response:

json
{
  "ok": true,
  "task_id": "uuid-xxx",
  "retried_to": "coder-1"
}

Limitation

  • Can only retry tasks with status failed / expired / cancelled (verify tools.ts:713); other statuses return {ok: false, error: "task status is <X>, not retryable"}
  • Retry gives the task a fresh +1 hour TTL (hardcoded at tools.ts:718) — the original task's ttl_seconds is not preserved
  • task_id is reused; a new inbox row (new UUID) is inserted and a new_task SSE event is pushed to the target alias

cancel_task

View source ↗

Cancel a pending task.

Parameters:

ParameterTypeRequiredDescription
task_idstringTask ID
reasonstringCancellation reason (max 1000 chars)
from_sessionstringSender identifier

Response:

json
{
  "ok": true,
  "task_id": "uuid-xxx",
  "cancelled": true
}

Constraint

Only cancellable from these 4 source statuses: created / delivered / acked / running (verify the WHERE clause at tools.ts:817). Calling on a terminal status (replied / failed / cancelled / expired) returns {ok: false, cancelled: false}.

created is only the DB column default; the normal API path never produces a row in that state (see Task lifecycle — the created state).


reassign_task

View source ↗

Reassign a task to another agent.

Parameters:

ParameterTypeRequiredDescription
task_idstringTask ID
new_aliasstringNew target agent alias
from_sessionstringSender identifier

Response:

json
{
  "ok": true,
  "task_id": "uuid-xxx",
  "reassigned_from": "coder-1",
  "reassigned_to": "coder-2"
}

Constraint

  • Reassign works only on non-terminal tasks: created / delivered / acked / running (tools.ts:853 rejects replied / failed / cancelled / expired with {ok: false, error: "task is terminal (<status>)"})
  • The old alias's inbox row is acked=1 (tools.ts:858) so the original agent will not pick it up
  • Task status resets to delivered, started_at clears, delivered_at refreshes to now (tools.ts:863) — a running task is interrupted
  • TTL (expires_at) is not modified (unlike retry_task which forces +1 hour); the task keeps its remaining time
  • The new alias receives a fresh-UUID inbox row + a new_task SSE event

Query Tools

get_task

View source ↗

Query task details.

Parameters:

ParameterTypeRequiredDescription
task_idstringTask ID

Response:

json
{
  "ok": true,
  "task": {
    "task_id": "uuid-xxx",
    "from_name": "commander",
    "to_name": "coder-1",
    "priority": "normal",
    "status": "replied",
    "content": "Write sorting algorithm",
    "result": "Implemented with quicksort...",
    "created_at": "2026-04-12 10:00:00",
    "delivered_at": "2026-04-12 10:00:01",
    "started_at": "2026-04-12 10:00:03",
    "completed_at": "2026-04-12 10:00:15",
    "expires_at": "2026-04-12 11:00:00",
    "network_id": "net_xxx"
  }
}

get_task does SELECT * FROM tasks (tools.ts:749) and returns the full row (the example above shows sample fields; the actual row also includes requires_response / parent_task_id and every other column). When the task doesn't exist it returns {ok: false, error: "task not found"}.


list_tasks

View source ↗

Query task list with multi-dimensional filtering.

Parameters:

ParameterTypeRequiredDescription
aliasstringFilter by recipient
statusstringFilter by status
from_namestringFilter by sender
network_idstringFilter by network
limitnumberMax items (default 20, max 100)

Response:

json
{
  "ok": true,
  "tasks": [
    {
      "task_id": "uuid-xxx",
      "from_name": "commander",
      "to_name": "coder-1",
      "priority": "normal",
      "status": "replied",
      "content": "Write a sorting algorithm",
      "result": "Implemented with quicksort...",
      "created_at": "2026-04-12 10:00:00",
      "completed_at": "2026-04-12 10:00:15"
    }
  ],
  "count": 1,
  "stats": [
    { "status": "replied", "count": 42 },
    { "status": "running", "count": 3 },
    { "status": "delivered", "count": 1 }
  ]
}

list_tasks rows are a subset of get_task

Each list_tasks row SELECTs only 9 columns (tools.ts:776): task_id / from_name / to_name / priority / status / content / result / created_at / completed_at. It does not include delivered_at / started_at / expires_at / network_id / requires_response / parent_task_id — use get_task (SELECT *) for those. count is the number of rows returned this call (≤ limit); stats is the status-grouped count for the entire scope (not affected by the filters).


get_all_status

View source ↗

Get all session statuses. Sessions without a heartbeat for over 10 minutes are auto-marked offline.

Parameters:

ParameterTypeRequiredDescription
filter_statusstringFilter by status (idle / working / offline)
filter_serverstringFilter by server
network_idstringFilter by network

Response:

json
{
  "ok": true,
  "sessions": [
    {
      "resume_id": "sdk-n_xxx",
      "alias": "coder-1",
      "status": "idle",
      "agent": "agent-node:codex",
      "node_id": "n_a1b2c3d4",
      "last_seen_at": "2026-04-12 10:00:00",
      "network_id": "net_xxx"
    }
  ],
  "summary": [
    { "status": "idle", "count": 5 },
    { "status": "working", "count": 2 },
    { "status": "offline", "count": 1 }
  ]
}

The sessions row has no model field

get_all_status runs SELECT * FROM sessions (tools.ts:388, no JOIN). The sessions table schema (db.ts:7-26 + V2 migration db.ts:59-68) has a model column — the V2 migration runs ALTER TABLE sessions ADD COLUMN model, and report_status's sessions upsert unconditionally writes sessions.model = COALESCE(model, old) (tools.ts:129/141). So get_all_status returns each session's model directly (null if the agent never passed a model parameter). The nodes table also keeps a copy of model (synced by report_status when node_id is passed) as the more durable source. summary is the status-grouped count over the entire scope (same as list_tasks's stats).


get_session_status

View source ↗

Get detailed status of a single session, including pending inbox count and recent completions.

Parameters:

ParameterTypeRequiredDescription
aliasstringSession alias

Response:

json
{
  "ok": true,
  "session": {
    "resume_id": "sdk-n_xxx", "alias": "coder-1", "status": "idle",
    "agent": "agent-node:codex", "node_id": "n_a1b2c3d4",
    "last_seen_at": "2026-04-12 10:00:00", "network_id": "net_xxx"
  },
  "inbox_pending": 2,
  "recent_completions": [
    {
      "id": "uuid-xxx",
      "session_name": "coder-1",
      "task": "Write sorting algorithm",
      "result": "Done",
      "artifacts": null,
      "score": 8,
      "duration_minutes": 2,
      "network_id": "net_xxx",
      "completed_at": "2026-04-12 10:00:15"
    }
  ]
}

Response shape

  • session is SELECT * FROM sessions (tools.ts:423) — the full sessions row (same as get_all_status's session row, including the model column — see the get_all_status note); if the alias doesn't exist session is null but ok is still true
  • recent_completions is SELECT * FROM completions ... LIMIT 5 (tools.ts:433-435) — the full 9-column completion row (id / session_name / task / result / artifacts / score / duration_minutes / network_id / completed_at), ordered by completed_at DESC, max 5

get_completions

View source ↗

Get completion records.

Parameters:

ParameterTypeRequiredDescription
aliasstringFilter by agent
sincestringStart time (ISO 8601, default last 24h)
network_idstringFilter by network
limitnumberMax items (default 50, max 500)

Response:

json
{
  "ok": true,
  "completions": [
    {
      "id": "uuid-xxx",
      "session_name": "coder-1",
      "task": "Write sorting algorithm",
      "result": "Implemented with quicksort...",
      "artifacts": "[\"/tmp/sort.py\"]",
      "score": 8,
      "duration_minutes": 2,
      "network_id": "net_xxx",
      "completed_at": "2026-04-12 10:00:15"
    }
  ]
}

completions is SELECT * FROM completions WHERE completed_at >= <cutoff> (tools.ts:938) — the full 9-column row, ordered by completed_at DESC. artifacts is a JSON-array string (not a parsed array — report_completion JSON.stringifys it on the way in). When since is omitted the cutoff defaults to 24 hours ago.


Broadcast Tools

broadcast

View source ↗

Broadcast a message to all online agents. broadcast triggers AI processing on receivers, the same as task (agent-node/src/cli.ts thinks only on task and broadcast types; reply / message / ack are display-only). If you just want a notification without an AI reply, loop send_message instead. Full message-type table: Task lifecycle — Message types.

Parameters (verify server/src/tools.ts:880-885):

ParameterTypeRequiredDescription
messagestringBroadcast content (max 10000 chars)
filter_serverstringOnly deliver to sessions whose server field matches
filter_statusstringOnly deliver to sessions in the given status (e.g. idle / working)
network_idstringNetwork ID (broadcast within this network only; can be supplied with a utok_, but ntok_ callers are pinned to their bound network)

The field is message, not content; from_session is not a parameter — the server hard-codes it to 'hub'.

Response:

json
{
  "ok": true,
  "recipients": 10,
  "message_ids": ["uuid-xxx-1", "uuid-xxx-2"]
}

message_ids.length === recipients — one inbox row per target session.


Common Response Format

All tools return in MCP Content format:

json
{
  "content": [
    {
      "type": "text",
      "text": "{\"ok\": true, ...}"
    }
  ]
}

The text field is a JSON string that needs to be parsed.

Error Codes

ErrorMeaning
permission_deniedInsufficient permissions (viewer writing, missing writable network binding, etc.)
license_expiredTrial period expired (v0.6 legacy path; not needed after Apache 2.0 OSS — when hit, follow the license_expired section in troubleshooting to clear the SQLite licenses table)
message not found or not yoursMessage doesn't exist or doesn't belong to this agent
task not foundTask doesn't exist
task is terminalTask is in a terminal state, cannot be operated on
task status is X, not retryableOnly failed/expired/cancelled tasks can be retried

Next steps

Corresponding REST API:

  • REST API — the HTTP endpoints these MCP tools call under the hood

Agent integration:

Hands-on:

Powered by Sleep2AGI