Navigation
Copy page

MCP tools

On this page

cairn provides fourteen MCP tools. They are the wire surface an agent calls to plan in the task graph and resume into it: one observe tool, eight writes, and five reads. Each entry below mirrors the tool's registered schema, so the page tracks the served surface and does not drift from it.

Every tool carries a capability gate in its metadata: :cairn/observe, :cairn/write, or :cairn/read. The gate is the boundary, not the tool name — read and write are decided by capability, not by which tools are exposed (see Reads, writes, and capabilities). A call whose gate is not granted runs nothing.

Errors travel on two channels. Protocol faults — malformed JSON-RPC, an unknown tool, a missing required argument the schema rejects — surface as transport-level errors. Tool-execution failures surface as a normal tool result with isError: true, carrying the verbatim text the model reads and self-corrects from: name is required., No current task; create or select one first., and the rest quoted in the entries below. Every returned and error string on this page is copied from source; treat the punctuation as load-bearing.

Most tools resolve a target task the same way: the task_id argument when given, otherwise the current task pointer. When neither names a task, a current-scoped call fails with No current task; create or select one first. Task arguments accept a bare slug or a depot-prefixed one; the depot prefix is stripped before lookup.

Tools by capability

Tool Capability One line
observe :cairn/observe Record a freeform observation on the current task.
task_create :cairn/write Create a top-level task.
task_fork :cairn/write Create a child task and make it current.
task_link :cairn/write Add a typed edge to a target task.
task_sever :cairn/write Remove a typed edge to a target task.
task_set_metadata :cairn/write Set a free key/value on a task.
task_update_status :cairn/write Set a task's status.
handoff :cairn/write Scaffold a resumable handoff.
task_query_write :cairn/write Run a TQ query that may mutate or define views.
task_search :cairn/read Full-text search over observations.
task_get :cairn/read Read computed task state.
timeline :cairn/read Recent events for a task.
task_query :cairn/read Run a read-only TQ query.
task_bootstrap :cairn/read Orient on a task in one call.

Tool reference

The entries below run observe, then the write tools, then the reads. Each one uses the same field order: summary, capability gate, signature, parameters, returns, semantics, example, see also.

observe

Record a freeform observation on the current task. This is the cheapest call and the work heartbeat.

Capability gate: :cairn/observe.

Signature: observe(text, task_id?)

Parameter Type Required Description
text string yes The observation. Blank text fails with text is required.
task_id string no Task to observe on. Defaults to the current task.

Returns: Observed on <task>. Records one observation event.

Semantics: an observation never moves the current pointer; it appends to the task you are already on (or to task_id). Observations are the corpus task_search ranks over.

Example: observe(text="bm25 ranking confirmed against t/cairn-search.lisp") on task 2026-06-22-tune-searchObserved on 2026-06-22-tune-search.

See also: The cairn-methodTasks, observations, and handoffs.

task_create

Create a top-level task. Adopts the new task as current only when no current task is set.

Capability gate: :cairn/write.

Signature: task_create(name, description?)

Parameter Type Required Description
name string yes Descriptive task name. Blank fails with name is required. Slugified and stamped with today's UTC date; a name too thin to slugify is rejected.
description string no Longer description stored on the task.

Returns: Created <slug>. Records one task.create event.

Semantics: the slug is YYYY-MM-DD-<slugified-name>. Minting is idempotent over the date prefix — a name you already date-prefixed is stripped back to its core before today's prefix is stamped, so the prefix stays singular. The task becomes current only if none was set; an existing pointer is left undisturbed.

Example: task_create(name="tune search ranking") with no current task → Created 2026-06-22-tune-search-ranking.

See also: Plans, phases, and the frontierTasks, observations, and handoffs.

task_fork

Create a child task linked to a parent and make the child current. The default edge is phase-of, so forking builds a plan's phase backbone.

Capability gate: :cairn/write.

Signature: task_fork(name, from?, edge_type?, description?)

Parameter Type Required Description
name string yes Child task name. Blank fails with name is required.
from string no Parent slug. Defaults to the current task; with neither, fails with No parent task; pass from or select a task first. The parent argument is from.
edge_type string no One of phase-of, depends-on, related. Defaults to phase-of. An out-of-enum value fails with "<value>" is not a valid edge type; expected one of phase-of, depends-on, related.
description string no Longer description for the child.

Returns: Forked <child> from <parent> (<edge>). Records task.create for the child and task.fork on the parent.

Semantics: task_fork always switches the current pointer to the new child. A task cannot fork from itself; that fails with A task cannot fork from itself.

Example: task_fork(name="add prefix query support") while current on 2026-06-22-tune-searchForked 2026-06-22-add-prefix-query-support from 2026-06-22-tune-search (phase-of).

See also: Plan and resumePlans, phases, and the frontierEdges, statuses, and fields.

Create a typed edge from the current task to a target task.

Capability gate: :cairn/write.

Signature: task_link(target_id, edge_type, task_id?)

Parameter Type Required Description
target_id string yes Edge destination slug. Blank fails with target_id is required.
edge_type string yes One of phase-of, depends-on, related. An out-of-enum value fails with "<value>" is not a valid edge type; expected one of phase-of, depends-on, related.
task_id string no Edge source. Defaults to the current task.

Returns: Linked <src> -> <target> (<edge>). Records one task.link event.

Semantics: links the resolved source to target_id. A task cannot link to itself; that fails with A task cannot link to itself. Use depends-on for lateral ordering inside a plan and related for cross-references; phase-of names structural parentage. See Edges, statuses, and fields for the closed enum.

Example: task_link(target_id="2026-06-22-tokenizer-fix", edge_type="depends-on") on 2026-06-22-add-prefix-query-supportLinked 2026-06-22-add-prefix-query-support -> 2026-06-22-tokenizer-fix (depends-on).

See also: task_severEdges, statuses, and fieldsThe task graph.

task_sever

Remove a typed edge from the current task to a target task.

Capability gate: :cairn/write.

Signature: task_sever(target_id, edge_type, task_id?)

Parameter Type Required Description
target_id string yes Edge destination slug. Blank fails with target_id is required.
edge_type string yes One of phase-of, depends-on, related. An out-of-enum value fails with "<value>" is not a valid edge type; expected one of phase-of, depends-on, related.
task_id string no Edge source. Defaults to the current task.

Returns: Severed <src> -> <target> (<edge>). Records one task.sever event.

Semantics: the inverse of task_link — it removes the named edge. Severing is recorded as its own event, so the graph's history shows when an edge was dropped.

Example: task_sever(target_id="2026-06-22-tokenizer-fix", edge_type="depends-on") on 2026-06-22-add-prefix-query-supportSevered 2026-06-22-add-prefix-query-support -> 2026-06-22-tokenizer-fix (depends-on).

See also: task_linkThe task graph.

task_set_metadata

Set a free key/value on a task — for example display-name, phase, objective, acceptance, or tags.

Capability gate: :cairn/write.

Signature: task_set_metadata(key, value, task_id?)

Parameter Type Required Description
key string yes Metadata key. Blank fails with key is required.
value string yes Value to store. Required by the schema; a null value is coerced to the empty string rather than rejected.
task_id string no Task to annotate. Defaults to the current task.

Returns: Set <key> on <task>. Records one task.set-metadata event.

Semantics: metadata is open — any key is accepted, and setting an existing key overwrites it. Most keys are read back as open text. A handful map to typed queryable fields (for instance display-name); the rest stay free-form.

Example: task_set_metadata(key="acceptance", value="prefix queries return ranked snippets") on 2026-06-22-add-prefix-query-supportSet acceptance on 2026-06-22-add-prefix-query-support.

See also: Edges, statuses, and fieldsTasks, observations, handoffs.

task_update_status

Set a task's status. Idempotent.

Capability gate: :cairn/write.

Signature: task_update_status(status, task_id?, reopen?)

Parameter Type Required Description
status string yes One of open, active, completed, abandoned, blocked. Missing fails with status is required.; out of enum fails with "<status>" is not a valid status; expected one of open, active, completed, abandoned, blocked.
task_id string no Task to update. Defaults to the current task.
reopen boolean no When true and status is omitted, sets the status to active to revive a completed task.

Returns: <task> is now <status>. Records one task.update-status event.

Semantics: status is lower-cased before validation. Re-applying the same status is a no-op event-wise but still returns the line. Marking a phase completed is what advances the plan frontier (see Plans, phases, and the frontier).

Example: task_update_status(status="completed") on 2026-06-22-tokenizer-fix2026-06-22-tokenizer-fix is now completed.

See also: Edges, statuses, and fieldsPlans, phases, and the frontier.

handoff

Scaffold a resumable handoff for the current task. The summary is the load-bearing field; an optional path points at a written note.

Capability gate: :cairn/write.

Signature: handoff(summary, path?, task_id?)

Parameter Type Required Description
summary string yes One-line summary of where the work stands. Blank fails with summary is required.
path string no File path for the note. Defaults to a timestamped file under the task scratchpad.
task_id string no Task to hand off. Defaults to the current task.

Returns: Handoff scaffolded for <task> at <path> — no trailing period. Records one handoff.create event.

Semantics: handoff is deterministic. It mints a path, writes a skeleton (frontmatter, a computed state snapshot, and empty ## Recent work / ## Next steps sections), records the event, and returns the path. It never drives an authoring turn — the caller overwrites the skeleton with the rich body. An existing file at the path is left in place. The richer, interactive authoring flow is the /handoff slash command, which is separate from this tool.

Example: handoff(summary="prefix queries land; tokenizer fix is next") on 2026-06-22-add-prefix-query-supportHandoff scaffolded for 2026-06-22-add-prefix-query-support at <scratchpad>/2026-06-22_14-30-05_prefix-queries-land-tokenizer-fix-is-next.md

See also: Tasks, observations, and handoffsSlash commands.

task_query_write

The write surface for the TQ query language, accepting the !-forms that task_query refuses.

Capability gate: :cairn/write.

Signature: task_query_write(query)

Parameter Type Required Description
query string yes A single TQ s-expression. Accepts mutating steps (:set-status!, :set!, :link!, :unlink!) and the view forms define! / undefine!.

Returns: the rendered query result as text. A mutation step records one append-only event per task it touches and returns the set, so a trailing (:count) reports how many tasks were affected. define! records the named view in the durable log.

Semantics: mutations converge on re-run — the same query applied twice reaches the same state. Sub-query operands inside set operations always evaluate read-only, even on this surface. (define! "name" Q) records Q as a reusable named view resolvable as (query "name"); a user view shadows a built-in of the same name. Read-only queries also run here, but prefer task_query for them.

Example: task_query_write(query="(-> (query \"plan-frontier\") (:set-status! \"active\"))") → marks every ready phase active and returns the set.

See also: The TQ languageViewsReads, writes, and capabilities.

Full-text ranked search over observations. Returns matching tasks with a snippet.

Capability gate: :cairn/read.

Signature: task_search(query, limit?, task_id?)

Parameter Type Required Description
query string yes Free-text search. Blank fails with query is required. Sanitized into an FTS5 MATCH expression.
limit integer no Maximum results. Defaults to 10, clamped to a maximum of 50.
task_id string no Restrict the search to one task's observations.

Returns: ranked rows (task slug plus a snippet), or No matching observations for "<query>". when nothing matches. A query the engine cannot parse fails with Could not parse the search query "<query>". Records no event.

Semantics: results are BM25-ranked. The sanitizer is forgiving so identifiers and paths search literally: a token carrying any of the special characters " ( ) : - . / ^ * + is phrase-quoted (so tools.lisp:45-78 and depends-on match as written), a trailing * stays a prefix query, and tokens join with implicit AND. A query that sanitizes to nothing usable returns the no-match line rather than erroring.

Example: task_search(query="prefix query*") → ranked observations across tasks whose text contains a token starting with prefix and the token query.

Deferred scope. A scope parameter exists in the runner (defaulting to observations) but is not in the registered wire schema, so tool-call search is deferred. Treat observation search as the advertised contract.

See also: The task graphTasks, observations, and handoffs.

task_get

Read computed task state: status, description, parent, children, edges, metadata, and recent observations.

Capability gate: :cairn/read.

Signature: task_get(task_id?)

Parameter Type Required Description
task_id string no Task to read. Defaults to the current task.

Returns: a rendered state block, or No task <slug>. when the slug is unknown. Records no event.

Semantics: the state is computed from the event log, not stored as a row of flags. The block carries the slug and status, then any description, parent, children, outgoing edges, metadata pairs, and up to five most recent observations. When more than five observations exist, a final pointer line — … N earlier observations — call timeline with full=true, types=observation to read them all — names how to read the rest. Each edge line prints the edge type first, then the destination slug. It is the same snapshot embedded at the top of task_bootstrap.

Example: task_get(task_id="2026-06-22-add-prefix-query-support")

text
2026-06-22-add-prefix-query-support  [active]
  add prefix query support
  parent: 2026-06-22-tune-search
  edges:
    depends-on 2026-06-22-tokenizer-fix
  recent:
    - prefix queries land; tokenizer fix is next

See also: timelinetask_bootstrapThe task graph.

timeline

Recent events for a task, most recent first.

Capability gate: :cairn/read.

Signature: timeline(task_id?, types?, full?, before_seq?, after_seq?, limit?)

Parameter Type Required Description
task_id string no Task to read. Defaults to the current task.
types string no Comma-separated event-type filter, validated against the live vocabulary. Omitted or blank means every type.
full boolean no When true, render each event verbatim — a header line plus the untruncated multi-line body — instead of the one-line digest. Defaults to false.
before_seq integer no Exclusive upper seq bound; also the page-back cursor printed when output is truncated.
after_seq integer no Exclusive lower seq bound.
limit integer no Number of events. Defaults to 20.

Returns: a header line <task> — last <N> events followed by one line per event (sequence number, event type, and a one-line digest of its payload). With full=true, each event instead renders as a header <seq> <type> (<YYYY-MM-DD HH:MM> UTC) and its verbatim body, block-indented when multi-line. Output is capped at a soft character ceiling: when the next event would exceed it, emission stops and prints … truncated at <N> chars; <M> events remaining; pass before_seq=<seq> to continue. Records no event.

Semantics: where task_get shows computed state, timeline shows the raw event stream that produced it — observation, task.create, task.fork, task.link, task.sever, task.update-status, task.set-metadata, handoff.create, and so on. By default each digest is collapsed to a single line and truncated; full=true renders bodies verbatim, making timeline the way to read a task's complete observation history. Filter to one or more event types with types — an unknown type errors with a nearest-match suggestion rather than silently returning nothing. Window or page with before_seq (exclusive) and after_seq (exclusive): feeding the before_seq cursor from a truncated page back into the next call returns the older events with no gap and no overlap. Read it to reconstruct how a task got where it is.

Example: timeline(task_id="2026-06-22-add-prefix-query-support", limit=3)

text
2026-06-22-add-prefix-query-support — last 3 events
  7  handoff.create  prefix queries land; tokenizer fix is next → ...
  6  task.update-status  → active
  5  observation  bm25 ranking confirmed against t/cairn-search.lisp

Example: timeline(types="observation", full=true) renders every observation on the current task verbatim — full multi-line bodies, no 140-character digest cut — newest first.

See also: task_getEvents, projection, and reconcile.

task_query

Run a read-only query over the task graph and return text. The query is a single s-expression in cairn's query language (TQ).

Capability gate: :cairn/read.

Signature: task_query(query)

Parameter Type Required Description
query string yes One TQ s-expression. A Lisp form, not a bare word — names and probes are wrapped in parens.

Returns: the rendered query result as text. Every form yields a value or a structured error, never a crash or a silent no-op. Records no event.

Semantics: a query is a SOURCE optionally threaded through STEPs with ->, for example (-> (current) (:follow :phase-of) (:ids)). Transformer steps compose node-set to node-set; shaper steps such as (:select :slug :status), (:count), and (:ids) terminate into a value. Pass a reflective form on its own to learn the surface: (schema) lists the sources, steps, predicates, and write forms, with steps and predicates carrying their kinds and argument signatures — call it first; (views) lists named views, (fields) the queryable fields, (edges) the edge types. These are sources that list names, so project with (:select …). An unknown field, predicate, step, source, or edge errors with a nearest-match suggestion rather than a silent miss. Timestamps (created-ts, updated-ts, status-ts) are universal-time, so a date intent is one predicate — (on :updated-ts "2026-06-24"), (since …), (before …) — and an integer operand to a numeric comparison against a timestamp is read as Unix seconds. Run a named view by wrapping it as (query "plan") or (query "plan-frontier"); a bare word like plan is a query error, since it is not a source form. The !-forms — mutations and view definitions — are refused here; use task_query_write.

Example: task_query(query="(-> (current) (:follow :phase-of) (:select :slug :status))") → the current task's phase-of children with their statuses.

See also: The TQ languageViews.

task_bootstrap

Orient on a task in one call: its computed state, neighbors, open handoffs, and recent observations.

Capability gate: :cairn/read.

Signature: task_bootstrap(task_id?)

Parameter Type Required Description
task_id string no Task to orient on. Defaults to the current task; with neither, fails with No task to bootstrap; pass task_id or select a task first. An unknown slug fails with No task <slug>. and lists recent slugs.

Returns: the task_get state block, then any open handoffs. Records no event.

Semantics: task_bootstrap is the single-call session-start read. An explicit task_id switches the current pointer to that task — orienting on a task makes it current, so spawned sessions and the injected context agree on what is in focus. With no task_id, it orients on the current task and adopts it only when none is set; the pointer switches only after the task is found.

Example: task_bootstrap(task_id="2026-06-22-add-prefix-query-support") → the task's state block and its open handoffs.

See also: The current task pointerThe cairn-method.