Claude Code and MCP
The board speaks MCP, so an agent in your terminal can read the backlog, pick work up, and report back where the team can see it.
The fourteen tools
Section titled “The fourteen tools”| Tool | Does |
|---|---|
list_projects |
Every project the token can see |
get_project |
One project by key or id, including its columns and fields |
list_tasks |
Tasks, filtered by status, assignee, category or priority |
get_task |
One task by key, for example ORB-14 |
create_task |
Create a task |
update_task |
Change a task’s fields |
change_task_status |
Move a task between columns |
list_sprints |
Sprints in a project |
create_sprint |
Create a sprint |
update_sprint |
Change a sprint |
add_comment |
Comment on a task |
list_comments |
Read a task’s comments |
link_tasks |
Link two tasks: a parent and its child, a blocker, a duplicate |
unlink_tasks |
Remove one of those links |
Project-defined fields go through a generic fields argument keyed by field name — {"Difficulty": "L"} — so a project that renames or removes a field does not break the client. Ask get_project which fields exist rather than assuming.
A tool refuses an argument it does not declare, rather than dropping it and answering as though the write had happened. The refusal names the argument and points at the replacement where there is one: a checklist passed to update_task is acceptanceCriteria, a status is the change_task_status tool, a difficulty is fields. An update that names no field at all is refused too, so a task’s updatedAt never moves for a call that wrote nothing.
Linking tasks
Section titled “Linking tasks”link_tasks writes the relations the board already draws, so an epic can hold real sub-tasks
instead of a checklist nothing can move, assign or run. Both ends are named by task key, and the
type reads from taskKey’s side:
type |
Means |
|---|---|
parent_of |
taskKey is the parent, targetTaskKey the child |
blocked_by |
taskKey is blocked by targetTaskKey |
relates |
The two are related, in no direction |
duplicates |
taskKey duplicates targetTaskKey |
The link is written on taskKey’s side. A second call replaces the relates, duplicates or
parent_of link that end holds; blocked_by is kept separately and stacks with it. The far end
keeps whatever it stored about taskKey, so linking a pair from both sides leaves the board holding
both, and the task page shows two rows for the one pair.
parent_of is the exception, because a task has one parent: a second parent_of moves the child,
and the child’s previous parent loses it without being named in the call.
Only relates reads the same from both ends. duplicates does not: the far task’s page shows
Duplicated by and offers no way to remove it, so name the ends the way round you mean.
A link that would make a task its own ancestor, or close a chain of blockers, is refused. relates
and duplicates carry no ordering, so there is no cycle for them to close.
Both tasks have to be on the same board — a pair on two boards is refused by name, rather than reading as a mistyped key.
get_task reads the links back from both ends: the parent lists its children, and the child names
its parent without anything having been written to it.
unlink_tasks takes the same three arguments and removes the link stored on taskKey’s side, so
it has to name the end that holds it. Asking from the other end is refused and says which end holds
it, rather than reporting a removal that did not happen. Removing the same link twice is refused
the second time for the same reason.
Handing a task to a machine
Section titled “Handing a task to a machine”update_task carries an agent parameter, and setting it is the hand-over: the machine belonging to the task’s assignee takes the task and runs that agent, and only when that person assigned it to themselves. An empty string means nobody, which is what a task somebody is doing by hand looks like.
The agent is named, not identified — no response here carries an agent id. Names resolve within the task’s own project: a project agent belonging to another board is refused with exists on another project, rather than silently matching a board you did not mean when two of them share a name.
Who may set it follows the agent, not your instance role:
- a project agent — anyone who can edit the task;
- a global agent — the same;
- a personal agent — only its owner, and only onto their own task. It is dropped again if the task is handed to somebody else.
An agent with no steps is refused, because a machine handed that task would have nothing to run.
Connecting over HTTP
Section titled “Connecting over HTTP”The server is built into the app at POST /api/mcp. Nothing to clone, nothing to build — one URL and a bearer token:
{ "mcpServers": { "boardplanner": { "type": "http", "url": "https://your-instance.example.com/api/mcp", "headers": { "Authorization": "Bearer cp_..." } } }}Create the token under Settings → API Tokens, and scope it to the projects the agent should touch. The scope is enforced centrally, so the agent sees exactly what the token allows — no more than its owner, and often less.
Connecting as a connector (OAuth)
Section titled “Connecting as a connector (OAuth)”Clients that expect a custom connector rather than a static token get a full OAuth 2.1 with PKCE, including dynamic client registration — so there is no client secret to paste. Point the client at the same /api/mcp URL, leave the client id and secret empty, sign in, and choose whether the connection gets all projects or only selected projects.
Tokens issued this way carry the same project scope as a scoped API token, on both REST and MCP.
Changing, setting or resetting your password disconnects Claude Code and revokes your API tokens — reconnect it afterwards. See A password change revokes more than sessions.
Running it locally
Section titled “Running it locally”A standalone MCP server also ships in the repository for stdio clients. It exposes the twelve reading and writing tools above, but not link_tasks or unlink_tasks — new tools land on the HTTP endpoint only. It is configured with two environment variables — BOARDPLANNER_URL (defaulting to http://localhost:3000) and BOARDPLANNER_TOKEN. A bearer token is the only credential it accepts; there is no username-and-password mode.