MCP Tools Reference
CommHub Server provides 17 MCP Tools, called via the POST /mcp (Streamable HTTP) endpoint.
Tool Categories
| Group | Count | Purpose |
|---|---|---|
| Agent-side tools | 4 | Status reporting, message retrieval |
| Task management tools | 7 | Send tasks, reply, retry, cancel, reassign |
| Query tools | 5 | Query task detail, task list, status, completions |
| Broadcast tools | 1 | Broadcast to all agents |
Agent-Side Tools
report_status
Report agent status. Also serves as a heartbeat (recommended every 3 minutes).
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
resume_id | string | ✓ | Session unique identifier (max 200 chars) |
alias | string | ✓ | Display name (max 200 chars) |
status | enum | ✓ | working / idle / blocked / error / waiting_input / offline |
task | string | Current task description (max 10000 chars) | |
output | string | Recent output (max 50000 chars, storage truncated to 4000) | |
score | number | Self-rating 0-10 (doc previously said 1-10; schema is actually .min(0).max(10)) | |
progress | number | Progress 0-100 | |
server | string | Server identifier | |
hostname | string | Hostname | |
agent | string | Agent 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_dir | string | Working directory | |
version | string | Agent version | |
tmux_name | string | tmux session name | |
node_id | string | Stable 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_id — report_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_id | string | Runtime session/thread ID | |
config_path | string | Config file path | |
channels | string | Channel list (JSON array string) | |
model | string | AI model name (written to nodes.model only when node_id is also passed) | |
node_name | string | Node display name (written to nodes.node_name only when node_id is also passed) | |
network_id | string | Network ID |
Response:
{
"ok": true,
"resume_id": "sdk-n_a1b2c3d4",
"alias": "coder-1",
"inbox_count": 3
}Example:
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 atask, transitions the correspondingtasksrow fromdelivered/ackedtorunning(tools.ts:150-153; see Task lifecycle) - When
node_idis passed, upserts thenodestable (includingmodel/node_name/runtime; see thenode_idrow above)
report_completion
Report task completion. Automatically updates session status to idle.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Session alias |
task | string | ✓ | Completed task description |
result | string | ✓ | Result summary (max 50000 chars) |
artifacts | string[] | Output file paths or URLs (max 50) | |
score | number | Self-rating 0-10 | |
duration_minutes | number | Duration (minutes) | |
network_id | string | Network ID |
Response:
{
"ok": true,
"completion_id": "uuid-xxx"
}Example:
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-242UPDATE sessions SET status='idle', task=NULL, progress=0(matched by alias) - Task state transition:
tools.ts:244-266moves thetasksrow fromdelivered/acked/runningtoreplied. First triestask_id = <task param>; on miss it falls back toto_name=<alias> AND content=<task param>— so thetaskparameter can be either the real task_id (preferred) or the task description string (fallback) resulttruncation: only the first 4000 chars are written totasks.result(tools.ts:246); the fullresultstill lands incompletions.result- chained_reply auto-propagation: if the task has a
parent_task_id, the parent's originator gets achained_replySSE event (tools.ts:271-291; used so subtask replies bubble up to the parent — seetask-lifecycledual-write) task_eventslog: arepliedevent 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
Fetch pending messages.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Session alias |
limit | number | Max items (default 10, max 100) |
Response:
{
"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
Acknowledge message receipt. After ACK, the message won't be returned by get_inbox.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Session alias |
message_id | string | ✓ | Message ID |
response | string | Currently 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:
{ "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
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Target agent alias |
task | string | ✓ | Task content (max 10000 chars) |
priority | enum | high / normal (default) / low | |
context | string | Context information (max 10000 chars) | |
from_session | string | Sender identifier (default "hub") | |
ttl_seconds | number | Expiration time (default 3600, max 86400) | |
network_id | string | Network ID | |
parent_task_id | string | Parent task ID; child replies are auto-chained back to the parent task originator |
Response:
{
"ok": true,
"message_id": "uuid-xxx",
"session_status": "idle"
}Example:
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
Send a message (does not trigger AI processing, display only).
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Target agent alias |
message | string | ✓ | Message content (max 10000 chars) |
from_session | string | Sender identifier (default "hub") |
Response:
{
"ok": true,
"message_id": "uuid-xxx",
"session_status": "idle"
}send_reply
Reply to a task. Links to the original task_id and does not trigger the recipient's AI processing.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Target agent alias |
text | string | ✓ | Reply content (max 10000 chars) |
in_reply_to | string | Original task/message ID | |
status | enum | replied (default) / failed / cancelled | |
from_session | string | Sender identifier (default "hub") |
Response:
{
"ok": true,
"message_id": "uuid-xxx",
"session_status": "idle"
}send_ack
Acknowledge task receipt (lightweight, does not enter inbox).
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | ✓ | Task ID |
from_session | string | Sender identifier (default "hub") |
Response:
{
"ok": true,
"task_id": "uuid-xxx",
"updated": 1
}retry_task
Retry a failed/cancelled/expired task.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | ✓ | Task ID |
from_session | string | Sender identifier |
Response:
{
"ok": true,
"task_id": "uuid-xxx",
"retried_to": "coder-1"
}Limitation
- Can only retry tasks with status
failed/expired/cancelled(verifytools.ts:713); other statuses return{ok: false, error: "task status is <X>, not retryable"} - Retry gives the task a fresh
+1 hourTTL (hardcoded attools.ts:718) — the original task'sttl_secondsis not preserved task_idis reused; a new inbox row (new UUID) is inserted and anew_taskSSE event is pushed to the target alias
cancel_task
Cancel a pending task.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | ✓ | Task ID |
reason | string | Cancellation reason (max 1000 chars) | |
from_session | string | Sender identifier |
Response:
{
"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
Reassign a task to another agent.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | ✓ | Task ID |
new_alias | string | ✓ | New target agent alias |
from_session | string | Sender identifier |
Response:
{
"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:853rejectsreplied/failed/cancelled/expiredwith{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_atclears,delivered_atrefreshes to now (tools.ts:863) — arunningtask is interrupted - TTL (
expires_at) is not modified (unlikeretry_taskwhich forces+1 hour); the task keeps its remaining time - The new alias receives a fresh-UUID inbox row + a
new_taskSSE event
Query Tools
get_task
Query task details.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
task_id | string | ✓ | Task ID |
Response:
{
"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
Query task list with multi-dimensional filtering.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | Filter by recipient | |
status | string | Filter by status | |
from_name | string | Filter by sender | |
network_id | string | Filter by network | |
limit | number | Max items (default 20, max 100) |
Response:
{
"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
Get all session statuses. Sessions without a heartbeat for over 10 minutes are auto-marked offline.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
filter_status | string | Filter by status (idle / working / offline) | |
filter_server | string | Filter by server | |
network_id | string | Filter by network |
Response:
{
"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
Get detailed status of a single session, including pending inbox count and recent completions.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | ✓ | Session alias |
Response:
{
"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
sessionisSELECT * FROM sessions(tools.ts:423) — the full sessions row (same asget_all_status's session row, including themodelcolumn — see the get_all_status note); if the alias doesn't existsessionisnullbutokis stilltruerecent_completionsisSELECT * 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 bycompleted_atDESC, max 5
get_completions
Get completion records.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
alias | string | Filter by agent | |
since | string | Start time (ISO 8601, default last 24h) | |
network_id | string | Filter by network | |
limit | number | Max items (default 50, max 500) |
Response:
{
"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
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):
| Parameter | Type | Required | Description |
|---|---|---|---|
message | string | ✓ | Broadcast content (max 10000 chars) |
filter_server | string | Only deliver to sessions whose server field matches | |
filter_status | string | Only deliver to sessions in the given status (e.g. idle / working) | |
network_id | string | Network 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, notcontent;from_sessionis not a parameter — the server hard-codes it to'hub'.
Response:
{
"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:
{
"content": [
{
"type": "text",
"text": "{\"ok\": true, ...}"
}
]
}The text field is a JSON string that needs to be parsed.
Error Codes
| Error | Meaning |
|---|---|
permission_denied | Insufficient permissions (viewer writing, missing writable network binding, etc.) |
license_expired | Trial 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 yours | Message doesn't exist or doesn't belong to this agent |
task not found | Task doesn't exist |
task is terminal | Task is in a terminal state, cannot be operated on |
task status is X, not retryable | Only 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:
- Agent Node — how an agent connects to the MCP server
- Runtimes — all four runtimes talk to the Hub via MCP
- Channel plugins — how to write a custom MCP channel
Hands-on: