   
                          
                                                                                                         
                
   
<!-- Built from the MCP server's tool table (backend/src/mcp/server.ts), the write permissions in backend/src/mcp/server-routes.ts and the permission names in webapp/src/product/i18n/i18n.ts by scripts/docs/mcp-reference.ts. Change the wording there and run `bun run --cwd website docs:reference`; edits made here are overwritten. -->

# MCP tools reference

Legevio's MCP server lets the AI you already use read your project and hand work back. Each tool below is listed with the permission it needs, in the words the server itself sends to your AI client. Your client only sees the tools its permissions allow.

For setup, see [Connect your AI](/start/connect-your-ai/). For what a task's context contains, see [What the AI receives](/concepts/context/).

## How the server talks

- One HTTPS endpoint speaks JSON-RPC 2.0. Each request is a single POST; there is no session to keep open.
- Protocol version `2025-06-18`; the server also accepts `2025-03-26`, `2024-11-05`. Server name `legevio`.
- A batch holds at most 20 messages. A larger batch is refused whole, and nothing in it runs.
- A tool's reply is compact JSON in the first text part. When there is more to say (a page was cut, a hint, a note about a hand-in), it comes in a second text part.
- A failed lookup or a refused write is a normal reply marked as an error, with a sentence saying why. Only a malformed request or an unknown method is a protocol error.
- Permissions are checked again on every message, so a revoked connection stops working at once, even inside a batch.

## Permissions

| Permission | In Settings | What it allows | Tools |
| --- | --- | --- | --- |
| `read` | Read project context | Read your tasks, Project Memory, Context Packs, and the project binder. | `list_projects`, `list_tasks`, `get_task`, `get_week`, `get_project_memory`, `list_team`, `get_project_state`, `list_activity` |
| `tasks.manage` | Create and update tasks | Create and update tasks and the project state page directly without an approval step. Task changes are recorded in the work log; every version of the state page is kept. This does not run AI or approve Project Memory. | `update_project_state`, `submit_result`, `propose_task`, `propose_task_update`, `propose_task_delete`, `create_task`, `update_task` |
| `drafts.create` | Create drafts | Propose content as AI Drafts that land in your Inbox for review. | `submit_result`, `create_draft` |
| `messages.write` | Post messages | Send chat messages into Inbox threads with MCP attribution. Messages change no tasks, roles, or Project Memory. | `post_message` |
| `team.manage` | Propose team roles | Propose new team roles — each lands in your Inbox for approval; you add it, the client can't add roles directly. | `propose_role`, `propose_composition` |

Every connection needs `read`. `submit_result` needs both `drafts.create` and `tasks.manage`. No permission lets an outside AI accept anything into Project Memory: a result always goes to a person, or to an acceptance rule a project owner or admin turned on.

## Working rules the server sends

When your client connects, the server sends these rules with its reply. Many clients add them to the model's instructions. A connection with fewer permissions gets only the rules that apply to it.

> Legevio holds this team's tasks and its accepted Project Memory.
>
> - Before working on a task, read it with get_task. Its brief and context pack are the current inputs; prefer them over earlier copies or your own notes.
>
> - get_project_memory returns only what people have accepted. Treat it as the project's source of truth.
>
> - Start each session with get_project_state: where the project stands and what is next. A working page, not Project Memory.
>
> - Before you stop, update it with update_project_state from its revision.
>
> - Hand in with submit_result: draft, task to Review and git references in one call. Pass that read's revision as expectedRevision and its contextReceiptId (null if it carried contextReceiptUnavailable or no pack), a document and, when one fits, a collection from list_projects.
>
> - A result goes to a person for review. It is not accepted Project Memory until they approve it; never say it is.
>
> - For other task changes, pass the revision from get_task as expectedRevision to update_task.
>
> - Give each keyed write a stable idempotencyKey and retry a lost response with the same key. A used key is refused naming what it made; list_activity with mine: true lists your earlier hand-ins.

## Read tools

| Tool | Needs | What it does |
| --- | --- | --- |
| [`list_projects`](#list_projects) | `read` | List the projects in this workspace with their goal, phase, status and collections (the sections of Project Memory a result can be filed under). |
| [`list_tasks`](#list_tasks) | `read` | List tasks (board state), optionally limited to one project. |
| [`get_task`](#get_task) | `read` | Get one task by id or by its board number (pass taskId or number), including its current brief and permitted context pack for its assigned role. |
| [`get_week`](#get_week) | `read` | Read the Tasks Week, Monday to Sunday: each day has the tasks due that day (the plan) and the stretches In progress that day (who, local time, minutes; one still In progress ends "now"), with daily and weekly minutes for people and AI, plus open tasks with no date yet. |
| [`get_project_memory`](#get_project_memory) | `read` | Read approved Project Memory (Approved/Canon only — never drafts pending review) and the accepted text of the project documents (Overview, Context, Decisions, Notes, Asset rights), optionally filtered to one project. |
| [`list_team`](#list_team) | `read` | List the team of each project, optionally one project: its roles (id, name, role, ai or human; a seat taken by a person names that person as memberUserId; model, effort and rank when the role asks its tasks to run on a model; level and xp from the accepted tasks it holds) and the people who can open it (userId, display name, project role, and you: true for yourself). |
| [`get_project_state`](#get_project_state) | `read` | Read the project's state page: where it stands, what is in progress, what comes next, what waits for the owner, risks and working rules. |
| [`list_activity`](#list_activity) | `read` | List recent activity newest first: work-log events and MCP hand-ins (a result or draft sent through MCP) with what a person decided about each (reviewed, revision requested with its follow-up task and a preview of the comment, approved, rejected, saved to Project Memory; accepted by the acceptance rule of the project, or returned to review when a person undid that; an older version closed when a newer version of the same task was accepted). |

### `list_projects`

Needs: `read`

List the projects in this workspace with their goal, phase, status and collections (the sections of Project Memory a result can be filed under).

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |

### `list_tasks`

Needs: `read`

List tasks (board state), optionally limited to one project. order: "desc" puts the newest tasks first (default: ascending task number); updatedSince keeps only tasks changed after a moment. Narrow with status (one or several), number (one or several), q (words in the title, brief or description, in any order), parentTaskId or tag; choose columns with fields (id, projectId and workspaceId always come back; revision lets you update a task without reading it; brief adds each brief); page with limit (max 200) and offset. parentTaskId and blockedByTaskIds come with parentNumber and blockedByNumbers. When a page cuts the list, a second text part gives the total and the next offset. An unknown value is refused with the accepted ones. An unfiltered large board is big: filter and limit.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `status` | string or array of string |  | Keep only these statuses: one, or several. Values: `backlog`, `ready_for_ai`, `in_progress`, `blocked`, `review`, `approved`, `done`. |
| `number` | integer or array of integer |  | Project-local task numbers (#12 is 12): one, or several. Limits: up to 200 items. |
| `q` | string |  | Only tasks whose title, brief or description contains every one of these words, in any order (case-insensitive). Limits: up to 200 characters. |
| `parentTaskId` | string |  | Only direct subtasks of this task. Limits: up to 200 characters. |
| `tag` | string |  | Only tasks carrying this tag (case-insensitive). Limits: up to 48 characters. |
| `fields` | array of string |  | Columns to return besides id, projectId and workspaceId, which always come back. Add revision to update a task without reading it. Values: `number`, `boardOrder`, `tags`, `dueDate`, `parentTaskId`, `blockedByTaskIds`, `title`, `status`, `priority`, `type`, `assigneeType`, `updatedAt`, `revision`, `brief`. |
| `limit` | integer |  | Page size. When a page cuts the list, a second text part gives the total and the next offset. Limits: 1–200. |
| `offset` | integer |  | Rows to skip (paging). |
| `order` | string |  | By task number: asc (default) or desc, the newest tasks first. Unnumbered tasks come last either way. Values: `asc`, `desc`. |
| `updatedSince` | string |  | Only tasks changed after this moment (ISO 8601 with a time zone). |

### `get_task`

Needs: `read`

Get one task by id or by its board number (pass taskId or number), including its current brief and permitted context pack for its assigned role. Context is read now, not a historical run snapshot; file originals are not included. In a large project the pack carries in full only the accepted records this task names or matches; the others are listed in contextPack.memoryIndex by id, title and one line, and get_project_memory with ids returns their text. When your token can submit drafts, the response carries contextReceiptId: submit it with the draft you build from this context, so Legevio can flag that result if these inputs change later. When Legevio cannot record this context (for example, the project knowledge history is not active yet), it carries contextReceiptUnavailable with the reason instead. Pass includeContextPack: false to reread status or revision, or the brief alone, without the pack: that read hands out no context and carries neither contextReceiptId nor contextReceiptUnavailable. When the assigned role asks for a model, assigneeRole names it with its effort and rank: run the task, or the subagent you hand it to, on that model at that effort.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `taskId` | string |  | The task id. Pass this or number. |
| `number` | integer |  | The project-local task number (#40 is 40). Pass this or taskId; with several projects, add projectId. |
| `includeContextPack` | boolean |  | Default true. false returns the task without its context pack: brief, status, revision and the other fields only. No context is handed out, so the response carries neither contextReceiptId nor contextReceiptUnavailable; when you submit a result, pass the receipt of the full read you worked from. |
| `format` | string |  | agent_brief returns the task as plain text to hand a subagent as its prompt, at most 3000 characters: the brief, parent and blockers, the accepted decisions and records it names, and the project state. No context pack and no receipt. Values: `agent_brief`. |

### `get_week`

Needs: `read`

Read the Tasks Week, Monday to Sunday: each day has the tasks due that day (the plan) and the stretches In progress that day (who, local time, minutes; one still In progress ends "now"), with daily and weekly minutes for people and AI, plus open tasks with no date yet. A stretch is how long a task sat in In progress, nights included, not hours worked; the minutes add stretches up, so overlapping ones can pass 24 hours a day. To put a task on a day, call update_task with patch { dueDate: "YYYY-MM-DD" } (null clears it) and the revision from here.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `week` | date (YYYY-MM-DD) |  | Any day of the week to read (YYYY-MM-DD). Default: this week in timeZone. |
| `timeZone` | string |  | IANA time zone where days begin and end for the stretches In progress, for example Europe/Moscow. Default UTC. Due dates carry no time zone. Limits: up to 64 characters. |
| `assigneeId` | string |  | Only this role's or person's plan and work (a role id from list_team, or a person's user id). Limits: up to 200 characters. |

### `get_project_memory`

Needs: `read`

Read approved Project Memory (Approved/Canon only — never drafts pending review) and the accepted text of the project documents (Overview, Context, Decisions, Notes, Asset rights), optionally filtered to one project. A record filed in a document names it. Read less with document (that document and the records filed in it), collection (the records placed in one collection, by id or name as list_projects shows it), ids (only those records, such as the ones get_task lists in contextPack.memoryIndex), includeContent: false (titles and summaries only), limit and offset. When a page cuts the list, a second text part gives the total and the next offset.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `document` | string |  | Only this project document and the records filed in it: overview, context, decisions, notes or legal (Asset rights). Values: `overview`, `context`, `decisions`, `notes`, `legal`. |
| `collection` | string |  | Only the records placed in this collection, by its id or name as list_projects shows it. An unknown collection is refused with the collections there are. Limits: up to 200 characters. |
| `includeContent` | boolean |  | Default true. false leaves out the text: ids, titles, summaries and where each is filed. |
| `ids` | array of string |  | Only these accepted records, by id: for example the ones a task lists in contextPack.memoryIndex. Documents are left out. Limits: up to 50 items. |
| `limit` | integer |  | Page size. Limits: 1–200. |
| `offset` | integer |  | Entries to skip (paging). |

### `list_team`

Needs: `read`

List the team of each project, optionally one project: its roles (id, name, role, ai or human; a seat taken by a person names that person as memberUserId; model, effort and rank when the role asks its tasks to run on a model; level and xp from the accepted tasks it holds) and the people who can open it (userId, display name, project role, and you: true for yourself). Use these ids to assign a task or post to a role. Archived roles are left out.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |

### `get_project_state`

Needs: `read`

Read the project's state page: where it stands, what is in progress, what comes next, what waits for the owner, risks and working rules. Read it at the start of a session instead of earlier notes. It is the team's working page that people and their AI keep current, not accepted Project Memory. Returns content (at most 5000 characters), revision (0 while empty; pass it to update_project_state), who last wrote it and when, canEdit (whether this token may update it), and taskCounts (the project's tasks by status).

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The project, from list_projects. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |

### `list_activity`

Needs: `read`

List recent activity newest first: work-log events and MCP hand-ins (a result or draft sent through MCP) with what a person decided about each (reviewed, revision requested with its follow-up task and a preview of the comment, approved, rejected, saved to Project Memory; accepted by the acceptance rule of the project, or returned to review when a person undid that; an older version closed when a newer version of the same task was accepted). A hand-in entry carries taskId, draftId, draftStatus and mine (true for this token's own hand-ins, which also carry their idempotencyKey); never the draft text. Filter with mine: true and taskId. A second text part gives a cursor: pass it as since to get only what came after it, oldest first, and keep passing the returned cursor to continue. limit: default 20, max 100.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `limit` | integer |  | Max entries (default 20, max 100). |
| `since` | string |  | The cursor from a previous list_activity response: return only entries after it, oldest first. Limits: up to 200 characters. |
| `mine` | boolean |  | Only this token's own hand-ins and what was decided about them. |
| `taskId` | string |  | Only entries about this task. Limits: up to 200 characters. |

## Write tools

| Tool | Needs | What it does |
| --- | --- | --- |
| [`create_task`](#create_task) | `tasks.manage` | Create a reversible task directly on the project board. |
| [`update_task`](#update_task) | `tasks.manage` | Update ordinary task fields directly. |
| [`update_project_state`](#update_project_state) | `tasks.manage` | Rewrite the project's state page with the whole new text (at most 5000 characters): where it stands, what is in progress, what comes next, what waits for the owner, risks, working rules. |
| [`submit_result`](#submit_result) | `drafts.create` + `tasks.manage` | Hand in a finished task in one call: your result as an AI Draft in the Inbox, the task moved to Review, and your git references (commits, branch, tests, pull request, links), shown to the reviewer as reported by you, not verified. |
| [`create_draft`](#create_draft) | `drafts.create` | Propose content as an AI Draft for a task. |
| [`propose_task`](#propose_task) | `tasks.manage` | Optionally propose a private task for human review in Inbox. |
| [`post_message`](#post_message) | `messages.write` | Post a message into a project participant's Inbox thread, optionally anchored to a task. |
| [`propose_task_update`](#propose_task_update) | `tasks.manage` | Optionally propose changes to a private task for human review as an Inbox diff. |
| [`propose_task_delete`](#propose_task_delete) | `tasks.manage` | Propose deleting an EXISTING task. |
| [`propose_role`](#propose_role) | `team.manage` | Propose a team role for a project. |
| [`propose_composition`](#propose_composition) | `team.manage` | Propose a whole team composition for a project — the set of roles (seats) it needs, e.g. |

### `create_task`

Needs: `tasks.manage`

Create a reversible task directly on the project board. Requires tasks.manage, current project write access, an explicit workspaceId (null for private), and a stable idempotencyKey. Retry the exact same command with the same key after a lost response. Returns a committed acknowledgement; does not run AI or accept Project Memory. A key already used is refused naming what it made. Name who does the work: a task for AI (assigneeType "ai") names one of the AI roles of the project from list_team as assigneeId, and a task created In progress names its assignee (a role id, or the userId of a person); its time In progress counts for that assignee.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | Limits: up to 200 characters. |
| `workspaceId` | UUID or null | yes | — |
| `idempotencyKey` | string | yes | Limits: up to 128 characters. |
| `title` | string | yes | Limits: up to 300 characters. |
| `description` | string |  | Limits: up to 200,000 characters. |
| `brief` | string |  | Limits: up to 200,000 characters. |
| `type` | string |  | Values: `writing`, `visual`, `game_design`, `development`, `marketing`, `review`, `research`, `production`, `worldbuilding`, `general`. |
| `status` | string |  | Values: `backlog`, `ready_for_ai`, `in_progress`, `blocked`, `review`, `approved`, `done`. |
| `priority` | string |  | Values: `low`, `medium`, `high`. |
| `assigneeId` | string |  | Limits: up to 200 characters. |
| `assigneeType` | string |  | Values: `ai`, `human`. |
| `contextItemIds` | array of string |  | Limits: up to 200 items. |
| `externalReferenceIds` | array of string |  | Limits: up to 200 items. |
| `contextRefs` | array of object |  | Limits: up to 200 items. |
| `contextRefs[].id` | string | yes | — |
| `contextRefs[].kind` | string | yes | Values: `codex`, `file`, `external`, `board`, `context_pack`. |
| `contextRefs[].refId` | string | yes | — |
| `contextRefs[].audience` | array of string | yes | Values: `human`, `ai`, `mcp`. |
| `contextRefs[].note` | string |  | — |
| `contextRefs[].addedBy` | string |  | — |
| `contextRefs[].createdAt` | string | yes | — |
| `outputDestination` | string |  | Limits: up to 200 characters. |
| `tags` | array of string |  | Limits: up to 20 items. |
| `dueDate` | date (YYYY-MM-DD) or null |  | — |
| `parentTaskId` | string or null |  | Limits: up to 200 characters. |
| `blockedByTaskIds` | array of string |  | Limits: up to 200 items. |

### `update_task`

Needs: `tasks.manage`

Update ordinary task fields directly. Read get_task first and pass its revision as expectedRevision. On a conflict, compare the current task before sending a new command key. Same-key exact retries return the original committed acknowledgement, not current task content. Requires tasks.manage and current write access. Does not accept Project Memory or start an AI run. A key already used is refused naming what it made. To plan a task for a day, patch dueDate (YYYY-MM-DD; null clears it); get_week shows the week. Moving a task into In progress, or handing it to AI, names who does the work (assigneeId from list_team, as for create_task); its time In progress counts for that assignee.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | Limits: up to 200 characters. |
| `workspaceId` | UUID or null | yes | — |
| `idempotencyKey` | string | yes | Limits: up to 128 characters. |
| `taskId` | string | yes | Limits: up to 200 characters. |
| `expectedRevision` | integer or string (64 hex) | yes | — |
| `patch` | object | yes | — |
| `patch.title` | string |  | Limits: up to 300 characters. |
| `patch.description` | string |  | Limits: up to 200,000 characters. |
| `patch.brief` | string |  | Limits: up to 200,000 characters. |
| `patch.type` | string |  | Values: `writing`, `visual`, `game_design`, `development`, `marketing`, `review`, `research`, `production`, `worldbuilding`, `general`. |
| `patch.status` | string |  | Values: `backlog`, `ready_for_ai`, `in_progress`, `blocked`, `review`, `approved`, `done`. |
| `patch.priority` | string |  | Values: `low`, `medium`, `high`. |
| `patch.assigneeId` | string |  | Limits: up to 200 characters. |
| `patch.assigneeType` | string |  | Values: `ai`, `human`. |
| `patch.contextItemIds` | array of string |  | Limits: up to 200 items. |
| `patch.externalReferenceIds` | array of string |  | Limits: up to 200 items. |
| `patch.contextRefs` | array of object |  | Limits: up to 200 items. |
| `patch.contextRefs[].id` | string | yes | — |
| `patch.contextRefs[].kind` | string | yes | Values: `codex`, `file`, `external`, `board`, `context_pack`. |
| `patch.contextRefs[].refId` | string | yes | — |
| `patch.contextRefs[].audience` | array of string | yes | Values: `human`, `ai`, `mcp`. |
| `patch.contextRefs[].note` | string |  | — |
| `patch.contextRefs[].addedBy` | string |  | — |
| `patch.contextRefs[].createdAt` | string | yes | — |
| `patch.outputDestination` | string |  | Limits: up to 200 characters. |
| `patch.tags` | array of string |  | Limits: up to 20 items. |
| `patch.dueDate` | date (YYYY-MM-DD) or null |  | — |
| `patch.parentTaskId` | string or null |  | Limits: up to 200 characters. |
| `patch.blockedByTaskIds` | array of string |  | Limits: up to 200 items. |

### `update_project_state`

Needs: `tasks.manage`

Rewrite the project's state page with the whole new text (at most 5000 characters): where it stands, what is in progress, what comes next, what waits for the owner, risks, working rules. Read get_project_state first and pass its revision as expectedRevision; a page changed meanwhile is refused naming its current revision, so read it again and merge. Every save is kept as a version people can restore. No idempotencyKey: retrying a lost response with the same text and expectedRevision returns that save. Requires tasks.manage and edit access to the project. Not Project Memory.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | Limits: up to 2,000 characters. |
| `workspaceId` | UUID or null | yes | — |
| `content` | string | yes | Limits: up to 5,000 characters. |
| `expectedRevision` | integer | yes | — |

### `submit_result`

Needs: `drafts.create` and `tasks.manage`

Hand in a finished task in one call: your result as an AI Draft in the Inbox, the task moved to Review, and your git references (commits, branch, tests, pull request, links), shown to the reviewer as reported by you, not verified. The task moves only from Backlog, Ready for AI or In progress and only when expectedRevision is the revision you read; otherwise the draft still lands and the acknowledgement says why the task stayed. Requires drafts.create and tasks.manage, current project write access, an explicit workspaceId (null for private) and a stable idempotencyKey; retry a lost response with the same key and arguments; a key already used for something else is refused naming what it made. contextReceiptId is the one from the get_task read you worked from, or null. You cannot approve, accept or close the task, or write Project Memory.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The task's project id, as get_task returned it. Limits: up to 200 characters. |
| `workspaceId` | UUID or null | yes | Shared workspace id, or null for a private project. Use the address get_task returned. |
| `taskId` | string | yes | The task you finished. Limits: up to 200 characters. |
| `idempotencyKey` | string | yes | Stable key for this hand-in. Retry a lost response with the same key and arguments. Limits: up to 128 characters. |
| `expectedRevision` | integer or string (64 hex) | yes | The revision of the get_task read (or update_task acknowledgement) you worked from. If the task changed since, the draft still lands and the task is not moved. |
| `contextReceiptId` | UUID or null | yes | The contextReceiptId from the get_task response you worked from, or null when that read carried contextReceiptUnavailable or no context pack. Never a receipt from another read. |
| `title` | string | yes | Short result title. Limits: up to 300 characters. |
| `summary` | string |  | One-line summary (may be empty). Limits: up to 2,000 characters. |
| `content` | string | yes | The result text the reviewer reads. Markdown. Link the Project Memory it builds on as [Title](id), with the record id from get_project_memory, or a project document as [Decisions](decisions) (overview, context, decisions, notes, legal), so the Project Wiki connects them. Link only ids you have read; never invent one. Limits: up to 200,000 characters. |
| `collection` | string |  | Optional collection of the task's project, by id or name as list_projects shows it; offered on approval. Limits: up to 200 characters. |
| `document` | string |  | Optional project document: overview, context, decisions, notes or legal. Values: `overview`, `context`, `decisions`, `notes`, `legal`. |
| `references` | array of object |  | Your git evidence, up to 20 items: commits, branch, tests before/after, pull request, links. Stored with the result and shown to the reviewer as reported by you. Limits: up to 20 items. |
| `references[].kind` | string | yes | commit, branch, pull_request, tests or link. Values: `commit`, `branch`, `pull_request`, `tests`, `link`. |
| `references[].value` | string | yes | The reference itself: a commit SHA (7–64 hex), a branch name, a test summary such as "238 pass, 2 fail", or a title. Limits: up to 300 characters. |
| `references[].label` | string |  | Optional note, e.g. the commit message, "before"/"after" for tests. Limits: up to 120 characters. |
| `references[].url` | string |  | Optional https link (commit, pull request, CI run). Shown as reported, never fetched. Limits: up to 500 characters. |

### `create_draft`

Needs: `drafts.create`

Propose content as an AI Draft for a task. It lands in the human's Inbox for review — you cannot approve it or write to Project Memory. Requires the drafts.create scope. When your token also manages tasks, prefer submit_result: it also moves the task to Review and keeps your git references. A key already used is refused naming what it made; an exact retry returns that draft.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string |  | Optional — limit to one project. |
| `workspaceId` | string or null |  | Shared workspace ID, or null for a private project. Use the address returned by list_projects. |
| `idempotencyKey` | string |  | Optional stable key for retrying this same result submission. Limits: up to 128 characters. |
| `contextReceiptId` | UUID |  | The contextReceiptId from the get_task response you built this result from. Omit it if you did not read the task through get_task; the result is then kept without recorded inputs. |
| `taskId` | string | yes | The task this draft is for. |
| `title` | string | yes | Short draft title. |
| `summary` | string |  | One-line summary (may be empty). |
| `content` | string | yes | The proposed draft content. Markdown. Link the Project Memory it builds on as [Title](id), with the record id from get_project_memory, or a project document as [Decisions](decisions) (overview, context, decisions, notes, legal), so the Project Wiki connects them. Link only ids you have read; never invent one. |
| `collection` | string |  | Optional. The collection of the task's project this result belongs in, by its id or name as list_projects shows it. The reviewer is offered it on approval and can change it. An unknown collection is refused with the project's collections. Limits: up to 200 characters. |
| `document` | string |  | Optional. The project document this result belongs in: overview, context, decisions, notes, or legal (Asset rights). The reviewer is offered it on approval and can change it. Values: `overview`, `context`, `decisions`, `notes`, `legal`. |

### `propose_task`

Needs: `tasks.manage`

Optionally propose a private task for human review in Inbox. Use create_task for an immediate reversible task action. Requires the tasks.manage scope.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The project this task belongs to. |
| `title` | string | yes | Short task title. |
| `description` | string |  | What the task is about (optional). |
| `typeHint` | string |  | Suggested task type (e.g. writing, visual, development, research, general). Optional; an unknown type is rejected. |
| `roleHint` | string |  | Suggested role/assignee name (optional). Matched to an existing team member on accept, else the task lands unassigned. |

### `post_message`

Needs: `messages.write`

Post a message into a project participant's Inbox thread, optionally anchored to a task. Shared projects require workspaceId and idempotencyKey. Reuse the same key only to recover the same message after a lost response. Requires messages.write and current write permission; changes no tasks, roles or Project Memory.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The project whose Inbox thread receives the message. |
| `workspaceId` | string or null |  | Exact shared workspace ID, or null for private storage. |
| `idempotencyKey` | string |  | Stable command key for retries; required for shared messages. Limits: up to 128 characters. |
| `participantId` | string | yes | The roster participant (team member/agent id) whose thread to post into. |
| `body` | string | yes | The message text. |
| `taskId` | string |  | Optional task this message concerns (must belong to the same project). |

### `propose_task_update`

Needs: `tasks.manage`

Optionally propose changes to a private task for human review as an Inbox diff. Use update_task for an immediate reversible task action. Omitted fields stay unchanged. Requires the tasks.manage scope.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `taskId` | string | yes | The existing task to change. |
| `changes` | object | yes | Only the fields to change — omitted fields stay as they are. At least one is required. |
| `changes.title` | string |  | New title. |
| `changes.description` | string |  | New description/brief. |
| `changes.status` | string |  | New status: backlog, ready_for_ai, in_progress, blocked, review, approved, or done. An unknown status is rejected. |
| `changes.priority` | string |  | New priority: low, medium, or high. |
| `changes.typeHint` | string |  | New task type (e.g. writing, visual, development, research, general). An unknown type is rejected. |
| `changes.roleHint` | string |  | New assignee hint — matched to an existing team member by role or name on accept; no match leaves the assignee unchanged. |
| `reason` | string |  | Why you propose this change (optional, shown on the card). |

### `propose_task_delete`

Needs: `tasks.manage`

Propose deleting an EXISTING task. It lands in the human's Inbox as a warning card — you cannot delete anything directly; only the human's Accept removes the task, and Dismiss leaves it untouched. Requires the tasks.manage scope.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `taskId` | string | yes | The existing task to delete. |
| `reason` | string |  | Why this task should go (optional, shown on the card). |

### `propose_role`

Needs: `team.manage`

Propose a team role for a project. It lands in the human's Inbox for review — you cannot add it directly; the human approves each one and the role is then added to their team (with no AI connection). Requires the team.manage scope.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The project this role belongs to. |
| `name` | string | yes | The team member's name. |
| `role` | string | yes | The role (freeform, e.g. Editor, Illustrator). |
| `description` | string |  | What this role does (optional). |

### `propose_composition`

Needs: `team.manage`

Propose a whole team composition for a project — the set of roles (seats) it needs, e.g. after analyzing its scope. It lands in the human's Inbox as ONE proposal; the human's single Accept creates all the seats as vacancies on their roster (no AI is connected by a proposal — a 'ai' typeHint only labels the plan). Max 12 seats. Requires the team.manage scope.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | yes | The project this composition is for. |
| `rationale` | string |  | Why these seats (optional, shown on the card). |
| `seats` | array of object | yes | The proposed seats (1-12). |
| `seats[].role` | string | yes | The role (freeform, e.g. Animator, Composer). |
| `seats[].name` | string |  | Optional display name; defaults to the role. |
| `seats[].typeHint` | string |  | Optional 'ai' or 'human' — labels the plan only. |
