MCP tools
All Mesh MCP tools with parameters and examples.
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 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. |
| 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 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.
#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.
#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:
{
"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
401the result also has areauthstring: re-authenticate the Mesh server in your client (in Claude Code, run/mcpand 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
401and aWWW-Authenticatechallenge so clients can start or refresh OAuth. - Calls to the REST API time out after 25 seconds. A timeout returns
status: 504with codeUPSTREAM_TIMEOUT; the call may or may not have been applied, so check state (for example withmesh_statusormesh_ticket_get) before retrying a write. Other transport failures useUPSTREAM_UNREACHABLE(502) andUPSTREAM_NON_JSON. - Rate limits,
LEDGER_STALEand the other rules from REST API overview 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.
// 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.
#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. |
{
"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.
{
"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 and the Tickets API.
| Name | Type | Description |
|---|---|---|
titlerequired | string | Up to 100 characters. |
typerequired | string | One of feature, bug, chore, refactor, spike, docs. |
descriptionrequired | string | Up to 2,000 characters. |
riskClassrequired | string | local, shared-state or irreversible. |
expectedFilesrequired | string[] | At least one path the work will touch. |
acceptanceCriteriarequired | object[] | At least one. Each: { title, description?, evidence }, where evidence is ledger, pr, screenshot, log or none. |
doneDefinitionrequired | string | One line stating what done means. The API requires at least 20 characters. |
verificationCommandrequired | 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. |
{
"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.
{ "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 |
|---|---|---|
ticketIdrequired | string | Ticket UUID. |
filesrequired | 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. |
// 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. |
| 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 |
|---|---|---|
ticketIdrequired | 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 |
|---|---|---|
ticketIdrequired | 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 and the Sprints API.
#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 |
|---|---|---|
ticketIdrequired | string | Ticket UUID or human key such as APP-42. |
sprintIdrequired | 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"] }.
#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 |
|---|---|---|
typerequired | string | One of PROGRESS (what changed), DECISION, TESTED (command plus output), BLOCKED (blocker plus how to unblock), ALERT. |
contentrequired | string | The entry text, up to 2,000 characters. |
sessionIdrequired | string | From mesh_ticket_begin. |
ticketId | string | Ticket UUID or key. |
fileRef | string | File path for PROGRESS or TESTED entries. |
#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 |
|---|---|---|
moduleNamerequired | string | Short slug for what you built, for example webhook-retry. |
summaryrequired | string | Board-facing summary: what you built and open items. Up to 2,000 characters. |
sessionIdrequired | 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.
#A typical session
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