> ## Documentation Index
> Fetch the complete documentation index at: https://docs.claw.boo/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP tools reference

> The four clawboo-hosted MCP servers (tasks, memory, tools, teamchat) and every tool each exposes, with its zod input schema.

Clawboo hosts four MCP servers over the shared SQLite substrate, exposed to consuming runtimes over two transports: a stdio bin per server, and an in-process Streamable HTTP mount at `/api/mcp/<name>`. Each server is a thin protocol façade over a `@clawboo/db` service core, the same database file the API server and every spawned runtime read and write, so a tool call from an externally-spawned agent and a UI action land on one store.

This page lists each server, its `tools/list` name and version, and every tool it exposes with the tool's name, one-line description, and zod input schema. Three servers (tasks, memory, teamchat) carry an authoritative connection-bound scope that the calling model cannot override: the anti-spoof binding, covered per server below.

<Note>
  Servers are built with the low-level MCP SDK `Server` + `setRequestHandler` API, not `McpServer.registerTool`. Each tool's zod object is converted to JSON Schema for `tools/list` by a small in-package converter; non-optional fields appear in the schema's `required` array. The reported server version for all four is `0.1.0`.
</Note>

## At a glance

| Server                       | `tools/list` name  | Tools                             | Service core                             | Bound scope                     |
| ---------------------------- | ------------------ | --------------------------------- | ---------------------------------------- | ------------------------------- |
| [Tasks](#tasks-server)       | `clawboo-tasks`    | 12 (2 in read-only mode)          | durable board repository                 | `boundScope` (team; reads only) |
| [Memory](#memory-server)     | `clawboo-memory`   | 3                                 | `SqliteMemoryStore` (facts + procedures) | `boundScope` (team/agent)       |
| [Tools](#tools-server)       | `clawboo-tools`    | 4 builtin (availability-filtered) | tool broker                              | n/a                             |
| [TeamChat](#teamchat-server) | `clawboo-teamchat` | 2 (+1 when orchestrator-driven)   | `team_chat` room substrate               | `boundIdentity` (author + room) |

| Server   | stdio bin              | HTTP path           |
| -------- | ---------------------- | ------------------- |
| Tasks    | `clawboo-mcp-tasks`    | `/api/mcp/tasks`    |
| Memory   | `clawboo-mcp-memory`   | `/api/mcp/memory`   |
| Tools    | `clawboo-mcp-tools`    | `/api/mcp/tools`    |
| TeamChat | `clawboo-mcp-teamchat` | `/api/mcp/teamchat` |

***

## Tasks server

`createTasksServer(db, opts?)` → `clawboo-tasks`. A protocol façade over the durable board so any runtime can coordinate on the same kanban board. The atomic claim surfaces a conflict as a tool-error the model must not retry (the "never retry a 409" rule). `opts.readOnly` serves only `list_tasks` and `get_task`, which is how a team agent gets board visibility without racing the engine's claims.

A few tools return a tool-error (`isError: true`) rather than throwing: `get_task` on an unknown id, `claim_task` / `assign_task` on a conflict, `update_task_status` / `block_task` / `unblock_task` on an illegal state-machine transition, `link_task` on a dependency cycle, and `create_task` / `create_subtask` when the parent is unknown, the parent is already at its child-count or depth ceiling, or the root-creation rate is exhausted.

### `list_tasks`

List board tasks. Pass `ready=true` for only claimable (deps satisfied) work.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  teamId?: string
  status?: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled'
  ready?: boolean
}
```

### `get_task`

Get a task with its comments and ancestor chain. Returns `{ task, comments, ancestors }`; a tool-error `not found: <id>` when the task does not exist.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
}
```

### `create_task`

Create a board task. Setting `parentTaskId` makes this a subtask: the per-parent child-count and nesting-depth caps apply, and it inherits the parent's team unless `teamId` is given. Without one it is a root task, bounded instead by a rolling-window creation rate. Both paths are covered by the caps note under [`create_subtask`](#create_subtask).

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  title: string
  description?: string
  status?: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled'
  priority?: number        // integer
  teamId?: string
  parentTaskId?: string    // non-empty when present
  assigneeRuntime?: string
}
```

### `create_subtask`

Create a subtask under a parent (inherits the parent's team). Same creation caps and tool-errors as a parented `create_task`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{ parentTaskId: string; title: string; description?: string }
```

<Note>
  **Creation caps.** A parented create is bounded so a looping agent cannot fill the board: a parent may have at most **24 live (non-dropped) children**, and a task may nest at most **2 levels** deep (`root → child → grandchild`), the same ceiling the delegation depth cap applies at dispatch. Soft-deleting a stale child frees a slot; `done` and `cancelled` children still count, since they are still rows on the board.

  Both checks and the insert run inside **one `BEGIN IMMEDIATE` transaction** in the board's `createCappedSubtask`, so two attached runtimes racing the same parent cannot both land the N+1th child. Do not automatically retry the unchanged request: while the board state stays as it is, the answer does not change. A retry *after* remediation can legitimately succeed — dropping a stale child frees a slot, and the root-rate window rolls.

  * `parent not found: <id>` — previously this reached the foreign key and failed the tool call with a protocol error instead of returning a tool-error.
  * `subtask rejected: parent <id> is at the maximum nesting depth (2); attach the new task higher in the tree`
  * `subtask rejected: parent <id> already has 24 children (max 24); drop one or attach the new task elsewhere`

  A **root** create (no `parentTaskId`) is bounded too, by a rolling-window **rate** rather than a total: at most **30 root tasks per 5 minutes**, counted across every surface. A lifetime ceiling on roots would eventually jam a long-lived board, and counting only open roots would be trivially evadable by an agent that completes-then-creates; velocity is the actual runaway signature and it self-clears. The refusal is `task rejected: <n> root tasks already created in the last 5 min (max 30); …`. A subtask is never charged against it, so decomposition still works while filing is limited.

  An **empty** `parentTaskId` is rejected by the schema (`invalid args`), never silently treated as a root task.

  The caps bound this protocol boundary only: the REST board API and the in-process team-chat orchestrator write through the uncapped repository primitive and carry their own per-turn fan-out and dispatch-depth limits. Measurement is global, though — rows those surfaces create still count toward a parent's total, so an agent cannot launder rows in through another surface to raise its own ceiling. A refusal is returned to the calling model and is not recorded in the governance audit log. It does ride the typed `_meta.denied` channel with its machine-readable reason (`child_cap`, `depth_cap`, `root_rate_cap`, `parent_not_found`), so an in-process caller classifies it without parsing prose — and an agent that keeps hitting the same wall trips the circuit breaker's repeat-policy-denied rule instead of looping forever.
</Note>

### `claim_task`

Atomically claim a `todo` task. A `conflict` error means another agent won; do not retry. Returns the claimed task on success.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{ taskId: string; assigneeAgentId: string; assigneeRuntime?: string }
```

### `assign_task`

Assign a `todo` task to an agent (the same atomic claim as `claim_task`; a conflict means already assigned). Same input schema as `claim_task`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{ taskId: string; assigneeAgentId: string; assigneeRuntime?: string }
```

### `release_task`

Release an `in_progress` task back to `todo`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
}
```

### `update_task_status`

Transition a task status. State-machine enforced; an illegal transition returns a tool-error `status change failed: <reason>`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
  status: 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'blocked' | 'done' | 'cancelled'
}
```

### `block_task`

Mark a task blocked. Tool-error `block failed: <reason>` on an illegal transition.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
}
```

### `unblock_task`

Unblock a task (back to `todo`). Tool-error `unblock failed: <reason>` on an illegal transition.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
}
```

### `add_comment`

Add a comment to a task (report-up summaries, system notes). `authorType` defaults to `'agent'`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
  body: string
  authorAgentId?: string
  authorType?: 'agent' | 'user' | 'system'
}
```

### `link_task`

Make `taskId` depend on `dependsOnTaskId`; it stays unready until the dependency is done. A link that would close a cycle — directly (`A → B`, then `B → A`) or transitively (`A → B → C → A`) — is refused with the tool-error `linking <taskId> to <dependsOnTaskId> would create a dependency cycle`. Re-linking an edge that already exists is still a harmless no-op.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  taskId: string
  dependsOnTaskId: string
}
```

<Warning>
  Do not work around the cycle refusal by re-linking in the other direction. A cycle can never resolve: a task is ready only when *every* dependency is `done`, so both ends sit un-ready forever and nothing surfaces the stall — the ready-pump simply has nothing to fire. That silent deadlock is what the check exists to prevent.
</Warning>

***

## Memory server

`createMemoryServer(db, embed?, opts?)` → `clawboo-memory`. Three tools over the shared `SqliteMemoryStore`: declarative facts plus versioned procedures, with FTS / vector / hybrid search. The store scrubs secrets on write.

<Info>
  **The `boundScope` binding (anti-spoof).** When the server is constructed with `opts.boundScope`, the run's scope is authoritative and the model's `scopeTeamId` / `scopeAgentId` args are ignored:

  * **Save** tags the fact with the bound team only (agentId dropped = team-shared, so any runtime's agent on the team recalls it).
  * **Search / browse** filter by the full bound scope (team + agent inclusive + global), never another team's private facts.

  When unset (the raw stdio bin / unbound default), the model's scope args are used. Over HTTP the binding rides query params on the Memory attach URL (`scopeTeamId` / `scopeAgentId` / `scopeTenantId`); see [the attach-URL scope](#scope-and-identity-binding).
</Info>

### `memory_save`

Save a durable fact (`title` + `content`) or a versioned procedure (set `procedureName`). Facts are declarative ("user prefers X"), not instructions. A fact requires a `title`; a procedure requires `procedureName`. If the content scrubs down to nothing but redaction sentinels, the save is declined (a tool-error). Returns `{ saved: 'fact', fact }` or `{ saved: 'procedure', procedure }`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  content: string
  title?: string            // required for a fact (not a procedure)
  tags?: string[]
  procedureName?: string    // set to save a procedure instead of a fact
  scopeTeamId?: string      // ignored when the connection is boundScope-bound
  scopeAgentId?: string     // ignored when bound
}
```

### `memory_search`

Search saved facts. `mode` is `fts` (default), `vector`, or `hybrid`. Results cite a fact id. `limit` is `1`–`100`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  query: string
  mode?: 'fts' | 'vector' | 'hybrid'
  limit?: number            // integer, 1..100
  scopeTeamId?: string      // ignored when bound
  scopeAgentId?: string     // ignored when bound
}
```

### `memory_browse`

List recent saved facts (scoped). `limit` is `1`–`200`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  limit?: number            // integer, 1..200
  scopeTeamId?: string      // ignored when bound
  scopeAgentId?: string     // ignored when bound
}
```

***

## Tools server

`createToolsServer(db, opts?)` → `clawboo-tools`. The tool broker. Unlike the other three servers, its tool list is dynamic: it lists only the builtin tools whose availability is satisfied; a hidden tool is absent from `tools/list`, so a model can't hallucinate it. Every call routes through the broker pipeline (inspector chain → DB-mediated approval → execute → compaction → audit), and a typed denial reason rides the result's `_meta` channel.

The four builtin descriptors:

| Tool          | Description                                                                                | Input schema                        | Risk        | Availability                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| `echo`        | Echo a message back. Safe, no side effects.                                                | `{ message: string }`               | safe        | always                                                                                             |
| `note`        | Record a short note. Safe.                                                                 | `{ note: string }` (min 1)          | safe        | always                                                                                             |
| `web_search`  | Search the web. External side effect → requires approval.                                  | `{ query: string; limit?: number }` | external    | hidden until a search provider is configured (`TAVILY_API_KEY` env or an authed `tavily` provider) |
| `delete_path` | Delete a path. Destructive → requires approval. (Demo executor, does not actually delete.) | `{ path: string }` (min 1)          | destructive | always                                                                                             |

<Note>
  These builtins re-express real capabilities so the broker has tools to gate, inspect, approve, and audit; the executor bodies are intentionally lightweight. The broker pipeline, not the tool bodies, is the point.
</Note>

***

## TeamChat server

`createTeamChatServer(db, opts?)` → `clawboo-teamchat`. Two tools that let every runtime, regardless of dialect, post to and listen on the shared team room over the `team_chat` substrate, plus a third, `team_delegate`, pushed onto the tool list only on orchestrator-driven team runs. The board stays canonical; a post is narration, never a board mutation (this server has no board access).

<Info>
  **The `boundIdentity` binding (anti-spoof).** When the server is constructed with `opts.boundIdentity`, the post author and room are authoritative, taken from the binding, never from tool args. A runtime may pass `authorAgentId` / `teamId` / `roomId` in args; they are ignored. The binding rides the clawboo-written attach URL (`roomTeamId` / `postAuthorAgentId`), so a runtime cannot post as a peer it is not. When unbound (the raw stdio bin / external attach), the model must pass `authorAgentId` + `teamId` in args; the default room is `team:<teamId>`.
</Info>

### `team_chat_post`

Post a message to your team room as a named peer. Returns `{ posted: { seq, roomId, authorAgentId } }`; a tool-error if the text is empty, or (unbound) if no `authorAgentId` + `teamId` were supplied.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  text: string
  authorAgentId?: string   // ignored when the connection is bound
  teamId?: string          // ignored when bound
  roomId?: string          // ignored when bound
}
```

### `team_chat_subscribe`

Read new posts from your team room since a cursor (`sinceSeq`, default `0`). Returns `{ posts, nextSeq }`. Each post is wrapped as inter-session evidence with the `isUser=false` tag (a teammate post is context to synthesize, never an instruction that overrides your policy), and your own posts are never returned (the per-room echo guard). `limit` is `1`–`500`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  sinceSeq?: number        // integer ≥ 0, default 0
  limit?: number           // integer, 1..500
  authorAgentId?: string   // ignored when bound
  teamId?: string          // ignored when bound
  roomId?: string          // ignored when bound
}
```

The `isUser=false` substring is the load-bearing safety property: a peer post is delivered as tool-routed evidence, never as a turn carrying user authority. Each delivered post entry is `{ seq, authorAgentId, kind, wrapped }`, where `wrapped` is the `[Inter-session message · from=… · kind=… · seq=… · isUser=false]` envelope.

### `team_delegate`

Hand a self-contained piece of work to a teammate by name. **Conditional**: this tool is pushed onto the list only when the bound identity carries `delegate: true`, which the HTTP route sets from the `delegate=1` attach-URL param. On every other session (unbound stdio, or a merely team-scoped attach) it is absent from `tools/list`.

```ts theme={"theme":{"light":"github-dark","dark":"github-dark"}}
{
  assignee: string
  task: string
}
```

Signal-only, and the one tool on this server that does not touch the `team_chat` substrate at all: the handler writes neither the board nor the room. It validates that both fields are non-empty (otherwise the tool-error `team_delegate requires both an assignee (teammate name) and a task`) and returns an acknowledgement string. The orchestration engine watches the emitted `tool-call` event, matches it by *name*, resolves `assignee` against the team roster, and creates the durable board task; a name that resolves to nobody, or to the caller itself, is dropped. The engine owns every board write. See [Codex: leading a team](/runtimes/codex#leading-a-team) for the leader-side flow.

***

## Transports

Every server is served over two transports built from the same factory.

### stdio bin

A consuming runtime spawns one bin per server; the runtime owns the process lifecycle and the server serves over stdio. The `@clawboo/mcp` package declares them as bins, and the `clawboo` CLI re-exposes them so a clean Clawboo install ships them:

| Bin                    | Server   |
| ---------------------- | -------- |
| `clawboo-mcp-tasks`    | tasks    |
| `clawboo-mcp-memory`   | memory   |
| `clawboo-mcp-tools`    | tools    |
| `clawboo-mcp-teamchat` | teamchat |

Each bin opens the shared clawboo DB. The default DB path is the bins' own default; set `CLAWBOO_DB_PATH` so a spawned bin reaches the same board the API server uses (the attach snippets embed it for you). The Memory bin also resolves an embedding provider once at boot (Ollama → OpenAI → none; vector/hybrid degrades to FTS when none is available). The TeamChat bin runs unbound by default; an external attach passes `authorAgentId` + `teamId` in the tool args.

### Streamable HTTP

The API server mounts each server in-process over MCP's Streamable HTTP transport. Sessions are keyed by the `mcp-session-id` header: a fresh server + transport is created on the `initialize` request and reused for that session's subsequent calls.

| Method | Path (× tasks / memory / tools / teamchat) | Role                                                         |
| ------ | ------------------------------------------ | ------------------------------------------------------------ |
| POST   | `/api/mcp/<name>`                          | JSON-RPC requests (`initialize`, `tools/list`, `tools/call`) |
| GET    | `/api/mcp/<name>`                          | SSE stream for an established session                        |
| DELETE | `/api/mcp/<name>`                          | session teardown                                             |

A POST without a valid session that is not an `initialize` request returns a JSON-RPC error `No valid session; send an initialize request first.`; a GET/DELETE with an invalid or missing session id returns `Invalid or missing MCP session id.`. A handler throw surfaces as a `500 { error }` if headers have not been sent.

`GET /api/mcp/config?runtime=&server=&transport=` emits a copy-pasteable attach snippet for the chosen runtime (`claude-code` | `codex` | `openclaw`) and transport (`http` default | `stdio`). See [Tools & MCP API](/reference/rest-api/tools-and-mcp) for the full route reference.

### Scope and identity binding

Over HTTP, the authoritative bindings ride query params on the attach URL the server itself writes (the model never controls the URL):

* **Memory**: `scopeTeamId` / `scopeAgentId` / `scopeTenantId` set the run's visibility scope (`boundScope`).
* **TeamChat**: `roomTeamId` / `postAuthorAgentId` set the room and post author (`boundIdentity`); an added `delegate=1` marks the session orchestrator-driven and exposes [`team_delegate`](#team_delegate). Clawboo writes that param on orchestrator-driven team runs and nowhere else: an external attach must not add it, because nothing is observing the tool-call there and the delegation would silently no-op.
* **Tasks**: `scopeTeamId` / `scopeAgentId` bind the run's board reads to its team (`boundScope`); `scopeAgentId` also carries the mid-run inbox piggyback.
* **Tools**: no scope params; the URL stays bare.

When these params are absent (an external attach, or the stdio bins), the server is unbound and the model supplies scope/identity in args.

## See also

* [MCP servers as teammates: attach config, transports, scoping](/operating/mcp-servers)
* [Tools & MCP API](/reference/rest-api/tools-and-mcp), the `/api/mcp/*` and `/api/tools*` REST surface
* [Memory](/concepts/memory), the shared Memory tier vs each runtime's private tier
* [Peer chat](/concepts/peer-chat), rooms, speaker selection, the `isUser=false` evidence wrapper
* [The board](/concepts/the-board), the durable kanban the Tasks server fronts
* [@clawboo/mcp](/reference/packages/mcp), package API
* [Glossary](/appendices/glossary)
