MCP tool reference
Complete reference for the logbook MCP server, generated from source
(app/api/mcp/route.ts, serverInfo.name = "logbook", version = "0.9.0").
Audience: coding agents first, humans second. Every tool name, parameter,
default, and response field below is quoted verbatim from the tool
registrations.
The server exposes 25 tools and 2 prompts. Every tool is scoped to a
single project, resolved from your token and (for org keys) the project slug in
the URL — see Connecting agents. Every write is
attributed in the activity feed as claude (<your name>); created items are
attributed to your human name (not the claude (…) label).
Conventions used by every tool
Refs
Item refs render as LOG-<number> on output regardless of project
(formatRef always emits the LOG- prefix). On input, any tool that takes a
query/to/epic string accepts a bare number (12), a LOG--prefixed ref
(LOG-12), any other prefixed ref (GAP-88 — the prefix is stripped to the
number), or free title text (fuzzy match).
Query resolution (findItemByQuery) returns one of:
- exact number match, or
- a single title/description match, or
- a disambiguation message when >1 title matches:
"<q>" matches N items — be more specific or use the ref:followed by the candidate refs, or Nothing matching "<q>" — try list_backlog with a search term.
When a tool cannot resolve a query it returns that message as the tool result text (it does not throw).
Enums
Shared across tools (from lib/db/schema.ts):
| Enum | Values |
|---|---|
status (ITEM_STATUSES) | backlog, in_progress, done, stuck |
priority (ITEM_PRIORITIES) | low, medium, high, urgent |
size (ITEM_SIZES) | S, M, L, XL |
kind (ITEM_KINDS) | log, epic |
link kind (LINK_KINDS) | pr, branch, file, url |
Link objects
Wherever a tool takes links / add_links, each entry is
{ kind: <pr|branch|file|url>, value: <string, min length 1> }.
pr— pull request URL or numberbranch— git branch namefile— repo file pathurl— anything else
On output, links serialize as "<kind>:<value>" strings; a pr link that
matches a known PR is annotated with live state, e.g. "pr:<url> (merged)".
Errors surfaced as text
Domain errors (unknown label, unknown assignee email, epic invariant
violations) are caught inside the tool body and returned as the tool result
text prefixed with Error: — the call still "succeeds" at the protocol level.
The two create tools additionally gate on billing before running; see
upgrade_required.
Verbosity: the three output shapes
list_backlog, get_my_work, and (implicitly) the briefing snapshot serialize
item lists at one of three verbosity levels. Default is index.
index (default)
One line per item, no descriptions or bodies — the always-safe-to-hold-in-
context shape. Emails are dropped (assignee names only; emails remain in
pull_item). Fields, in order:
{
"ref": "LOG-42",
"title": "…",
"kind": "epic", // present on epic rows only
"epic": "LOG-12", // parent ref, present on child logs only
"summary": "…", // authored summary, else first sentence of
// description flattened + hard-cut at 140 chars;
// omitted when there is no description
"size": "M", // omitted when unsized
"status": "in_progress",
"priority": "high",
"children": "2/5", // done/total child logs, epic rows only
"assignees": ["Ada"], // names; omitted when none
"tasks": "1/3", // done/total subtasks; omitted when no tasks
"labels": ["frontend"], // omitted when none
"updated": "2026-07-17T…Z"
}
refs
The leanest shape — for very large orgs or pure lookups. kind appears on
epic rows only; child rows carry no parent ref here (refs is deliberately
terse).
{ "ref": "LOG-42", "title": "…", "kind": "epic", "size": "M", "status": "…" }
full
Complete item bodies (the legacy serializeItem shape, with descriptions). A
compatibility escape hatch for exceptional cases — not for surveys. For
working an item, prefer pull_item/pull_items (which return an even richer
payload including tasks, notes, related items, and live PR state).
Session / survey tools
briefing
Session-start briefing in ONE call: unread inbox (marked read), items assigned
to you with task progress, and the team scoreboard. Use this instead of
separate check_inbox / get_my_work / team_summary calls.
Params: none.
Response:
{
"inbox": ["<message>", …] , // or "empty — nothing new"
"my_work": [ <index row>, … ], // or "nothing assigned to you"
"scoreboard": [
{ "who": "Ada", "active": 3, "done_30d": 7 }
]
}
Side effect: marks your inbox notifications as read (same as check_inbox).
backlog_stats
Call this FIRST for any survey or audit. The shape of the backlog in one small object, no item bodies.
Params: none.
Response:
{
"total": 137,
"by_status": { "backlog": 40, "in_progress": 12, "done": 80, "stuck": 5 },
"by_size": { "S": 20, "M": 30, "L": 10, "XL": 2 },
"by_kind": { "log": 130, "epic": 7 },
"unsized": 35, // legacy items with no size — need cleanup
"top_labels": [ { "label": "frontend", "count": 22 }, … ],
"by_assignee": [ { "who": "Ada", "open": 4 }, … ],
"oldest_in_progress": { "ref": "LOG-9", "updated": "2026-05-01T…Z" } // or null
}
Workflow: understand the shape here, THEN use list_backlog (filtered) and
pull_items only for what you will actually work. Unsized legacy items never
match a size filter — they surface only through unsized here.
list_backlog
Returns an INDEX (one line per item, no bodies). By default excludes done
items. Use pull_item/pull_items to read or work an item.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
status | enum ITEM_STATUSES | no | — | Exact status filter. |
assignee_email | email string | no | — | Filter to this assignee. |
label | string | no | — | Taxonomy label, e.g. frontend, bug. |
size | array of ITEM_SIZES | no | — | OR semantics, e.g. ["S","M"]. Unsized items never match. |
kind | enum ITEM_KINDS | no | — | Only log or only epic. |
epic | string (ref) | no | — | Returns that epic's child logs. |
search | string | no | — | Free-text. |
include_done | boolean | no | false | Include done items. |
verbosity | refs|index|full | no | index | See verbosity. |
limit | int 1–200 | no | 50 | Max returned (capped at 200). |
offset | int ≥0 | no | 0 | Skip this many matches; page with limit. |
Response envelope:
{
"total": 137, // total matching BEFORE paging
"returned": 50, // rows in this page
"offset": 0,
"items": [ <row at requested verbosity>, … ],
"note": "87 more match — narrow with status/label/assignee/search, or page with offset."
}
note is present only when total - offset - returned > 0.
get_my_work
Items currently assigned to you that are not done. Same INDEX rows as
list_backlog.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
size | array of ITEM_SIZES | no | — | OR semantics. |
kind | enum ITEM_KINDS | no | — | Only log or epic. |
epic | string (ref) | no | — | Only your child logs under that epic. |
verbosity | refs|index|full | no | index | — |
Returns a bare array of rows, or the string
"Nothing assigned to you right now." when empty.
team_summary
Per-person scoreboard: active item count and items completed in the last 30 days.
Params: none. Returns an array [{ who, active, done_30d }], or
"No assigned items yet.".
check_inbox
Unread notifications for you (new assignments, notes on your items, status changes). Marks them read.
Params: none. Returns [{ at, message }], or "Inbox empty — nothing new.".
(briefing folds this in.)
Reading items
pull_item
PREFERRED way to pick up ONE work item: everything in one call. Find by ref or title text; optionally claim it.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
query | string (min 1) | yes | — | Ref (GAP-88/LOG-12/12) or title text. |
claim | boolean | no | — | Also assign me and set in_progress. |
claim=true runs an idempotent claim: adds you as assignee and moves
backlog → in_progress (skipped if archived). The response then includes
"claimed": true.
Response (serializeFullItem plus extras):
{
"ref": "LOG-42",
"title": "…",
"kind": "epic", // epic rows
"epic": "LOG-12", // child rows
"status": "in_progress",
"priority": "high",
"assignees": ["Ada <ada@x.com>"], // full name <email> in pull payloads
"children": "2/5", // epic rows
"labels": ["frontend"],
"changed_files": ["lib/db/schema.ts", …], // distinct paths from merged PRs, capped 40
"files_truncated": true, // present when more than the cap
"problem": "…",
"resolution": "…",
"description": "…",
"created_by": "Ada",
"archived": true, // present only when archived
"links": ["pr:<url> (merged)", "branch:feat/x"],
"tasks": [ { "title": "…", "done": false, "assignee": "Ada" } ],
"notes": [ { "author": "claude (Ada)", "body": "…", "at": "…Z" } ],
"documents": [ { "filename": "ux-spec.md", "size": 1234, "author": "…", "at": "…Z" } ],
"related_items": ["LOG-9 [done] Title", …],
"children": [ <index row>, … ], // when the item IS an epic: its child logs
"recent_activity": ["<event message>", …], // last 10 events
"claimed": true // only when claim=true
}
Notes:
documentscarries metadata only — fetch content withget_document.- Live PR state is joined in only when the item has a
prlink. - An epic's
childrenarray holds index rows (no bodies); pull them individually to work them.
pull_items
Batch pull_item: fetch MANY items completely in ONE call. Select by
refs/queries OR by filter. Per-item recent activity is omitted (pull one
ref if you need it).
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
queries | array of strings (1–50) | no | — | Refs or title text. Omit to use filter mode. |
status | enum ITEM_STATUSES | no | — | Filter mode. |
label | string | no | — | Filter mode. |
size | array of ITEM_SIZES | no | — | Filter mode. |
kind | enum ITEM_KINDS | no | — | Filter mode. |
epic | string (ref) | no | — | Filter mode — an epic's child logs. |
assignee_email | no | — | Filter mode. | |
search | string | no | — | Filter mode. |
limit | int 1–100 | no | 25 | Filter mode only. |
claim | boolean | no | — | Assign me + set in_progress on every selected item. |
Response:
{
"count": 6,
"items": [ <full pull_item payload>, … ], // no recent_activity
"not_found": ["GAP-99: Nothing matching …"],// only when some queries missed
"claimed": 6 // only when claim=true (count attempted)
}
queries mode resolves each entry independently; misses go to not_found.
Duplicate numbers are de-duplicated. Claims run sequentially so events land in
deterministic order.
get_item
Get full detail for one item by number (not a query). Includes description
and all notes. Simpler/legacy sibling of pull_item — no live PR state, no
claim, no changed_files.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | Item number, e.g. 42 for LOG-42. |
Returns the serializeItem fields plus problem, resolution,
description, created_by, tasks (each with done_by when present),
notes, and related_items. Returns No item LOG-<n> when absent.
history_for_files
Before modifying a file, ask what happened to it before — the items and resolutions that shaped these paths.
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
paths | array of strings (1–20) | yes | — | Repo-relative paths. |
limit | int 1–25 | no | 10 | Max items per path. |
A trailing / makes a path a directory query (lib/db/); otherwise the match
is exact and case-sensitive. Scoped to this project.
Response:
{
"results": [
{
"path": "lib/db/schema.ts",
"matched": 3,
"items": [
{
"ref": "LOG-42", "title": "…", "summary": "…", "size": "M",
"status": "done", "updated": "…Z",
"resolution_excerpt": "…", // when present
"prs": [ … ] // when present
}
]
}
]
}
Creating items
create_item
Add ONE new item. Always state the problem. Description supports markdown.
Labels must already exist. Gated by the billing quota (see
upgrade_required).
| Param | Type | Required | Notes |
|---|---|---|---|
title | string (min 1) | yes | — |
problem | string | no | What problem is this solving. |
description | string | no | Markdown. |
summary | string (max 120) | no | One line, imperative — shown in every index. |
size | enum ITEM_SIZES | conditional | Required for logs; omit for epics. |
kind | enum ITEM_KINDS | no | log (default) or epic. |
epic | string (ref) | no | Attach this log to an existing epic. Logs only. |
priority | enum ITEM_PRIORITIES | no | — |
labels | array of strings | no | Must already exist. |
links | array of link objects | no | — |
tasks | array of strings | no | Initial subtask checklist. |
assignee_emails | array of emails | no | Assign team members by logbook email. |
Create-time rules (enforced by validateKindFields, all-or-nothing — a
violation throws and nothing is created):
sizeis required for logs (kindomitted orlog). Missing →`size` is required for logs (S/M/L/XL). If this decomposes into shippable pieces, pass kind:'epic' and omit size.sizeis rejected for epics →Epics carry no size — omitsizefor kind:'epic' (containers aren't sized).- An epic cannot have a parent → passing
epic(orepic_index) on an epic →Epics cannot have a parent — `epic`/`epic_index` are for child logs only (no nesting in v1). summarymax length is 120 chars (schema-enforced).- Unknown
labels, unknownassignee_emails, or anepicref that resolves to a log (not an epic) each abort with a descriptiveError: ….
Sizing guidance (from the schema): S one focused change, single session; M a feature, several files, still one sitting; L cross-cutting / multi-session, needs a plan; XL usually should be an epic instead.
Kind decision test: if one PR would close it, it is a log (even with a long task checklist); if it decomposes into independently shippable pieces different sessions could claim, it is an epic with sized children.
Response: Created LOG-<n>: <title> (epics: Created LOG-<n> (epic): <title>).
create_items
Batch create_item: file MANY structured items in ONE call. Each entry takes
the same fields as create_item, plus epic_index.
| Param | Type | Required | Notes |
|---|---|---|---|
items | array (1–50) of entry objects | yes | Each = a create_item payload plus epic_index. |
Entry-only field:
| Field | Type | Notes |
|---|---|---|
epic_index | int ≥0 | Link this child log to an epic entry in THIS batch by its 0-based index. Use instead of epic. Logs only. |
Parent linking: a child links a parent either by epic (an existing epic ref)
OR epic_index (a 0-based index of an epic entry in this batch), never
both. Epics in the batch are inserted first, so an in-batch child resolves to
its just-created parent regardless of array order.
Validation is all-or-nothing — every check runs before any insert. Any of
these aborts with nothing was created and leaves the backlog untouched:
- Per-entry
kind/size/epicinterplay (same rules ascreate_item), reported asitem <i>: <message>. - Unknown labels across the whole batch:
Unknown label(s): <list> — nothing was created. Use list_labels / create_label first, then retry. epic_indexout of range:item <i>: epic_index <idx> is out of range (0..N-1) — nothing was created.epic_indexpointing at itself:item <i>: epic_index cannot point at itself — nothing was created.epic_indexpointing at a non-epic entry:item <i>: epic_index <idx> points at a non-epic entry — nothing was created.- An
epicref that does not resolve:item <i>: <resolve message> — nothing was created. - An
epicref that resolves to a log:item <i>: LOG-<n> is a log, not an epic — nothing was created. - A bad
assignee_emailsentry throws naming the email before anything inserts.
Response:
{ "created": 3, "items": [ { "ref": "LOG-42", "title": "…", "kind": "epic" }, … ] }
kind appears in each result row only for epics; results are in original input
order.
Updating items
update_item
Update ONE item. Change status (done to cross off, stuck to flag blocked
work), priority, title, description, labels, size, or reassign.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | Item number. |
status | enum ITEM_STATUSES | no | — |
priority | enum ITEM_PRIORITIES | no | — |
title | string | no | — |
problem | string | no | — |
description | string | no | — |
summary | string (max 120) | no | — |
size | enum ITEM_SIZES | no | Reclassify, or backfill an unsized legacy item. |
resolution | string | no | What was done — write one at completion; becomes documentation. |
epic | string (ref) or null | no | Attach to an epic by ref, or null to detach. Rejected on epics. |
labels | array of strings | no | Replaces the full label set; must already exist. |
add_links | array of link objects | no | Appends links. |
assignee_emails | array of emails or null | no | Replaces the full assignee set; null or [] unassigns everyone. |
Semantics and rejections:
kindis immutable after creation — there is no way to change it.epicon an item that is itself an epic →LOG-<n> is an epic — epics can't be attached to a parent.epicreferencing a log (not an epic) →LOG-<n> is a log, not an epic — children only attach to epics.epicreferencing a non-existent id →That epic does not exist in this project.- Unknown
labels→Unknown label(s): <list>. Available: <list>. Create labels deliberately (Settings, or the create_label tool). labelsreplaces the whole set;add_linksappends;assignee_emailsreplaces (withnull/[]meaning unassign-all).
Response: Updated LOG-<n> — status: <status>, priority: <priority>, or
No item LOG-<n> when absent.
update_items
Batch update_item: apply updates to MANY items in ONE call. Each entry takes
the same fields as update_item. Items are processed independently — one
failure does NOT stop the rest.
| Param | Type | Required | Notes |
|---|---|---|---|
updates | array (1–50) of update entries | yes | Each = an update_item payload (with number). |
Response:
{
"updated": 4,
"failed": 1,
"results": [
{ "ref": "LOG-42", "ok": true, "status": "done", "priority": "high" },
{ "ref": "LOG-9", "ok": false, "error": "no such item" }
]
}
Per-item errors carry the same messages as update_item (epic rejections,
unknown labels, etc.).
Tasks
add_task
Add a subtask to an item's checklist, optionally assigned.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | — |
title | string (min 1) | yes | — |
assignee_email | no | — |
Returns Task added to LOG-<n>: "<title>", or No item LOG-<n>.
complete_task
Check off a subtask, identified by its title (exact or unique substring).
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | — |
task | string (min 1) | yes | Task title, exact or unique substring. |
Matching: exact (case-insensitive) matches win; otherwise substring matches.
- No match →
No open task matching "<task>" on LOG-<n>. Open tasks: "…", "…" - Ambiguous (>1) →
"<task>" is ambiguous — matches: "…", "…". Be more specific. - Success →
Checked off "<title>" on LOG-<n> — <k> task(s) remaining
Notes
add_note
Leave a markdown note on an item. The assignee is notified.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | — |
body | string (min 1) | yes | Markdown. |
Returns Note added to LOG-<n>, or No item LOG-<n>.
Labels
list_labels
The managed label taxonomy, with item counts. Items can only use labels from this set.
Params: none. Returns the label rows ({ label, count, … }), or
No labels defined yet — use create_label..
create_label
Add a new label to the managed taxonomy. Check list_labels first — reuse over
near-duplicates (frontend vs front-end).
| Param | Type | Required | Notes |
|---|---|---|---|
name | string (min 1) | yes | Lowercase slug, e.g. front-end. Normalized on write. |
Returns Label created: <slug>.
Documents
Attachments live outside the item body, so they never bloat
list_backlog/pull_item. Agents see only the filename metadata until they
call get_document.
attach_document
Attach a markdown document to an item — for long-form specs, design docs, RFCs.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | — |
filename | string (min 1) | yes | e.g. ux-spec.md. Include the .md extension. |
content | string (min 1) | yes | The full markdown document. |
Stored as text/markdown. Returns
Attached "<filename>" (<size> bytes) to LOG-<n>, or No item LOG-<n>.
get_document
Fetch the full markdown content of an attached document. List filenames first
via pull_item/get_item.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | — |
filename | string (min 1) | yes | Attached name, exact or unique substring. |
Response:
{ "filename": "ux-spec.md", "size": 1234, "author": "claude (Ada)", "at": "…Z", "content": "# …" }
Returns No item LOG-<n> when the item is absent, or the finder's message when
the filename does not match / is ambiguous.
Relations
link_items
Link two related items (bidirectional). Linked items appear in each other's
pull_item as related_items. Link items that share a root cause, a PR
cluster, or block each other.
| Param | Type | Required | Notes |
|---|---|---|---|
number | int | yes | One item's number. |
to | string (min 1) | yes | The other item: ref or title text. |
Returns Linked LOG-<a> ↔ LOG-<b>, or
LOG-<a> and LOG-<b> were already linked.
Team
list_team
List logbook team members (for assigning work by email).
Params: none. Returns [{ name, email }].
The roster is the project's org members plus anyone attributed in its items
— never every user in the database (that would leak names across tenants). This
same roster backs assignee_email resolution: an email that is not on it is
rejected with No logbook user with email <email>. Known users: <list>.
Repos
link_repo
Register a GitHub repo with this project so its PR webhook events are recorded here. Used during project setup.
| Param | Type | Required | Notes |
|---|---|---|---|
repo | string (min 1) | yes | owner/name, e.g. groovy-os/logbook-v2. |
Returns
Linked <owner/name> to this project. PR webhook events for this repo will now be recorded here.
After linking, create a webhook on the repo pointing at the logbook webhook URL
(see the project's setup instructions and
Troubleshooting).
Maintenance
archive_done_items
Sweep all done items into the archive (they leave the board but keep their history and still count toward done stats).
Params: none. Returns Archived <n> done item(s), or
No done items to archive.
Prompts
Two planning prompts ship WITH the server (registered via registerPrompt), so
they are advertised on both /api/mcp and /api/mcp/<slug>. Claude Code
surfaces each as a slash command:
/mcp__logbook__plan-work and /mcp__logbook__enrich-log. Each returns a
single user message whose text orchestrates the existing tools; both keep a
human in the loop (draft → approve → file). The prompt text does not restate
tool docs — it references tools by their exact registered names.
plan-work
Title: Plan work. Turns a rough idea into well-formed backlog items, filing nothing until the human approves.
| Arg | Type | Required | Notes |
|---|---|---|---|
idea | string | no | Optional one-liner seeding the interview. |
Orchestrates: (1) an interview (problem, done, out-of-scope, constraints, how
work splits); (2) prior-art check via backlog_stats, then list_backlog
(status/label/search), history_for_files on named paths, and list_labels
(adopt existing taxonomy, do not invent labels); (3) the log-vs-epic decision
test with a stated verdict; (4) a full plan drafted as text for approval; (5) on
approval, a single create_items call (epic first, children linked by
epic_index), then attach_document for any flagged specs, reporting the
created refs.
enrich-log
Title: Enrich a log. Audits an existing item against the quality bar, asks only about the gaps, then fixes it (or proposes a split) on approval.
| Arg | Type | Required | Notes |
|---|---|---|---|
ref | string | yes | The item to enrich — LOG-42 or search text. |
Orchestrates: (1) pull_item to read the whole item; (2) audit for a missing
size, missing summary, no problem statement, no acceptance criteria, an XL log
that should be an epic, several deliverables under one title, or a stale status
— note only real gaps; (3) ask the human only about those gaps; (4) apply fixes
in a single update_item call (check list_labels, do not invent labels); (5)
if it is really several deliverables, propose a split via create_items +
re-parenting (epic_index or epic ref), or close the original as superseded
with a resolution — only on the human's approval.
Connection model (brief)
Agents authenticate with an lgbk_… bearer token. Two token kinds exist:
- Org key — the only kind Settings mints now. Valid only on
/api/mcp/<project-slug>; the slug picks the project, the org must own it. - Legacy project token — fixed to one project. Valid on the bare
/api/mcp, and on/api/mcp/<slug>only when the slug is its own.
serverInfo reports { name: "logbook", version: "0.9.0" }. maxDuration is
60s. For the full token/URL model, minting tokens, and setup, see
Connecting agents; for auth and validation errors see
Troubleshooting.