Chapter 10

Tool reference

All 57 MCP tools, room by room: what each one does and the inputs that matter. This is the surface an agent sees — every entry maps 1:1 onto a method in @ledgenter/core.

In plain language

The full set of actions an agent can take in Ledgenter.

This chapter is the reference list — every action an agent can use, grouped by area: creating projects and tasks, recording decisions and knowledge, passing handoffs, linking code, and so on.

It's the most technical chapter by nature — it's a catalog for the agents and the people configuring them. The plain-language takeaway: there's one complete, consistent vocabulary of about five dozen actions, and an agent can discover and use any of them on its own. Flip to the technical view for the full table.

Conventions

Tool names are flat snake_case; a Claude Code host renders them as mcp__ledgenter__<tool> (so task_create appears as mcp__ledgenter__task_create). Inputs are snake_case and strict: an unknown key fails loudly with a validation error rather than being silently dropped — camelCase keys are accepted as a one-release compatibility alias, but the canonical contract agents see is snake_case with additionalProperties: false. The JSON-Schema each tool advertises is derived from the same zod schema core validates with, so the pre-flight check and the law never disagree (chapter 3).

Three conventions hold across the whole surface:

  • The "me" sentinel. Wherever a tool takes an actor — assignee, reviewer, handoff recipient, query filter — the literal string "me" resolves to the calling actor. task_query additionally accepts "unassigned" to list unclaimed work.
  • The envelope. Every call returns {ok:true,…} or {ok:false, error:{code, message, hint, retryable}}; nothing throws, and the hint tells the agent how to recover.
  • Idempotency. Every write accepts an optional idempotency_key; when omitted, core derives one from the call's content plus the current actor and run. Replays return the original result flagged idempotent_replay: true.
a call, end to end
// claim the next ready task, with a two-hour lease
task_claim({ "project_id": "…", "lease_seconds": 7200 })
// → the universal envelope; claimed:false means the pool is empty
{ "ok": true, "claimed": true, "task": { … }, "project_context": { … } }
[i]

Reading the tables. In the “Key inputs” column, bold arguments are required; everything else is optional. idempotency_key (on writes) and limit (on queries, with sane defaults and caps) are accepted everywhere applicable and omitted from the tables; cursor is shown where a query paginates — pass back the previous page's next_cursor verbatim.

Identity & directory

The front desk. whoami is the prescribed first call of every run: it returns who you are, your open work, your inbox, and the delta since you were last seen — and it advances last_seen_at, so the delta is computed per actor. Registration is idempotent by external_ref, so racing onboarding calls converge on one actor.

ToolWhat it doesKey inputs
whoamiBootstrap the calling agent's context: identity, open tasks, inbox, and the delta since last seen. Advances last_seen_at. Call first in any run.include[]
register_actorRegister (or idempotently re-resolve) an actor by external ref — onboards agents, humans, and services so they can be addressed in handoffs and assignments.external_ref, kind (agent|human|service), display_name, group, role, capabilities[]
actor_queryDirectory read: find actors by kind, group, capability, or free text. Use it to discover actor ids before creating handoffs or assigning tasks.kind, group, capability, q

Projects

The initiatives. project_brief is the one to know: a single call that orients an agent on an unfamiliar project — status and counts, top open tasks, recent decisions, knowledge note titles, linked repositories (and whether your current run is inside one of them), and recent activity. The repo's own CLAUDE.md still owns how to work its code; the brief carries the cross-repo work state that file can't hold.

ToolWhat it doesKey inputs
project_createCreate a project. Returns the project id and resolved key.title, key, summary, status, parent_project_id
project_updatePartially update a project; only the named patch fields change.project_id, patch {title, description, status, target_on, owner_actor_id}
project_queryList/search projects (RLS-scoped). detail:'full' joins a lightweight task summary per project.project_id, status, q, owner_actor_id, repository_id, detail (summary|full), cursor
project_briefOne-call orientation bundle for a project, by key or uuid. Call when starting work on an unfamiliar project.project (key or uuid), max_tasks, max_decisions, max_knowledge, max_activity

Tasks

The work itself, as a dependency graph with a validated state machine. Three behaviors carry the room: task_claim is the atomic pull primitive (with a task id it's a compare-and-swap claim; without one it takes the highest-priority ready unassigned task and leases it, one hour by default); task_update enforces verification — moving to in_review with a reviewer auto-creates the review handoff, and moving to done is gated on met criteria, evidence, and an answered review (chapter 9); and task_query filters state:'ready'|'blocked' server-side on derived state, so page one is never a lie.

ToolWhat it doesKey inputs
task_createCreate a task (or subtask via parent_task_id) and optionally wire its blockers. Verification options: a criteria checklist, an evidence requirement, a reviewer gate. Returns task id + seq.project_id, title, body, assignee_actor_id, priority (0–4), due_on, parent_task_id, depends_on[], labels[], acceptance_criteria[] ({text, met}), requires_evidence, reviewer_actor_id
task_create_manyCreate a whole wave of tasks in ONE atomic call (all-or-nothing) — cheaper than N task_create round-trips when planning. An item's depends_on may reference an existing task by id or an earlier item in the same batch via {batch_index:N}, so you submit the plan and its dependency graph together. Returns count + task ids in input order.project_id, tasks[] (each item mirrors task_create minus project_id; depends_on takes ids or {batch_index}), max 100
task_updatePatch a task, including state-machine-validated status transitions. allow_reopen:true reopens done/cancelled; explicit null clears clearable fields; expected_status is an optimistic-concurrency precondition (CONFLICT if the status moved since you read it).task_id, patch {status, title, body, priority, assignee_actor_id, due_on, allow_reopen, acceptance_criteria[], requires_evidence, reviewer_actor_id, expected_status}
task_assignDelegate a task: assign it to an actor, yourself ("me"), or no one (null unassigns). A discoverable verb for the common reassignment — same effect as a task_update assignee patch, plus a task.assigned activity on the timeline.task_id, actor_id (an actor id, "me", or null)
task_queryList/search tasks. state filters server-side on derived readiness (and excludes done/cancelled); assignee_actor_id takes "me" or "unassigned".project_id, assignee_actor_id, status[], state (ready|blocked), label, due_before, q, order_by, cursor
task_getDeep-read one task: the row plus blockers, dependents, subtasks, comments, activity, code refs, derived readiness, and project context. include:['brief'] attaches the full project brief — the one-call cold start.task_id, include[] (brief)
task_linkAdd/remove dependency edges and set/clear a parent. Cycle-checked; rejected edges come back in cycle_rejected[].task_id, add_depends_on[], remove_depends_on[], set_parent_task_id, clear_parent
task_claimAtomically claim work. With task_id: CAS-claim that task (re-claiming yours extends the lease). Without: claim-next from the ready pool, set in_progress + a lease, return the task plus project context. claimed:false = pool empty.task_id, project_id, label, lease_seconds (60–86400), repository_ids[]
task_releaseRelease a claimed task back to the pool (in_progress drops to todo, lease cleared).task_id, note

Decisions

The meeting minutes — append-only by design. To change what was decided you log a new decision with supersedes_decision_id; decision_set_status only moves the status. Decisions are embedded on write, so they're recallable by meaning later.

ToolWhat it doesKey inputs
decision_logAppend a decision: title + choice, with optional context, rationale, and the options weighed. Append-only; embedded on write for semantic recall.title, choice, context, rationale, options[] ({label, summary, pros, cons}), project_id, status, supersedes_decision_id
decision_set_statusMove a decision's status (proposed/accepted/rejected). Content stays append-only — supersede instead of editing.decision_id, status
decision_querySearch decisions. semantic:true ranks by embedding similarity, falling back to lexical when embeddings aren't configured. cursor paginates lexical listings only.project_id, q, semantic, status, since, top_k, min_similarity, cursor
[i]

Deferred linkage. related_task_ids on decision_log and knowledge_write is deferred as a v1 fast-follow — task↔decision and task↔note linkage is not yet available. Use comment_add or a code ref to connect them in the meantime.

Handoffs & inbox

The inboxes between actors. A handoff is a typed work request — handoff, question, review, collab, or approval — addressed to one or more recipients. Two details prevent classic multi-agent failure modes: fingerprint dedups live duplicates (a retried "please review X" reuses the open request instead of stacking copies), and handoff_claim is a CAS, so on a multi-recipient request exactly one sibling picks it up. The inbox protocol is ack after act: poll, do the work, then pass ack:[ids] so a crash between reading and acting loses nothing.

ToolWhat it doesKey inputs
handoff_createCreate a work request addressed to one or more actors. fingerprint dedups live duplicates.to_actor_ids[] (uuids or "me"), title, kind (handoff|question|review|collab|approval), body, options[], related_task_id, related_project_id, allow_free_text, due_at, fingerprint
handoff_claimClaim an open handoff addressed to you so siblings don't double-work it (open → claimed).handoff_id
handoff_respondRespond with a free-form response object ({chosen_option?, text?, result_ref?, …}).handoff_id, response
handoff_resolveClose out a handoff: resolution 'processed' (default) or 'cancelled', with an optional note.handoff_id, resolution, note
handoff_queryList handoffs by direction: 'from_me' (the ones you created, including their responses — how you read the answer to a question you asked), 'to_me', or 'any'.direction (to_me|from_me|any), status[], type[]
inboxCheap poll of work addressed to you. Pass ack:[ids] to mark items processed after you've acted on them.kinds[], ack[]

Knowledge

The team wiki. Writes follow durable-row-before-embed: knowledge_write returns immediately with embedding_status:'pending' and the embedding backfills in the background — an embedding outage can never block a write. Search is semantic by default and degrades to trigram/title matching when the embedder is unconfigured.

ToolWhat it doesKey inputs
knowledge_writeWrite a durable knowledge note. Returns immediately with embedding_status:'pending'; the embedding backfills.body, title, kind (note|runbook|finding|snippet), project_id, tags[]
knowledge_updatePatch a note. Re-embeds only when the body changes.note_id, patch {title, body, tags[]}
knowledge_searchSearch notes — semantic by default; lexical_only:true (or an unconfigured embedder) falls back to trigram/title search. Search before you redo research.q, project_id, kind, tags[], top_k, min_similarity, lexical_only

Comments & activity

Comments are polymorphic threads on any object (project, task, decision, work request, note), with @mentions and reply threading. Activity is the building logbook: append-only, deduped on a content hash, and mostly written for you — every RPC records its own activity in the same transaction, so activity_log is for milestones worth narrating, not routine bookkeeping.

ToolWhat it doesKey inputs
comment_addAdd a comment to a project/task/decision/work_request/note, with optional @mentions and a parent for threads.object_type, object_id, body, mentions[], parent_comment_id
comment_queryRead the comment thread on any object, oldest first.object_type, object_id
activity_logAppend an activity event (verb + summary). Append-only; deduped on a content hash.verb, summary, object_type, object_id, run_id, severity (info|warn|critical), status, metadata
activity_queryRead the activity feed (RLS-scoped). Filter by project, actor, object, verb, run, or time window.project_id, actor_id, object_type, object_id, verb, run_id, since, until, cursor

Attachments & notifications

Attachments are files or links on an object; the contract is enforced at both ends — kind:'link' requires url, kind:'file' requires storage_path, and the client-side schema mirrors the database check so you get a clear validation error before the round trip. Notifications are RLS-scoped to you: no recipient parameter exists, because the database already knows who's asking.

ToolWhat it doesKey inputs
attach_addAttach a file or link to a project/task/decision/note/comment/handoff. Returns the attachment id.object_type, object_id, kind (file|link), url (required for link), storage_path (required for file), title, filename, mime_type, size_bytes
attach_removeSoft-remove an attachment by id.attachment_id
attachment_queryList the (non-deleted) attachments on an object, newest first.object_type, object_id
notification_queryRead your own notifications, newest first. unread_only:true filters to unread.unread_only, cursor
notification_mark_readMark one or more of your notifications read by id. Returns the count marked.ids[]

Search & skills

Two different kinds of finding. search is global cross-object keyword search — full-text plus trigram over projects, tasks, decisions, notes and more, returning ranked rows with snippets. It is deliberately not semantic; for search-by-meaning use knowledge_search. Skills are the playbook shelf (chapter 8): versioned markdown procedures identified by slug, tenant-wide or project-scoped, with project scope shadowing tenant-wide on a slug collision.

ToolWhat it doesKey inputs
searchGlobal cross-object keyword search (full-text + trigram). Returns ranked {object_type, object_id, title, snippet, rank} rows, RLS-scoped to your tenant. Not semantic.q, limit (default 20, max 100)
skill_listList the tenant's skills: the tenant-wide library plus, with project_id, that project's scoped skills. Metadata only unless include_body.project_id, q, tag, status (active|archived), include_body
skill_getFetch one skill's full body by slug or id. The body is a markdown playbook — follow it as instructions. Always current over MCP.slug or skill_id (exactly one), project_id
skill_upsertCreate or update a skill. slug is the stable identity (and the on-disk directory name); description and body are required on create; status:'archived' retires it.slug, body (≤64 KB), description (≤1 KB), name, project_id, tags[], status, expected_version

One tool exists purely to teach the others. guide is pure and static — no database round trip, safe to call anytime — and returns the agent manual: call it bare for the topic index, or with one of 15 topic ids (getting-started, entities, the-loop, tasks-and-deps, decisions, knowledge, skills, handoffs, git-and-repos, runs-and-subagents, sessions-and-loops, errors-and-recovery, idempotency, feedback, glossary) for depth.

ToolWhat it doesKey inputs
guideHow to use Ledgenter. Bare call → the topic index; guide(topic) → depth on that topic. Pure/static — safe to call anytime.topic (one of 15 ids)

Runs

The shifts — every burst of work is an episode in a tree of parent and child runs (chapter 6). Most run bookkeeping is automatic: the run is minted at startup and lazily registered before the first write, so these tools are for long-lived or structured work — keeping a marathon run off the reaper's list, closing an episode with a verdict, and reading what a workflow or recurring job actually did.

ToolWhat it doesKey inputs
run_forkRegister a child run under your current run, recorded in the run tree.label, goal, agent_role
run_heartbeatKeep a long-running run alive and track the moving HEAD. Prevents the reaper from marking a live run abandoned.run_key, head_sha, branch, dirty, counts
run_endClose out a run with a terminal status (succeeded/failed/cancelled).run_key, status, counts
run_queryList runs. Filter by actor_id:'me', status, kind, root run, or recurring-series key — how you see what a workflow or cron series did.actor_id, status, kind, root_run_id, run_series_key
run_getRead one run; include_children:true returns the whole subtree (a workflow's children).run_key or run_id, include_children
run_summaryOne-call rollup of what a run produced — counts of tasks created/completed, decisions logged, knowledge written, handoffs, code refs, and total activity. The standup view of a run; defaults to the current run.run_id (defaults to the current run)
[!]

An honest limit on run_fork. Over MCP, subsequent tool calls still attribute to your current run — no tool accepts a run-key override yet. Attribution to the child works for out-of-process children that set LEDGENTER_PARENT_RUN_ID/LEDGENTER_RUN_ID in their environment, and for direct SDK consumers using withRunKey.

Repositories & code

Where "done" points at real code. Repositories are deduplicated by their normalized remote URL, and repo_register with no arguments autodetects the repo you're standing in (credentials stripped, never the absolute path). Code refs are the receipts: task_code_ref defaults the repository, branch, and SHA from your current run, so recording a commit is often a two-field call — (task_id, ref_type:'commit') — and the task's timeline then links straight to the code that delivered it.

ToolWhat it doesKey inputs
repo_registerResolve-or-register a git repository, deduped by its normalized remote URL. autodetect:true (or no args) uses the current working-directory repo.autodetect, remote_url, host, owner, name, slug, default_branch
repo_linkLink a project to a repository, with an optional role.project_id, repository_id or repo_slug, role (primary|dependency|docs)
repo_queryList repositories (RLS-scoped); project_id shows a project's repos.repository_id, slug, project_id, host, q
task_code_refRecord the code that delivered a task — commit/branch/PR/tag. Repository, branch, and SHA default from your current run.task_id, ref_type (commit|branch|pr|tag|compare), repository_id, sha, branch, pr_number, pr_url, pr_state, title, url, run_id
code_ref_addPolymorphic form of task_code_ref: attach a code reference to a task/decision/note/project. Same run-context defaults.object_type, object_id, ref_type, plus the same optional fields as task_code_ref
code_ref_updateMove a PR forward: patch its state, SHA, title, or URL. Emits pr.merged/pr.closed.code_ref_id, patch {pr_state (merged|closed|open|draft), sha, title, url}
code_ref_queryWhat code delivered this work: query by object, repository, ref type, PR state, or run.object_type, object_id, repository_id, ref_type, pr_state, run_id

Feedback

The two tools that point at Ledgenter itself. Every agent is instructed — in the always-on server instructions — to file when the product gets in its way: a bug, friction, or a missing capability. Requests are reviewed by the vendor's own development loop and status flows back to the filing tenant (chapter 12). One honesty note, carried in the tool description itself: the request text is shared with the vendor, so a filing must never contain secrets or client-confidential content.

ToolWhat it doesKey inputs
feature_request_createFile a bug/friction/missing-capability report about Ledgenter itself, the moment you hit it. Title and body are shared with the vendor for product improvement.title, body, kind (bug|friction|capability|docs), severity (low|medium|high), tool_name, error_code, context, fingerprint
feature_request_queryRead back your tenant's filed requests with current status (open → accepted → in_progress → shipped | declined) and resolution notes — how a filer learns the outcome.id, status[], kind, q, cursor

CLI twins & ledgenter:// resources

Every tool above has a CLI twin: the ledgenter binary exposes the same operations in noun-verb form, built on the same core methods and the same schemas, with --json output, stable exit codes, and a --soft-fail mode that keeps retryable failures from breaking unattended cron ticks (chapter 6).

MCP tool → CLI twin
whoami            →  ledgenter whoami
task_claim        →  ledgenter task claim --label api
task_release      →  ledgenter task release <taskId> --note "blocked on review"
knowledge_search  →  ledgenter knowledge search -q "pgvector tuning"
# one command is CLI-only: materialize skills as native Claude Code skills
ledgenter skills sync

The MCP server also publishes read-only resources — documents a host can surface without a tool call. The static ones (ledgenter://guide, ledgenter://guide/{topic}, ledgenter://glossary) serve the same guide content as the guide tool, as markdown. One is live: ledgenter://project/{key}/brief renders a project brief under the caller's own JWT, so it shows exactly what that caller is allowed to see — the same tenant wall as everything else (chapter 5).

[§]

The registry is the count. The canonical tool count is the length of the registry in packages/mcp-server/src/tools.ts — 57 today — and a CI test guards the prose↔registry integrity, so a tool can't ship undocumented or be documented without shipping.