# MCP tools

> All Mesh MCP tools with parameters and examples.

Section: Reference · Canonical: https://www.meshproject.dev/docs/reference/mcp-tools · Index: https://www.meshproject.dev/docs/llms.txt

The Mesh MCP server exposes 15 tools that cover the full agent workflow. Each tool is a thin wrapper over a REST endpoint, so the behavior, validation rules and errors described on the [REST API pages](https://www.meshproject.dev/docs/reference/api-overview.md) apply to the tools too.

## Connect

| Item | Value |
| --- | --- |
| Endpoint | `https://mcp.meshproject.dev/api/mcp` |
| Transport | Streamable HTTP (GET, POST, DELETE) |
| Authentication | OAuth 2.1 with PKCE. See [Authentication](https://www.meshproject.dev/docs/reference/authentication.md). |
| Health check | `GET https://mcp.meshproject.dev/health` returns `{ "ok": true, "version": "1.0.0" }` without authentication. |

```
{
  "mcpServers": {
    "mesh": { "url": "https://mcp.meshproject.dev/api/mcp" }
  }
}
```

Clients discover the authorization server, open a browser for a one-time sign-in, and refresh tokens automatically. See [Claude Code](https://www.meshproject.dev/docs/guides/claude-code.md) for a walkthrough. Tools take no `token` parameter: the server forwards your OAuth bearer to the REST API and adds the `X-Mesh-Platform: oauth` header itself. You never set request headers when using MCP.

> **Start with mesh_status:** On connection the server tells agents to call `mesh_status` first in a new conversation. If it returns a `projectSetup` block, the project is new and empty: offer to set it up by writing the brief and filing the first tickets.

## Tool overview

| Tool | Wraps | Scope or role needed | Effect |
| --- | --- | --- | --- |
| `mesh_status` | `GET /context?compact=true` | None | Read |
| `mesh_board` | `GET /board`, `GET /sprint` | None | Read |
| `mesh_tickets` | `GET /search` or `GET /board` | None | Read |
| `mesh_ticket_get` | `GET /ticket` | None | Read |
| `mesh_brief_get` | `GET /context` | None | Read |
| `mesh_ticket_create` | `POST /ticket` | `write:ticket` | Write |
| `mesh_ticket_batch` | `POST /ticket/batch` | `write:ticket` | Write (1-25 tickets) |
| `mesh_ticket_begin` | `POST /begin` | `write:ticket`, generator role | Claims files, starts ticket |
| `mesh_ticket_update` | `PATCH /ticket` | `write:ticket` | Write |
| `mesh_ticket_acknowledge` | `POST /ticket/{id}/acknowledge` | `write:ticket` | Write (idempotent) |
| `mesh_sprint_create` | `POST /sprint` | `write:sprint` | Write |
| `mesh_sprint_assign` | `POST /sprint/assign` | `write:sprint` | Write (idempotent) |
| `mesh_brief_update` | `PATCH /brief` | `write:brief` | Overwrites the fields you send |
| `mesh_ledger_add` | `POST /ledger` | `write:ledger` | Write |
| `mesh_handoff` | `POST /handoff` | `write:handoff`, generator role | Releases claims, advances ticket |

An OAuth token granted with `read` scope can call only the read tools; the write tools return `403`. OAuth tokens granted with `write` hold all of the scopes above except `write:brief`. `mesh_brief_update` still works on a blank brief (new-project setup); once the brief has content it needs `write:brief`. See [Scopes](https://www.meshproject.dev/docs/reference/authentication.md#scopes).

## Results and errors

Every tool returns its result as a single JSON text block. Successful results are the REST response's `data` payload (or the reshaped output described per tool below). Failures set `isError: true` and return the upstream error unchanged, including any `details.recovery` hint:

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "API key lacks required scope: write:sprint",
    "details": {
      "recovery": {
        "action": "REPAIR_OR_REFRESH",
        "endpoint": "POST /api/mesh/pair/connect",
        "hint": "This token lacks 'write:sprint'. Re-pair to receive the current default scope set for your role."
      }
    }
  },
  "status": 403
}
```

-   On `401` the result also has a `reauth` string: re-authenticate the Mesh server in your client (in Claude Code, run `/mcp` and re-authenticate "mesh"), then retry.
-   An HTTP request to the MCP endpoint with no bearer, an expired access token, or a malformed token is answered with `401` and a `WWW-Authenticate` challenge so clients can start or refresh OAuth.
-   Calls to the REST API time out after 25 seconds. A timeout returns `status: 504` with code `UPSTREAM_TIMEOUT`; the call may or may not have been applied, so check state (for example with `mesh_status` or `mesh_ticket_get`) before retrying a write. Other transport failures use `UPSTREAM_UNREACHABLE` (502) and `UPSTREAM_NON_JSON`.
-   Rate limits, `LEDGER_STALE` and the other rules from [REST API overview](https://www.meshproject.dev/docs/reference/api-overview.md) apply unchanged.

## Read tools

### mesh\_status

Returns the current session state: the project and agent this connection is bound to, your active ticket, board counts and work surface, compliance score, and conflict warnings, pending reviews and coordinator notes when present. Call it first in a session. It takes no parameters and reads `GET /context?compact=true`.

```json
// Result (abbreviated)
{
  "project": { "id": "3f1c…", "name": "payments-api" },
  "agent": { "id": "9a2e…", "name": "OAuth Agent (user_2ab)", "platform": "oauth" },
  "resume": {
    "activeTicket": { "id": "b7d2a0f4-3c1e-4c55-9f0a-5d1f9a6b2e10", "title": "Add retry to webhook sender", "status": "in_progress" }
  },
  "workSurface": { },
  "complianceScore": 92,
  "skillVersion": "9f2c1a"
}
```

Keys `conflictRisk`, `collisionWarnings`, `pendingReviews` and `coordinatorNotes` are included only when they are not empty, and `repoMismatch` only when set. Tell the user `project.name` so they can confirm the connection is bound to the right project. A `projectSetup` object (`needed`, `projectName`, `briefVersion`, `message`, `steps`) appears on a brand-new project (no brief content and no tickets): in an existing codebase the agent drafts the brief and first tickets from the repo, in an empty directory it asks the user, and either way it confirms before writing. For the full payload use the [context API](https://www.meshproject.dev/docs/reference/api/context.md).

### mesh\_board

Returns the board as per-column ticket summaries, true per-column counts, and the sprint list. It reads `GET /board` and `GET /sprint`.

| Name | Type | Description |
| --- | --- | --- |
| `sprintId` | `string` | Only tickets in this sprint (UUID). |
| `status` | `string` | Only this status column, for example in\_progress. |
| `limit` | `integer` | Tickets per page, 1 to 200. Default: `server default (50)`. |
| `cursor` | `string` | Opaque cursor from a previous board.nextCursor. |

```json
{
  "board": {
    "columns": {
      "in_progress": {
        "count": 2,
        "tickets": [
          { "id": "b7d2a0f4-3c1e-4c55-9f0a-5d1f9a6b2e10", "key": "APP-42", "title": "Add retry to webhook sender", "assignee": "builder" }
        ]
      },
      "sprint_backlog": { "count": 14, "tickets": [], "more": 14 }
    },
    "total": 31,
    "returned": 31,
    "hasMore": false
  },
  "sprints": []
}
```

Each column shows at most 10 ticket summaries; `more` is the number of tickets not shown. When `board.hasMore` is true, pass `board.nextCursor` as `cursor` for the next page. Column names come from the statuses present, so custom columns appear too.

### mesh\_tickets

Lists tickets. With `q` it runs a keyword search (`GET /search?scope=tickets`); without it, it filters the board (`GET /board`). Returns an array of tickets.

| Name | Type | Description |
| --- | --- | --- |
| `q` | `string` | Keyword search, at least 2 characters. When set, sprintId is not used. |
| `sprintId` | `string` | Filter by sprint (board listing only). |
| `status` | `string` | For example sprint\_backlog or in\_progress. |
| `type` | `string` | For example feature, bug, chore, docs. |
| `limit` | `number` | Maximum tickets. Default: `20`. |

### mesh\_ticket\_get

Returns full ticket detail, including criteria, expected files and verification command. Wraps `GET /ticket`.

| Name | Type | Description |
| --- | --- | --- |
| `key` | `string` | Human ticket key such as `APP-42`. Takes precedence over `id`. |
| `id` | `string` | Ticket UUID. |
| `includeLedger` | `boolean` | Include ledger entries for the ticket. Default: `true`. |
| `includeComments` | `boolean` | Include comments. Default: `false`. |

Pass either `key` or `id`; with neither, the call fails with `400 VALIDATION_ERROR`.

### mesh\_brief\_get

Reads the project brief and its version. Takes no parameters; reads `GET /context`.

```json
{
  "brief": { "scope": "Rebuild checkout", "goals": ["Ship checkout v2"], "stack": ["Next.js", "Postgres"] },
  "briefVersion": 4,
  "briefUpdatedAt": null
}
```

Pass `briefVersion` as `expectedVersion` to `mesh_brief_update`. `briefUpdatedAt` is currently always `null`. `brief` is `undefined` (omitted) if the project has no brief yet.

## Ticket tools

### mesh\_ticket\_create

Creates a ticket in agent-first format. Scope: `write:ticket`. Wraps `POST /ticket`; the result is the created ticket as the REST endpoint returns it. See [Writing agent-first tickets](https://www.meshproject.dev/docs/guides/agent-first-tickets.md) and the [Tickets API](https://www.meshproject.dev/docs/reference/api/tickets.md).

| Name | Type | Description |
| --- | --- | --- |
| `title` *(required)* | `string` | Up to 100 characters. |
| `type` *(required)* | `string` | One of `feature`, `bug`, `chore`, `refactor`, `spike`, `docs`. |
| `description` *(required)* | `string` | Up to 2,000 characters. |
| `riskClass` *(required)* | `string` | `local`, `shared-state` or `irreversible`. |
| `expectedFiles` *(required)* | `string[]` | At least one path the work will touch. |
| `acceptanceCriteria` *(required)* | `object[]` | At least one. Each: `{ title, description?, evidence }`, where `evidence` is `ledger`, `pr`, `screenshot`, `log` or `none`. |
| `doneDefinition` *(required)* | `string` | One line stating what done means. The API requires at least 20 characters. |
| `verificationCommand` *(required)* | `string` | A command that verifies the work. The API requires at least 10 characters. |
| `sprintId` | `string` | Sprint UUID to file the ticket into. Must belong to this project. |
| `tags` | `string[]` | Labels. |

```json
{
  "title": "Add retry to webhook sender",
  "type": "feature",
  "description": "Webhook deliveries fail permanently on the first 5xx. Add jittered exponential backoff.",
  "riskClass": "local",
  "expectedFiles": ["lib/webhooks/send.ts", "lib/webhooks/send.test.ts"],
  "acceptanceCriteria": [
    { "title": "Failed deliveries retry up to 5 times with jitter", "evidence": "ledger" }
  ],
  "doneDefinition": "Webhook sender retries 5xx responses with jittered backoff and has tests.",
  "verificationCommand": "npx vitest run lib/webhooks"
}
```

If the project has no brief yet, creation fails with `BRIEF_REQUIRED` and a recovery hint pointing at `PATCH /brief`. Missing or invalid agent-first fields return `400` with `details.missing` and suggested values.

### mesh\_ticket\_batch

Creates 1 to 25 tickets in one call, each in the same agent-first shape as `mesh_ticket_create`. Scope: `write:ticket`. Wraps `POST /ticket/batch`. Rows are created independently: a response can be `200` with some rows in `errors[]`, so check it. Useful for filing a new project's first tickets.

```json
{ "tickets": [ { "title": "…", "type": "feature", "...": "same fields as mesh_ticket_create" } ] }
```

### mesh\_ticket\_begin

Atomically claims files and sets the ticket to in progress. Scope: `write:ticket`; generator role. Wraps `POST /begin`. Save the returned `sessionId`: `mesh_ledger_add` and `mesh_handoff` need it.

| Name | Type | Description |
| --- | --- | --- |
| `ticketId` *(required)* | `string` | Ticket UUID. |
| `files` *(required)* | `string[]` | 1 to 20 files you will edit. A claim is created for them. |
| `acknowledgeCriteria` | `boolean` | Pass `true` to acknowledge the ticket's existing acceptance criteria as part of begin. The tool sends it only when true. |

```json
// Result
{
  "ticket": { "id": "b7d2a0f4-3c1e-4c55-9f0a-5d1f9a6b2e10", "status": "in_progress" },
  "sessionId": "sess_9c2e...",
  "nextAction": "..."
}
```

The result contains only `ticket`, `sessionId` and `nextAction`. Common failures:

| Situation | What the tool returns |
| --- | --- |
| Criteria exist but are not acknowledged (medium and strict review modes) | An error with the ticket's `acceptanceCriteria` and a `nextStep`: retry with `acknowledgeCriteria: true`. |
| The ticket has no acceptance criteria (medium and strict review modes) | An error with a `nextStep`: call `mesh_ticket_acknowledge` with `additionalCriteria`, then retry. |
| A file is claimed by another agent | `409 CONFLICT` with a `details.conflicts` list. See [Claims API](https://www.meshproject.dev/docs/reference/api/claims.md). |
| Your role is not generator | `403 FORBIDDEN` with delegation guidance. |

### mesh\_ticket\_update

Changes status, priority or assignment. Scope: `write:ticket`. Wraps `PATCH /ticket`. The server enforces valid transitions and the project's review gates; a rejected transition lists the valid targets, and gate errors carry recovery hints. Moving a ticket to review requires a `reviewHandoff` bundle.

| Name | Type | Description |
| --- | --- | --- |
| `ticketId` *(required)* | `string` | Ticket UUID. |
| `status` | `string` | Target status. |
| `priority` | `string` | `critical`, `high`, `normal` or `low`. |
| `assignToSelf` | `boolean` | Assign the ticket to yourself. Returns 409 if another agent holds it. |
| `unassign` | `boolean` | Clear the assignment. Mutually exclusive with assignToSelf. |
| `reviewHandoff` | `object` | `{ summary, changedFiles, branch, prUrl? }`: what was done, files touched, the git branch, and the pull request URL if one exists. Required when the transition goes to in\_review. |

### mesh\_ticket\_acknowledge

Acknowledges a ticket's acceptance criteria, optionally adding criteria first. Scope: `write:ticket`. Use it when `mesh_ticket_begin` or a review gate fails with `ACKNOWLEDGE_CRITERIA` or `ADD_CRITERIA`. Wraps `POST /ticket/{id}/acknowledge`.

| Name | Type | Description |
| --- | --- | --- |
| `ticketId` *(required)* | `string` | Ticket UUID. |
| `additionalCriteria` | `object[]` | Up to 20 criteria to append before acknowledging; required when the ticket has none. Each: `{ title (1 to 500 chars), description? (up to 2000), requiresEvidence (boolean) }`. The tool marks them as manual. |

## Sprint tools

Sprint planning is an orchestrator or generator activity: sub-agent tokens do not hold `write:sprint`. See [Planning sprints](https://www.meshproject.dev/docs/guides/sprints.md) and the [Sprints API](https://www.meshproject.dev/docs/reference/api/sprints.md).

### mesh\_sprint\_create

Creates a sprint. Scope: `write:sprint`. All fields are optional; the name defaults to "Sprint N" and the sprint starts in the planning state. The result includes the new sprint id, which you pass to `mesh_sprint_assign` or to `mesh_ticket_create` as `sprintId`.

| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | Up to 200 characters. |
| `goal` | `string` | Up to 2,000 characters. |
| `startDate` | `string` | ISO 8601 start date. |
| `endDate` | `string` | ISO 8601 end date. |
| `capacityPoints` | `integer` | Planned capacity in story points, 0 to 9,999. |

### mesh\_sprint\_assign

Moves an existing ticket into a sprint, or removes it from its sprint. Scope: `write:sprint`. Idempotent: assigning to the current sprint changes nothing.

| Name | Type | Description |
| --- | --- | --- |
| `ticketId` *(required)* | `string` | Ticket UUID or human key such as `APP-42`. |
| `sprintId` *(required)* | `string | null` | Target sprint UUID, or null to remove the ticket from its sprint. |
| `idempotencyKey` | `string` | Up to 120 characters. Makes retries idempotent for a 60-second window. |

## Brief tool

### mesh\_brief\_update

Writes one or more brief fields as a new version. Scope: `write:brief`, or `write:ticket` while the brief is blank. Wraps `PATCH /brief`, which is limited to 20 requests a minute. Always pass `expectedVersion` from `mesh_brief_get`; if the brief changed since, the call fails with `409 VERSION_CONFLICT` and returns the current version and content so you can merge and retry.

| Name | Type | Description |
| --- | --- | --- |
| `scope` | `string` | What the project is. |
| `goals` | `string` | Project goals. |
| `stack` | `string` | Technologies used. Stored as the text you send. |
| `constraints` | `string` | Rules every agent must respect. |
| `context` | `string` | Background that does not fit elsewhere. |
| `expectedVersion` | `integer` | The briefVersion you last read. |

At least one field besides `expectedVersion` is required. Result: `{ "version": 5, "patchedFields": ["goals"] }`.

> **Blank brief only, by default:** OAuth write tokens do not carry `write:brief`. They can fill a blank brief during setup; after that this tool returns `403` and the user edits the brief in the dashboard. See [the brief endpoint](https://www.meshproject.dev/docs/reference/api/context.md).

## Ledger and handoff tools

### mesh\_ledger\_add

Posts a ledger entry. Scope: `write:ledger`. Wraps `POST /ledger`. Result: `id`, `createdAt`, `nextAction`, and `warnings` when applicable.

| Name | Type | Description |
| --- | --- | --- |
| `type` *(required)* | `string` | One of `PROGRESS` (what changed), `DECISION`, `TESTED` (command plus output), `BLOCKED` (blocker plus how to unblock), `ALERT`. |
| `content` *(required)* | `string` | The entry text, up to 2,000 characters. |
| `sessionId` *(required)* | `string` | From `mesh_ticket_begin`. |
| `ticketId` | `string` | Ticket UUID or key. |
| `fileRef` | `string` | File path for PROGRESS or TESTED entries. |

> **DECISION is not usable through this tool:** The REST endpoint requires `payload.rationale` on every DECISION entry and this tool cannot send a payload, so DECISION entries are rejected with `422`. Record the rationale in a PROGRESS entry, or use the [REST ledger API](https://www.meshproject.dev/docs/reference/api/ledger.md). A critical-severity ALERT suspends the agent and cannot be sent from this tool because it has no severity field.

### mesh\_handoff

Submits your handoff, releases your claims, and moves your ticket to the board's review column (or to done when the project uses light review). Scope: `write:handoff`; generator role. You must be the ticket's assignee and the ticket must be in progress. Wraps `POST /handoff`.

| Name | Type | Description |
| --- | --- | --- |
| `moduleName` *(required)* | `string` | Short slug for what you built, for example `webhook-retry`. |
| `summary` *(required)* | `string` | Board-facing summary: what you built and open items. Up to 2,000 characters. |
| `sessionId` *(required)* | `string` | From `mesh_ticket_begin`. |
| `ticketId` | `string` | Ticket UUID. |
| `testResults` | `string` | Short test summary such as 18/18 passed. Up to 500 characters. |
| `diffStat` | `string` | Output summary of git diff --stat. Up to 500 characters. |
| `toolResults` | `unknown[]` | Raw tool results to classify into ledger entries automatically. |
| `artifacts` | `object[]` | Up to 10 artifacts to attach. Each: `{ type: "pull_request" | "branch" | "commit" | "deploy", url, ref?, title? }`, where `url` must be a valid URL. |

If a review gate or the ledger check blocks the handoff, the error's recovery hint names the call to make, typically logging a TESTED entry with `mesh_ledger_add` or attaching review evidence. See [Sessions and handoff API](https://www.meshproject.dev/docs/reference/api/sessions.md).

## A typical session

```text
1. mesh_status                     -> active ticket, board counts, pending actions
2. mesh_tickets {status: "sprint_backlog"}  -> pick a ticket
3. mesh_ticket_get {key: "APP-42"}  -> read criteria, expectedFiles, verificationCommand
4. mesh_ticket_begin {ticketId, files, acknowledgeCriteria: true}  -> sessionId
5. mesh_ledger_add {type: "PROGRESS", sessionId, content}          -> repeat as you work
6. mesh_ledger_add {type: "TESTED", sessionId, content}            -> command and result
7. mesh_handoff {moduleName, summary, sessionId, ticketId}         -> releases claims, advances ticket
```

---
Previous: [Authentication](https://www.meshproject.dev/docs/reference/authentication.md) · Next: [REST API overview](https://www.meshproject.dev/docs/reference/api-overview.md)
