Skip to content
DocumentationMCP tools

MCP tools

Every tool the MCP server offers, with its parameters and an example call. To connect a client and make a first call, start with Connect an assistant over MCP.

Each example shows the params of a tools/call request: the tool's name and its arguments. A parameter that is not marked required may be left out. The all-capitals ids in the examples (BOARD_ID, BLOCK_ID, EDGE_ID, SKILL_ID, NOTIFICATION_ID and their OTHER_ twins) stand for ids you read from an earlier call, never for values to paste. The live list, with every input schema, is what tools/list returns, so read it rather than hard-coding this page.

A tool that cannot do what you asked answers with a normal result marked isError, and the reason is in its text. A key narrowed to one map is offered fewer tools, as Connect an assistant over MCP explains.

Reading tools#

list_boards#

Your maps, nested: each has its id, name, a one-line desc when its owner wrote one, pinned: true when it is pinned, and its sub-maps under children. It is the first call when you do not know which map holds something. It lists the maps you own; a map someone else added you to appears only to a key or connection made for that map, marked with your role.

  • search (string) — keep the maps whose name or description contains every

word you give.

{ "name": "list_boards", "arguments": { "search": "roadmap" } }

get_board#

Read a map you can reach. The default is the full mmap.v1 document plus the arrangement and style settings the server applies to anything you send it. Read before you build, not only when you edit.

  • boardId (uuid, required) — the map to read.
  • detail ("outline" | "style" | "full", default "full") —

"full" is every block's title, description and body. "outline" swaps each body for its length in characters and adds creation and update times, which makes it the cheap first look at a map you did not build. "style" is what every block wears and nothing else; it is JSON only.

  • branchOf (uuid) — read one block and everything under it, with the path back

up to the top, instead of the whole map. Cannot be combined with blockIds.

  • depth (integer, 0–20) — how many levels to include, counted from

branchOf, or from the map's roots when that is left out.

  • blockIds (array of uuid, 1–50) — read just these blocks, wherever they sit.

An id that is not on the map is listed as missing instead of failing the call.

  • format ("json" | "markdown" | "tree", default "json") —

"markdown" is the same map as nested prose, about half the tokens, for reading. "tree" is nested JSON for acting on what you read: each link the nesting cannot show is listed under crossRefs with its own edgeId, the id update_edge and delete_edge take.

{
  "name": "get_board",
  "arguments": { "boardId": "BOARD_ID", "format": "tree", "detail": "outline" }
}

search_blocks#

Find blocks by meaning across the maps you own; the phrase does not have to match the wording in the block. Use it to find an existing note before you write a duplicate. Each hit carries the block id, the map it is on and a short snippet, nearest first. A map someone else added you to is never searched, even through a key made for that map, so an empty answer says nothing about one: read it with get_board. Read-only, and it spends no AI allowance. If search is unavailable the reply says so, which is different from finding nothing.

  • query (string, 2–500 characters, required).
  • boardId (uuid) — search one map instead of every map you own. A key

narrowed to one map is not offered this parameter, because it only ever searches that map.

{ "name": "search_blocks", "arguments": { "query": "why we dropped the monthly window" } }

get_graph#

Describe the shape of a map: how many blocks and links it has, how many separate islands it falls into, which blocks are attached to nothing, how dense it is and which blocks have the most links. It carries no block bodies. Read-only.

  • boardId (uuid, required).
  • detail ("summary" | "full") — "full" adds every link with its own

id, both ends and whether it carries structure, which is what you need before you change or cut one.

{ "name": "get_graph", "arguments": { "boardId": "BOARD_ID", "detail": "full" } }

Pairs of blocks that are probably related and are not linked, in two sections you should not add together: structural (blocks that share several neighbours) and semantic (blocks close in meaning, which structure alone cannot find). Read-only.

  • boardId (uuid, required).
  • limit (integer, 1–20, default 5) — pairs per section. The

semantic section returns at most 3 however large a limit you pass.

  • blockId (uuid) — keep only the pairs that involve this block.
{ "name": "find_missing_links", "arguments": { "boardId": "BOARD_ID", "blockId": "BLOCK_ID" } }

get_edge_signal#

Read every link on a map through one lens. Despite the name it takes no edge id: it returns one value per link, each with the link's own id and both ends by id and by title, so a link worth changing goes straight to update_edge. A link the lens has nothing to say about is absent, which means unmeasured and not unrelated. Read-only.

  • boardId (uuid, required).
  • signal ("weight" | "coEdit" | "semantic", required) — the

strength declared on the link itself, how often its two blocks were edited in the same sitting, or how close they are in meaning.

{ "name": "get_edge_signal", "arguments": { "boardId": "BOARD_ID", "signal": "semantic" } }

review_map#

Look at a map as it actually sits on the board and check the work. Call it before you tell anyone a map you built is finished: the map arranges what you send into its own shape, so the layout you meant and the one on screen can differ. It reports overlapping blocks, blocks stranded far from everything, blocks connected to nothing and separate pieces that never meet, and returns a numbered sketch of where the blocks sit. Read-only.

  • boardId (uuid, required).
  • format ("svg" | "png" | "html" | "none", default "svg") —

the drawing. The markup is the largest part of the reply, so use "none" to check your own work and "png" only when a person needs pixels.

{ "name": "review_map", "arguments": { "boardId": "BOARD_ID", "format": "none" } }

list_docs#

List the mmap.v1 rulebook sections available to you, readable with get_doc. The list is resolved per account, so read it instead of assuming.

  • boardId (uuid) — also list the protocol of a scenario running on that map,

if there is one.

{ "name": "list_docs", "arguments": {} }

get_doc#

Read one or more rulebook sections by their exact id.

  • section (string, or an array of strings, required) — ids come from

list_docs; do not guess one.

  • boardId (uuid) — read the protocol of a scenario running on that map. It is

readable no other way.

{ "name": "get_doc", "arguments": { "section": "nodes" } }

list_agents#

The assistants this account has set up and where each is pointed: a Corlecti Map key or a Telegram bot, with its name and the map it reaches. A row with no map reaches every map the account reaches. It never returns a secret. A narrowed connection sees only the credentials narrowed the same way, and a connection to the whole account lists none on a map you were only invited to — delegate cannot reach those. Read-only.

No parameters.

{ "name": "list_agents", "arguments": {} }

list_skills#

The skills and scenarios you wrote, the ones you can edit or delete. It is a different question from list_docs, which is everything you may read. Start here before you write a skill, so you extend one that exists.

  • kind ("skill" | "scenario") — keep one kind.
  • detail ("list" | "full", default "list") — "full" includes each

body; the default gives its length. One body is cheaper through get_doc.

{ "name": "list_skills", "arguments": { "kind": "skill" } }

list_notifications#

Your notification inbox, newest first. Each row has the sentence already written for a person (line), a detail when there is more, an href you can hand them and whether it is read. It marks nothing, so polling it never empties an inbox nobody looked at. Read-only.

  • unreadOnly (boolean) — only what has not been marked read.
  • limit (integer, 1–100, default 20) — when it cuts the list short the

result says truncated.

  • boardId (uuid) — only notifications about one of your maps. A map you cannot

see gives an empty list, not an error.

{ "name": "list_notifications", "arguments": { "unreadOnly": true, "limit": 10 } }

list_scenarios#

The scenarios you may start: your own plus the shared catalog, each with how many steps and questions its author expects. A scenario's protocol is not in this list; it is readable through get_doc while a run of it is active. Read-only.

No parameters.

{ "name": "list_scenarios", "arguments": {} }

get_scenario#

Read the scenario run on a map: which scenario it is, every answer collected so far under collected, the checklist keys still outstanding and the stage to act on next. Call it first whenever a person is in a scenario. A finished run is still reported, with active false.

  • boardId (uuid, required).
{ "name": "get_scenario", "arguments": { "boardId": "BOARD_ID" } }

ask_user#

Compose several questions at once and get them back as a numbered list to put to the person in your own reply. There is no form here, because an MCP client has no panel to draw one. It writes nothing to any map.

  • questions (array of 1–12, required) — each is `{ key, question, hint?,

kind, options? }, wherekindis"text"or"choice"and a choice carries up to 6options`.

{
  "name": "ask_user",
  "arguments": {
    "questions": [
      { "key": "scope", "question": "Rebuild the whole map or just this branch?", "kind": "choice", "options": ["Whole map", "This branch"] }
    ]
  }
}

Writing tools#

create_board#

Create a new empty map, optionally nested under one you own. Maps nest at most 8 deep. A new map arranges itself, so build it with structure alone and never send a position to a map you just made. It is refused, and nothing is created, when you have used every map your plan includes. The reply is the new map's id and name, and its desc when one is set.

  • name (string, required).
  • metaDescription (string, nullable) — the one-line description of what

belongs on the map.

  • parentBoardId (uuid, nullable) — nest under a map you own.
{ "name": "create_board", "arguments": { "name": "Q3 planning" } }

update_board#

Rename a map or change its one-line description, and nothing inside it: no block, no link, no position. You must own the map, and a call that changes nothing is refused.

  • boardId (uuid, required).
  • name (string) — not blank.
  • metaDescription (string, nullable) — null clears it.
{ "name": "update_board", "arguments": { "boardId": "BOARD_ID", "name": "Q3 planning (final)" } }

apply_map#

Insert blocks and links into a map you can write to, atomically: one mmap.v1 document, and nothing is written if any part of it is invalid. Send a title and let the map decide position, size and colour, and put a parent on a node instead of drawing a link for ordinary tree structure. The document's fields are on The .mmap format. The reply names the id of every block and link created.

  • boardId (uuid, required).
  • document (mmap.v1 document, required) — version, kind

("fragment" to add to an existing map), nodes and edges.

  • offset ({ dx, dy }) — where your own layout should land. It is ignored

when the map arranges what you sent, which is the default.

One call takes up to 500 nodes and 1000 links, and a document over either is refused whole. To retry safely after a lost connection, send an Idempotency-Key HTTP header on the first attempt and the same value on the retry: within 5 minutes a repeat returns the original result, with the same ids, and writes nothing a second time. The value is yours alone. The server keeps it in memory, so a restart clears it. Only a document that was applied is kept: a refused one is not, so after fixing it you can send it again with the same value. Without the header a retried document is written again in full.

{
  "name": "apply_map",
  "arguments": {
    "boardId": "BOARD_ID",
    "document": {
      "version": "mmap.v1",
      "kind": "fragment",
      "nodes": [
        { "ref": "n1", "title": "Ship the beta" },
        { "ref": "n2", "title": "Write the changelog", "parent": "n1" },
        { "ref": "n3", "title": "Announce it", "parent": "n1" }
      ],
      "edges": [
        { "ref": "e1", "source": "n2", "target": "n3", "kind": "goal", "label": "leads to" }
      ]
    }
  }
}

update_block#

Update fields on one existing block you can write to.

  • id (uuid, required) — from get_board.
  • patch (object, required) — any of title, metaDescription, content,

positionX and positionY, width, height and maxHeight, color, bgColor and their light and dark pairs, blockVariant ("default" | "parent" | "portal" | "image"), styleInherited, isActive, isTextExpanded, linkedBoardId, plus two fields that exist only here. appendContent adds text to the end of the body without reading or resending it. parentId re-hangs the block under another one and removes the solid links that held it. Send content or appendContent, never both: together they are refused, and appendContent is not idempotent, so a retried call appends the text twice.

  • return ("minimal" | "id" | "full", default "minimal") — how

much of the patched block comes back; "minimal" reads the fields you patched back off the saved row, because a size you sent is not always the size that landed.

A position lasts only until the map's links next change when the map keeps itself arranged; get_board reports that as arrange.keepsArranged.

{ "name": "update_block", "arguments": { "id": "BLOCK_ID", "patch": { "appendContent": "\n\nDone 2026-09-10." } } }

update_blocks#

Patch many blocks on one map in a single call: the batch form of update_block, for applying one convention such as a colour or a status. It takes the same fields except appendContent and parentId, which are refused here. It is not atomic: an id that is not on the map is reported under missing and the rest are applied, so re-run the items that missed.

  • boardId (uuid, required).
  • items (array of { id, patch }, 1–100, required) — one item per block;

naming the same id twice is refused and nothing is written.

  • return ("minimal" | "id" | "full", default "minimal").
{
  "name": "update_blocks",
  "arguments": {
    "boardId": "BOARD_ID",
    "items": [
      { "id": "BLOCK_ID", "patch": { "bgColor": "amber" } },
      { "id": "OTHER_BLOCK_ID", "patch": { "bgColor": "amber" } }
    ]
  }
}

delete_block#

Delete one block you can write to, and its links with it. Its children are not deleted or re-hung: they lose their last link upward and become top-level, and the reply names them. To remove a whole branch, delete its blocks.

  • id (uuid, required).
{ "name": "delete_block", "arguments": { "id": "BLOCK_ID" } }

create_edge#

Connect two existing blocks you can write to without recreating either. The kind decides the map's structure: "default" is a solid link and makes the source the parent of the target, so use it only to add a level to the tree. "goal" (dashed) is right for almost everything else: sibling to sibling, branch to branch, a reference back up, a second parent. A link from a block to itself is refused, and so is a second link between the same two blocks: change the existing one with update_edge.

  • sourceId (uuid, required).
  • targetId (uuid, required).
  • label (string, up to 255 characters).
  • kind ("default" | "goal").
  • color (string, nullable).
  • direction ("none" | "start" | "end" | "both").
  • weight (number, 0–10) — the declared strength; it becomes the line's

thickness.

{
  "name": "create_edge",
  "arguments": { "sourceId": "BLOCK_ID", "targetId": "OTHER_BLOCK_ID", "kind": "goal", "label": "depends on" }
}

update_edge#

Update fields on one existing link you can write to, without touching either block.

  • id (uuid, required) — the link's own id, from get_graph at

"full", get_edge_signal or get_board with format "tree".

  • patch (object, required) — any of label, kind, color, direction,

weight.

{ "name": "update_edge", "arguments": { "id": "EDGE_ID", "patch": { "weight": 3 } } }

delete_edge#

Delete one link you can write to.

  • id (uuid, required).
{ "name": "delete_edge", "arguments": { "id": "EDGE_ID" } }

arrange_board#

Lay the whole map out again in the shape its owner chose, and save where every block landed. It is the one repair for a map built over several apply_map calls, where a later branch was placed against a structure that was still incomplete. It moves blocks and nothing else, and there is no argument for the shape. Check arrange.keepsArranged in get_board first: when it is true the map has already arranged itself after every write and this usually reports that nothing moved. It is the one way to put existing siblings in a chosen order, because order among siblings comes from where they sit and never from links.

  • boardId (uuid, required).
  • siblingOrder (array of 1–30 groups, each an array of 2–50 block ids) —

blocks that share one parent, or several top-level blocks, in the order they must read. Every id must be on the map, or nothing moves. The order holds through every later arrange.

{ "name": "arrange_board", "arguments": { "boardId": "BOARD_ID" } }

create_skill#

Write a new private skill or scenario. A skill is standing instruction text that changes how your maps get built. A skill written here applies on no map until the person chooses its maps in the app (Settings → Skills), so it does not show in get_board or get_doc yet. A scenario is an interview that builds a map: its body is the protocol to follow when it runs, and it needs a checklist of keys to collect. It is always private to you. Ask the person what should be in it before you invent one.

  • slug (string, required) — lowercase letters, digits and hyphens, 2–63

characters; a name a built-in rulebook section already uses is refused. A name the shared catalog already uses is refused for a skill; pick another.

  • title (string, required).
  • body (string, required).
  • kind ("skill" | "scenario", default "skill").
  • meta (object, nullable) — for a scenario, collect (the keys, at least

one) and estimatedSteps, and optionally estimatedQuestions and uses (the rulebook sections it builds on).

  • agents (array, nullable) — limit a skill to some of the app's own chat

agents; the accepted values are in the tool's schema. Leave it out for every agent, and never send an empty list. A scenario cannot be limited.

{
  "name": "create_skill",
  "arguments": { "slug": "claim-not-topic", "title": "Name nodes as claims", "body": "Every node title is a claim, never a bare topic word." }
}

update_skill#

Edit one of your own skills by its id. Only the fields you send change. This works only on a skill that applies on no map, such as one you just wrote with create_skill before the person has chosen its maps, and then any field may change. A skill that is in force on any map, and every scenario, is refused with a message saying the person can change it in the app (Settings → Skills); to propose a different scenario, create a new one with create_skill. kind cannot be changed after creation.

  • id (uuid, required) — from list_skills.
  • patch (object, required) — any of slug, title, body, meta, agents;

agents as null puts a skill back on every agent. A new slug must not be one the shared catalog uses.

{ "name": "update_skill", "arguments": { "id": "SKILL_ID", "patch": { "body": "Updated wording." } } }

delete_skill#

Delete one of your own skills or scenarios by its id. Maps already built with it are untouched, and a run of a deleted scenario ends with it. There is no undo, so confirm with the person first.

  • id (uuid, required).
{ "name": "delete_skill", "arguments": { "id": "SKILL_ID" } }

start_scenario#

Begin a scenario run on a map you own. Starting again on a map with an active run resumes it instead of failing. It returns the run and a kickoff instruction to follow. When the same scenario has already run to the end on this map, the reply carries that run's answers under previousRun, so you can ask only whether they still hold. Ask the person before you start one: an interview is many turns of their time.

  • boardId (uuid, required).
  • slug (string, required) — from list_scenarios.
{ "name": "start_scenario", "arguments": { "boardId": "BOARD_ID", "slug": "project-kickoff" } }

advance_scenario#

Record what the latest answer collected and move the interview forward by one step. Call it once per answer, before you ask the next question. Collecting every key does not end the run: the reply then says the stage is "building", which means build the map and only then call this once more with complete true, the call that closes the run.

  • boardId (uuid, required).
  • collected (object of string to string, required) — keys must be the

ones the scenario's checklist declares; each value is a short summary of the answer.

  • complete (boolean) — set it once every key is in and the map has been built

from them.

{
  "name": "advance_scenario",
  "arguments": { "boardId": "BOARD_ID", "collected": { "goal": "Ship v2 by October" } }
}

mark_notifications_read#

Mark notifications read: the write half of list_notifications, and the only thing that clears the unread badge in the app. Call it after you have shown the person what you read, never before. Both ids and all together, or neither, is refused. The reply says what actually moved: marked, count and unchanged, the ids that were already read or are not yours.

  • ids (array of uuid, 1–100) — exactly the rows you showed.
  • all (true) — clear everything unread.
{ "name": "mark_notifications_read", "arguments": { "ids": ["NOTIFICATION_ID", "OTHER_NOTIFICATION_ID"] } }

Delegation#

delegate#

Hand a task to an assistant working inside one branch of a map, or on a whole map you own, and get its answer back in the same call. It is synchronous and reads nothing but your task: one call is one turn, so put everything the assistant needs into task. On a map you own, the turn is also written into that place's chat — your task, the assistant's messages and how the run ended — so you can read it there afterwards; the answer you get back is the same either way. The assistant reads and changes the place you named and nothing else, and it works on your own maps only: a block on a map you do not own is refused exactly like an id that names nothing. A worker on a branch can rewrite, link, unlink and delete blocks inside it and add blocks under a block of that branch; it cannot start a new branch beside it or re-arrange the map. A worker on a whole map can do all of that anywhere on it, including adding top-level blocks, so do not run one beside another call on the same map. You get reply (its final message), changed (how many changes it committed), agent, branch and truncated.

This is the one tool that can spend your account's AI allowance, because the assistant's thinking runs on this server and not in your client. It runs on the model your account, or the key, is set to: a model included in your plan spends the allowance, and one that runs on your own provider key is not metered (the call still counts as one MCP call). A refusal names a percentage and a reset date.

A turn that builds can take minutes, and the reply is an event stream that stays open until the result. Send a progressToken in the request's _meta and you get a notifications/progress whenever the worker does something and in any case about every 20 seconds, which a client with its own timeout can reset on; once the worker has produced a line, each carries a short message saying what it just did (absent until then). Without a token the stream carries keep-alive comments. One turn runs for at most 5 minutes, plus the step it is on when the limit arrives. A turn stopped there says so, and every change it already made stays on the map. If your client gives up anyway the work goes on, so read the map with get_board instead of calling delegate again, which would start a second turn. Two calls aimed at the same branch overwrite each other's edits without warning.

Name the place in exactly one of three ways; a call that names none, or more than one, is refused before anything is read.

  • branch (uuid) — the id of a branch's root block; the map is worked out from the

block and nothing is created or left behind to revoke.

  • board (uuid) — the id of a map you own, to work on the whole of it.
  • agent (string) — the name or id of an assistant seated on a branch, from

list_agents. The branch comes off that assistant's row.

  • task (string, 1–4000 characters, required) — everything the assistant

needs.

Naming a place never grants access you did not already have, and delegation works on maps you own only: a map you were invited to — in any role — is refused in all three forms before any work starts or anything is charged, as an id that names nothing is. To put an assistant on a map you were invited to, connect it with a key bound to that map; such a key works on the map directly and cannot delegate.

{ "name": "delegate", "arguments": { "branch": "BLOCK_ID", "task": "Tidy this branch: merge duplicate blocks, fix stray links." } }