Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MCP server reference

cdno-mcp is a Model Context Protocol server that exposes your vault to AI clients (Claude Desktop, Claude Code, Kiro, Gemini CLI, …). It runs the same domain engine as the CLI, so anything the assistant does goes through the same rules and lands in the same Markdown files. To wire it up, see Connect to Claude.

Transport and vault selection

Two binaries serve the same tool catalogue:

  • cdno-mcp speaks JSON-RPC over stdio — the client launches it as a subprocess. It opens the vault named by CUADERNO_VAULT_PATH, or, if that’s unset, discovers one from its working directory (the same rule as the CLI).
  • cdno-mcp-server speaks MCP Streamable HTTP for remote clients — see The HTTP server for its flags and security model.

The tool surface

The server advertises 66 tools. This reference groups them by purpose:

GroupPageWhat’s in it
Context-gathering readsContext-gathering toolsOrientation, project/portfolio/weekly context, search, reads, lint, triage list
WritesWrite toolsLog, capture, file evidence, project/action/milestone/waiting edits, note revision and renaming, commitments, tracking, daily/weekly sections
Creation & lifecycleCreation and lifecycle toolsCreate projects/portfolios/questions/stewardships, link portfolios, park/activate/complete/drop, status transitions

Every tool returns typed JSON; the shapes mirror the CLI’s --json output, so a client gets the same structures whichever surface it uses.

Focus

Four of the write tools (pause_action, switch_action, switch_unplanned_action, resume_action) move the one “what I am on” slot that current_focus reads, and eighteen write results carry a focus field so the focus rides along without a second call. The server never refuses a write for being outside the focus; it refuses a second start (or a switch to the focused action, or a resume_action over a different focus) with a coded focus_open rejection, and pause_action or resume_action with nothing to act on with no_focus. See Write tools and Focus.

Conventions

  • Slugs, not paths. Tools take slugs (surrogate-model), matching the CLI.
  • Substring matching for completing actions/milestones and resolving waiting-on items, exactly as on the CLI.
  • The same rules apply. The five-project cap, append-only notes, auto-logged project-state history, and commitments aggregation all hold — the MCP server is not a back door around them.

When a call is rejected

Cuaderno splits failures along the line MCP itself draws, and the distinction is worth knowing because the two arrive in different places in the response.

A rejection you can act on comes back as a tool result with isError: true — not as a JSON-RPC error. The response carries no error member at all, and the payload is the single text content item, JSON-encoded exactly as a successful result’s payload is:

{ "isError": true, "content": [{ "type": "text", "text": "…" }] }

Decoding that text gives the rejection itself. This is real output from complete_action with a query matching two bullets:

{
  "code": "ambiguous_action",
  "details": {
    "candidates": [
      "Draft the methods section (deep)",
      "Revise the methods appendix (light)"
    ],
    "query": "methods",
    "slug": "ablation-study"
  },
  "message": "ambiguous action match for 'methods' on project 'ablation-study': [\"Draft the methods section (deep)\", \"Revise the methods appendix (light)\"]"
}

Three fields, and each has a job:

FieldFor
codeA stable snake_case discriminant to branch on — ambiguous_action, state_too_long, project_cap_reached, field_not_settable, …
messageThe human-readable sentence, for showing a person
detailsThe structured fields needed to recover — candidates to pick from, chars/max to condense against, active_projects to park one of

details is the part that matters for automation. An ambiguous query hands back its candidates as an array, so a client picks from a list rather than parsing them out of a sentence.

Five project codes carry details a client is likely to branch on:

CodedetailsWhen
project_not_activeslug, status (parked, completed or dropped), and closed when the map carries a dateA parked status means a focus verb (start_action, resume_action, switch_action, start_unplanned_action, switch_unplanned_action) or park_project was refused: starting or picking up work needs an active map, so activate it first only if the user wants to work on it now. Every other project-body verb (add_action, update_project_state, add_milestone, …) works on a parked map. A closed status means a body verb on a completed or dropped project, or a close repeating the outcome it already has: the project is finished, unless the user wants it back.
project_cap_reachedcurrent, max, active_projectsA project would begin to count with the counted projects at the cap (activate_project, or taking back an active project with hand_off_project). current and active_projects are the projects that count, active and held by no one else, so each listed slug is one whose parking, completion or dropping frees a slot: offer those, or leave the project as it is.
ambiguous_projectslug, candidates: [{path, note_type}]The slug names maps in more than one of projects/, projects/_parked/ and projects/_done/<year>/. Nothing can pick for you: keep one of the files by hand.
project_in_focusslug, actionA project was to be handed off while action, one of its actions, is in focus: a project is not handed off while you are in the middle of its work. Pause the action first, if the person wants to hand it off now.
empty_field (for held_by)field: "held_by", value, remedy: "take_back"The holder given names the person or nobody (me, tbd, someone, …, the vault’s [projects] holder_sentinels). If they cannot name who has the project, it is theirs: take it back rather than hand it off. Any other empty_field carries field alone.

The test is simply whether a different argument would change the outcome. A slug that matches no note is a rejection (not_found), and its message carries the list of slugs that do exist. A note missing the section a tool writes into is a rejection. So is a name that is already taken.

A mechanical failure stays a JSON-RPC error (-32603): a disk that would not write, permission denied, a contended write lock, an index that would not answer, a transaction that rolled back. Nothing the caller passed can fix those, so there is nothing to branch on. Malformed calls — an unknown tool, unparseable arguments — are protocol errors too (-32602), per the spec.

The practical upshot for a client: read isError on the result, not just the presence of error. A rejection is a normal, expected answer to a call that was itself well-formed.

Slug rejections

An explicit slug on a create tool (Creation and lifecycle) is refused with one of four codes, each leaving the vault untouched. They are the same objects the CLI prints under --json.

CodedetailsWhen
invalid_slugslug, whyThe slug breaks the shape rule (lowercase a-z, 0-9, single -, at most 50 characters, not YAML-typed); why names the part. Fix the shape, or ask the person for another name.
slug_takenslug, holder, kind (stem|folder|alias)Another note already holds the name. If holder is the note you meant, use it; otherwise ask the person for another name. alias means the name is a former name another note still lists. Do not suffix it yourself.
portfolio_slug_mismatchquestion_slug, slugcreate_portfolio with question set to a question’s slug and a slug that is not that slug. Drop slug or pass the question’s.
slug_given_twicefieldcreate_custom_note with slug and a slug entry in fields. Pass one of them.

Building skills on top

Multi-step rituals (a morning orientation, a guided weekly review) are best wrapped as Claude skills that call these tools in sequence. See Using with Claude skills.