MCP tools
Every MCP tool Lific exposes, with each parameter, its default and its limits.
Lific exposes 31 MCP tools. This page lists every parameter each one accepts, as published in its input schema. "Yes" in the Required column means the call fails without it; everything else can be omitted. MCP covers transports, errors and paging, and Working with Lific as an agent covers how the tools fit together.
Arguments are checked against the schema before a tool runs. A missing required parameter, a value of the wrong type, or a parameter the tool does not declare is rejected as a JSON-RPC -32602 invalid params error, and the tool never runs. Unknown parameters are refused on every tool, so a misspelled optional parameter cannot be dropped while the tool answers a different question. The message names the unknown key, suggests the closest valid name when one is close, and lists the valid ones:
Unknown parameter `comments` in get_issue. Did you mean `include_comments`? Valid parameters: `identifier`, `include_comments`.The same check applies inside nested objects, such as plan steps and create_issue batch items, where the message says Unknown field and names the nested object. Everything a tool itself refuses comes back as a result whose text starts with Error:.
edit_issue, edit_page, edit_comment and edit_plan_step also accept the camelCase spellings oldString, newString and replaceAll. The published schemas list only the snake_case names.
Module, label and folder names are matched as sent first. When nothing matches, a name that a client HTML-escaped on the way in (Infra & Ops) is tried once more with &, <, >, ", ' and ' decoded. A name that really contains one of those sequences still matches itself, and a second miss reports the name as you sent it.
When MCP is reached over HTTP and the server knows its public origin, the output renders issue, project, page, plan, module and comment references as Markdown links to the web UI. A comment link carries an anchor that scrolls its thread to that comment. Configure server.public_url for a deployment behind a proxy; without it, a direct request's Host is used only when it is on the allowlist. Local stdio has no external hostname, so references stay plain identifiers.
Which tools use a repository binding
In a session bound to a repository, an omitted project resolves to the bound project on these tools only:
| Tool | When the binding applies |
|---|---|
list_issues, create_issue, get_board, create_plan, get_briefing | Always. |
list_resources | For resource_type issue, plan, module, label or folder. |
On an unbound session these calls fail with project required (this session has no repository binding). An explicit project always wins. search, create_page, manage_resource, bulk_update, delete, and list_resources for pages and projects never read the binding, because omitting project already means something there or the call is destructive.
Issues
list_issues
Lists issues in one project, or across the projects of the people you name with members, filtered and paged. Each row shows the identifier, status, priority, title, labels and module, then any waits, who must do it, and checklist progress.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier, e.g. APP. Falls back to the repository binding; required on an unbound session unless members or roles is given. |
status | string | No | backlog, todo, active, done or cancelled. |
statuses | string[] | No | Several statuses at once, or all. Cannot be combined with status or be empty. |
priority | string | No | urgent, high, medium, low or none. |
module | string | No | Module name. |
label | string | No | Label name. |
workable | boolean | No | true returns open issues (not done or cancelled) whose blockers are all done and that have no wait still holding. |
blocked | boolean | No | true returns issues with at least one blocker that is not done, or a wait still holding, whatever their own status. Each row lists its open blockers as blocked_by:APP-3,APP-7. |
created_since | string | No | ISO date or datetime, inclusive, e.g. 2026-06-01. |
created_until | string | No | ISO date or datetime, exclusive. |
updated_since | string | No | ISO date or datetime, inclusive. |
updated_until | string | No | ISO date or datetime, exclusive. |
order_by | string | No | sort_order (default), sequence, created, updated or priority. |
order | string | No | asc (default) or desc. |
limit | integer | No | Default 50, capped at 500. |
offset | integer | No | Zero-based. |
members | string[] | No | Issues in every project you can see where any of these users has a role. "me" is you; name others by username, with or without @. |
roles | string[] | No | The roles that count for members: lead, maintainer, viewer or all (default). Without members, means your own roles. |
assignee | string | No | none (free for agents), human (a person must do it), me, or a username. Works with members too. |
workable and blocked are independent filters, not opposites. A closed issue with an open blocker is blocked and not workable; an open issue with no blockers is workable and not blocked. A cancelled blocker still blocks, and a deleted one no longer counts. Passing false to either is the same as omitting it. See ready and blocked work.
A wait is a blocker that is a person or a window of days rather than an issue; link_issues adds one. A user wait holds until someone clears it. A date wait holds before its first day, is due to check from its first day through its last, and is overdue after that. Only a holding wait blocks. "Today" is the server's local calendar day.
Every row that has waits carries one token per wait, whatever the filters:
| Token | Meaning |
|---|---|
waiting_on:@ada-lovelace | Waits on that person until the wait is cleared. |
waiting_until:2026-09-28..29 | A date wait that still holds. A one-day window prints as a single date. |
due_since:2026-09-28 | A date wait whose window has started. It no longer blocks. |
overdue_since:2026-09-30 | A date wait whose last day has passed, dated from the day after it. It no longer blocks. |
Notes are left out of the tokens; get_issue prints them. An issue a person must do carries for:human (any person) or for:@ada,@bob (named people). Rows without the token are free for any agent; workable does not filter on it, so check it before picking up work, or pass assignee="none". An issue whose description contains a Markdown task list ends its row with its progress, such as checklist: 2/5. Issues without task items carry no marker.
Your work across projects
members turns list_issues into a triage list across projects:
list_issues(members=["me"])
list_issues(roles=["lead", "maintainer"], statuses=["active", "todo"])
list_issues(members=["@bob"], priority="urgent")The response starts with the roles held in each listed project, then the rows in their usual format:
Your roles: APP lead · WEB viewer
3 issues:
- APP-7 | active | high | Ship the export
- WEB-12 | todo | urgent | Fix the login redirect
- APP-9 | todo | high | Review the importWith members, the listing matches the web home page's "My active issues": active and todo issues, active first, then by priority, then most recently updated. status or statuses brings in backlog or closed work, which sorts after todo. Unlike the home page, which shows every project you can see, only projects where the named users hold a role are listed. An explicit order_by still wins, and order needs order_by alongside it. An empty result still starts with the roles line, so "on three projects, nothing open" reads as such. roles=["lead", "maintainer"] is the sharpest "my work" filter: viewers often watch many projects they do not work on. module and label need project. Given project as well, members filters that one project, and the call says so when none of the named users has a role there. members never reaches beyond the projects you can see, and "me" resolves the same way as in list_resources.
get_issue
Returns one issue: title, status, priority, module, labels, checklist progress, who must do it, relations, waits, description and a window of its comments.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Issue identifier, e.g. PRO-42. |
include_comments | string | No | recent (default, the newest 3), all (the newest 500) or none (the count only). Any other value is an error. |
Relation lines include each related issue's current status, for example Blocked by: APP-42 (done).
An issue a person must do has an Assigned: human (a person must do this) or Assigned: @ada, @bob (a person must do this) line. An unassigned issue has none.
When the description contains a Markdown task list, a Checklist: 3/5 done line follows the status and labels. A task item is a list item (-, *, +, 1. or 1)) that starts with [ ], [x] or [X], nested or not. Items inside fenced code blocks are not counted, so a task list quoted as an example does not become the issue's checklist.
Each wait prints on its own line after the relations, with its note in parentheses:
Waiting on @ada-lovelace (decide the schema)Waiting until 2026-09-28..29 (state filing office)Due to check since 2026-09-28, expected by 2026-09-29Overdue since 2026-09-30, expected 2026-09-28..29
A comment that recorded closing evidence (see update_issue) is marked [verification] after its timestamp, here and in list_comments.
Comments print oldest first in every mode, and each mode reads only the rows it prints. When the thread is longer than the window, the comment header says how many it is showing and points at list_comments. The whole response is held to a 2 MiB budget, description included. If the comments would exceed it, fewer are shown and the header says the size budget cut them. An issue whose description alone leaves no room for its newest comment returns an error that sends you to list_comments.
create_issue
Creates an issue, or up to 50 at once, and returns the new identifiers.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier. Falls back to the repository binding; required on an unbound session. |
title | string | Unless issues is given | Issue title. |
description | string | No | Markdown. |
status | string | No | backlog (default), todo, active, done or cancelled. |
priority | string | No | urgent, high, medium, low or none (default). |
module | string | No | Module name. |
labels | string[] | No | Label names. A name that matches no label in the project is ignored. |
start_date | string | No | ISO 8601 date, e.g. 2026-06-01. |
target_date | string | No | ISO 8601 date, e.g. 2026-06-15. |
assignees | string[] | No | Who must do it: ["human"] for any person, or usernames ("me" is you, or the person who connected you). Omit to leave it free for agents. See Assignment. |
issues | object[] | No | A batch of 1 to 50 issues. Each item takes title (required) and any of the fields above except project. |
Attachment links (/api/attachments/12) in the description are linked to the new issue.
After the confirmation, the response lists up to three open issues in the same project that look like duplicates, under Similar open issues:, so you can link or cancel one right away. An issue qualifies when it shares at least two significant title words with the new one (one, if the new title has only one), and at least one of them appears in its own title. Significant means three or more characters and not a filler word such as "add", "fix" or "the". Done and cancelled issues are never listed, and the section is left out when nothing qualifies.
To file a burst of issues, pass issues instead of title. Only project may accompany it; any other top-level field is an error, not a default for the items. The batch is one transaction: if any item is invalid (an empty title, an unknown module, a bad status), nothing is created and the error names the item, such as issues[1]: module 'Nonexistent' not found in project. The response lists each new issue on its own line, in item order. The duplicate hint shrinks to identifiers on the item's line, as in - APP-8: Title (similar open: APP-3), and issues from the same batch are never listed as each other's duplicates.
update_issue
Changes the fields you pass and leaves the rest alone. Text fields are replaced whole; for a small change to a long description, use edit_issue.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Issue identifier, e.g. PRO-42. |
title | string | No | New title. |
description | string | No | New Markdown description. |
status | string | No | backlog, todo, active, done or cancelled. |
priority | string | No | urgent, high, medium, low or none. |
module | string | No | Module name. Pass "" to unassign. |
labels | string[] | No | Replaces the whole label set. |
start_date | string | No | ISO 8601 date. |
target_date | string | No | ISO 8601 date. |
expected_seq | integer | No | Concurrency check. The update is refused if the issue's seq is no longer this value. Omit for last-writer-wins. |
assignees | string[] | No | Replaces who must do it. [] frees it for agents, ["human"] marks it for any person, usernames name people. |
evidence | string | No | How the work was verified, in Markdown. Only with status: "done" on an issue that is not already done. Saved as a verification comment. |
A refused expected_seq returns an error naming the current seq and a one-line summary of the issue's current state. Moving an issue to or from done completes or reopens the plan steps that mirror it, and the response lists those steps.
evidence records what was checked (the test command that passed, its counts, what a person looked at) as a comment of kind verification, written in the same transaction as the close. The response names the new comment, and get_issue and list_comments mark it [verification]. It is refused when empty, with any status other than done (including cancelled, since cancelled work has nothing to verify), and on an issue that is already done; use add_comment for those. A refusal changes nothing, including the other fields in the call. The comment is authored by the caller, who can edit or delete it like any other.
Closing an issue whose description still has unchecked task items succeeds, and the response ends with a warning such as Warning: 2 of 5 checklist items still unchecked.
bulk_update
Sets the same fields on every issue in a project that matches the filters, in one transaction. Returns the number of issues updated.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | Yes | Project identifier. Never taken from a binding. |
filter_status | string | No | Only issues with this status. |
filter_priority | string | No | Only issues with this priority. |
filter_module | string | No | Only issues in this module, by name. |
filter_label | string | No | Only issues carrying this label, by name. |
set_status | string | No | Status to set. |
set_priority | string | No | Priority to set. |
set_module | string | No | Module to set, by name. |
set_assignees | string[] | No | Assignment to set, as in update_issue's assignees. |
With no filters, every issue in the project matches. At most 500 matching issues are selected per call; narrow the filters when more match. The batch is all or nothing: if any single update fails, none are applied.
edit_issue
Replaces one exact string in an issue's description or title, without resending the rest.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Issue identifier. |
old_string | string | Yes | Exact text to find. Must match exactly once unless replace_all is true. Cannot be empty. |
new_string | string | Yes | Replacement. Must differ from old_string. |
field | string | No | description (default) or title. |
replace_all | boolean | No | Replace every occurrence. Default false. |
Matching is byte-exact. No match, or more than one without replace_all, is an error and nothing is written. For the description, a string containing no real newline or tab has any literal \n and \t turned into real ones before matching, which repairs input from clients that double-escape JSON. Titles are matched as sent. Attachment links the edit adds are linked to the issue, and links it removes are dropped.
get_board
Groups a project's issues into columns.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier. Falls back to the repository binding; required on an unbound session. |
group_by | string | No | status (default), priority or module. |
include_closed | boolean | No | Include done and cancelled issues. Default false. |
max_per_column | integer | No | Cap the issues shown per column. A trailing … +N more (use list_issues) line reports the rest. |
Grouped by status, closed columns appear as a count with no rows unless include_closed is set. Grouped by priority or module, closed issues are left out and a closing line reports how many. The board reads at most 500 issues and prints a warning when a project has more; page through list_issues instead. Rows carry the same wait tokens as list_issues (waiting_on:@ada-lovelace, due_since:2026-09-28), but no checklist marker.
link_issues
Creates a relation between two issues, or makes target wait on a person or a window of days. Waiting on a person or a date explains how waits behave.
| Name | Type | Required | Description |
|---|---|---|---|
source | string | Unless this is a wait | Source issue identifier, e.g. PRO-1. Omit it for a wait. |
target | string | Yes | Target issue identifier, e.g. PRO-2. For a wait, the issue that waits. |
relation_type | string | Yes | blocks, relates_to or duplicate. source blocks target, or duplicates it. A wait must use blocks. |
user | string | No | Wait on this username (a leading @ is fine) until someone clears the wait. |
from | string | No | Wait for a window of days starting on this date, YYYY-MM-DD. The issue is blocked before this day. |
until | string | No | Last expected day of a from wait. After it the wait is overdue. Defaults to from. |
note | string | No | A note on the wait, such as who to chase. At most 500 characters. |
Linking two issues takes the maintainer role in both issues' projects.
Passing user, or from with an optional until, makes the call a wait: target is blocked by a person or a date instead of an issue. See list_issues for how waits block and how they print.
link_issues(target="APP-9", relation_type="blocks", from="2026-09-28", until="2026-10-02", note="state filing office")The response is the wait as get_issue prints it, such as APP-9: Waiting until 2026-09-28..10-02 (state filing office). A wait takes maintainer in the target's project. These are errors, and nothing is written:
- a
relation_typeother thanblocks, or asourcealongsideuserorfrom; - both
userandfrom,untilwithoutfrom, oruntilwithuser; - a username with no active account, a date not in
YYYY-MM-DDform, or anuntilbeforefrom; - a note over 500 characters;
- a wait the issue already has: the same person, or the same window.
unlink_issues
Removes the relation between two issues, or clears a wait on target.
| Name | Type | Required | Description |
|---|---|---|---|
source | string | Unless clearing a wait | First issue identifier. Omit it to clear a wait. |
target | string | Yes | Second issue identifier, or the issue that waits. |
user | string | No | Clear the target's wait on this username. |
from | string | No | Clear every date wait on the target whose window starts on this day, YYYY-MM-DD. |
Clear one kind per call: user and from together are an error, as is a source alongside either. Clearing a wait the issue does not have is an error. Clearing takes maintainer in the target's project, and the issue's activity history records it.
Projects and resources
list_resources
Lists projects, modules, labels, folders, pages, issues or plans. It is the discovery call: start a session with resource_type='project'.
| Name | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | project, module, label, folder, page, issue or plan. |
project | string | No | Required for issue, plan, module, label and folder unless the session has a binding. Optional for page, where omitting it lists pages from every project you can see. For project, lists only that project; you need viewer on it. |
folder | string | No | Pages only: folder name. Needs project. |
label | string | No | Pages only: label name. |
status | string | No | Pages: draft, active, complete or archived. Plans: active, done or archived. |
order_by | string | No | Pages only: sort_order (default), title, status, created or updated. |
order | string | No | Pages only: asc (default) or desc. |
limit | integer | No | Issues and pages default to 100, plans to 50; all cap at 500. Ignored for projects, modules, labels and folders. |
offset | integer | No | Zero-based, for issues, pages and plans. |
members | string[] | No | Projects only: keep projects where any of these users has a role. "me" is you; name others by username, with or without @. "all" is rejected. |
roles | string[] | No | Projects only: the roles that count for members: lead, maintainer, viewer or all (default). Without members, means your own roles. |
statuses | string[] | No | Projects only: which per-status issue counts each row prints: backlog, todo, active, done, cancelled or all (default). [] prints none. |
show_members | string[] | No | Projects only: roles whose members are all named, uncapped: lead, maintainer, viewer or all. [] prints only counts per role. |
Projects are sorted by recent activity, and each row carries its workable-issue count, active-plan count and how long ago anything happened. It then shows the project's members by role, your own role, and its issue count per status:
- APP | App (3 workable, last activity 2h ago) | lead @alice · maintainers @bob, @carol · 12 viewers | you: maintainer | 2 active · 5 todo · 9 backlog · 40 doneBy default, leads and maintainers are named, up to five per role followed by +N more, and viewers are counted. An instance with only one active person leaves the roster and you: segments out, since they would only ever name that person; pass show_members to print them anyway. you: admin marks an admin who holds no role in the project; you: is left out when you hold none and are not an admin. Deactivated accounts are left out of the roster. Zero counts are left out, and a project without issues shows no issues; when all its issues fall outside statuses, the chosen statuses print as 0 active · 0 todo. "me" is the caller the permission checks see: a bot counts as its owner, and a session without a user-bound credential counts as the first admin. On an instance with one active person, that person counts as lead of every project they hold no role in, so members: ["me"] also covers projects created without a lead, such as those from the CLI or an import. members only ever narrows the projects you can already see; it never reveals another user's projects that are hidden from you. When nothing matches, the call returns No projects where @bob has a role (among projects you can see). The issue listing takes no filters (its label is ignored); use list_issues to filter. Issue and page listings end with the next offset when more rows exist. The plan listing honors limit and offset but prints no paging hint, so request the next page yourself when a full page comes back.
An unrecognized resource_type returns a line naming the accepted values.
manage_resource
Creates or updates a project, module, label or folder. Deletion is delete.
| Name | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | project, module, label or folder. |
action | string | Yes | create or update. |
current_name | string | Depends | The existing name of the module, label or folder to update. Not used for projects. |
project | string | Depends | Project identifier. Required for every module, label and folder operation, and to select the project on a project update. |
name | string | Depends | Required on create. On update, the new name. |
identifier | string | Depends | Required when creating a project, e.g. PRO. On a project update, the new identifier. |
description | string | No | Project or module description. |
status | string | No | Modules only: backlog, planned, active (default on create), paused, done or cancelled. |
color | string | No | Labels only: hex color, e.g. #EF4444. Defaults to #6B7280 on create. |
emoji | string | No | Project or module icon: lucide:<Name> or a literal emoji. Omit to leave it unchanged; pass "" to clear it. |
| Resource and action | Required | Fields applied |
|---|---|---|
| project, create | name, identifier | description, emoji. An authenticated caller becomes the project's primary lead. |
| project, update | project | name, identifier, description, emoji. Passing current_name without project is an error. |
| module, create | project, name | description, status, emoji |
| module, update | project, current_name | name, description, status, emoji |
| label, create | project, name | color |
| label, update | project, current_name | name, color |
| folder, create | project, name | none |
| folder, update | project, current_name | name |
Updating a project takes lead. Module, label and folder changes take maintainer. An unsupported pair returns a line naming the accepted types and actions.
delete
Deletes one resource.
| Name | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | issue, page, plan, project, module, label or folder. |
identifier | string | Yes | APP-1 for an issue, APP-DOC-1 or DOC-1 for a page, APP-PLAN-2 for a plan, APP for a project, or the name of a module, label or folder. |
project | string | Depends | Required when deleting a module, label or folder, since those are found by name within a project. |
A deleted issue goes to the trash with its comments. Deleting a project takes lead, or an administrator when membership is not enforced. An unrecognized resource_type returns a line naming the accepted values.
Pages
get_page
Returns one page: its status, folder, labels, pin state and timestamps, then its content. A page of up to 30,000 characters comes back whole. A longer one comes back as its outline plus its opening, and you read the rest by section or by offset. The budget keeps a response under the roughly 50 KB at which agent harnesses silently cut tool output off.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | APP-DOC-1 for a project page, DOC-1 for a workspace page. |
section | string | No | Read one section: a heading's text (case-insensitive) or its anchor, such as current-state. Leading #s are ignored. |
outline | boolean | No | Return only the headings, each with its level and section size in characters, plus the page's seq. With section, outlines that section. |
since_seq | integer | No | A seq from an earlier read. Returns only what changed in the content since then, as a diff, plus the current seq. Cannot be combined with section, outline or offset. |
offset | integer | No | Character offset at which to continue a cut read: into the page, or into the section when section is given. Cannot be combined with outline. |
A section runs from its heading to the next heading of the same or a higher level, so it includes its subsections. An exact anchor wins over a text match. When several headings share the text, the call is an error that lists each one's anchor, its parent heading and its size; pass the anchor. A heading that matches nothing is an error that points at outline=true. Only ATX headings (# to ######) count, and lines inside fenced code blocks are never headings. Anchors are the heading lowercased, with spaces turned into hyphens, characters other than letters, digits, - and _ dropped, and -1, -2 appended to repeats.
How each read comes back:
| Read | Response |
|---|---|
| Plain, 30,000 characters or fewer | The whole content. |
| Plain, over 30,000 characters | The size, the outline (at most a third of the budget; deeper headings are dropped first), and the start of the page filling the rest of the 30,000. |
section | A Section: line with the section's size, the page's size and seq, then the section. A section over 30,000 characters is cut the same way as a long page. |
outline=true | Outline (seq N, X chars; sizes include subsections): and one indented line per heading. |
offset | A line giving the position and seq, then up to 30,000 characters from there. No outline. |
since_seq | Content unchanged since seq N, or a unified diff with two lines of context. A diff over 30,000 characters is cut and suggests reading the changed sections instead. |
Every cut response ends with the exact call that continues it and how many characters remain, for example Next: get_page(identifier="APP-DOC-3", section="Amendment log", offset=29316). It also suggests reading by section when a heading starts in the unread part. Cuts fall at the end of a line when one ends within the last 2,000 characters. An offset at or past the end is an error.
since_seq works from any seq, because issues, pages and comments share one counter per instance: the seq that outline, section, offset and get_briefing print, or one from the REST API. Lific keeps each page's newest 50 content versions, dropping the oldest once they total 2,000,000 characters. When the seq falls outside that history, the response says so and returns the page as a plain read would. Only content changes are versioned; a title, status or folder change adds no version.
create_page
Creates a page in a project, or a workspace page when project is omitted.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier. Omitting it creates a workspace page, even in a bound session. |
title | string | Yes | Page title. |
content | string | No | Markdown. |
folder | string | No | Folder name. Needs project. |
status | string | No | draft (default), active, complete or archived. |
labels | string[] | No | Label names. Labels belong to a project, so this is ignored on workspace pages. |
Workspace pages take an administrator when membership is enforced.
create_page, update_page and edit_page all accept content over the 30,000-character read budget of get_page. When the saved content is over it, the response adds one line saying so and suggesting a split, or moving old history to an archive page, because readers will get an outline instead of the whole page.
update_page
Changes the fields you pass and leaves the rest alone. Content is replaced whole; for a small change, use edit_page. Like create_page, it notes when the content is over the read budget.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Page identifier. |
title | string | No | New title. |
content | string | No | New Markdown content. |
folder | string | No | Move to this folder. Pass "" to move the page to the project root. |
status | string | No | draft, active, complete or archived. |
pinned | boolean | No | Pin the page to the top of the page list, or unpin it. |
labels | string[] | No | Replaces the whole label set. [] clears it. |
expected_seq | integer | No | Concurrency check. The update is refused if the page's seq is no longer this value. Omit for last-writer-wins. |
edit_page
Replaces one exact string in a page's content or title. The contract is the same as edit_issue.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Page identifier. |
old_string | string | Yes | Exact text to find. Must match exactly once unless replace_all is true. Cannot be empty. |
new_string | string | Yes | Replacement. Must differ from old_string. |
field | string | No | content (default) or title. |
replace_all | boolean | No | Replace every occurrence. Default false. |
The double-escape repair applies to content, not to titles. Attachment links the edit adds or removes are linked or unlinked. Like create_page, it notes when the content is over the read budget.
Plans
create_plan
Creates a plan and its whole step tree in one call. See Plans.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier. Falls back to the repository binding; required on an unbound session. |
title | string | Yes | Plan title. |
anchor_issue | string | No | Issue this plan breaks down, e.g. APP-42. Moving it to done archives the plan. |
steps | PlanStep[] | No | The nested step tree. |
A PlanStep object:
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Short, imperative step title. |
description | string | No | Longer notes. |
issue | string | No | Issue this step mirrors, e.g. APP-42. While the plan is active, moving the issue to done checks the step, and checking the step moves the issue to done. |
done | boolean | No | Create the step already done. Default false. |
steps | PlanStep[] | No | Child steps, to any depth. |
The anchor and every mirrored issue need maintainer in their own project, which may differ from the plan's.
get_plan
Returns a plan's full step tree, for resuming work. Each step line shows its numeric id as #18, its done state and its linked issue.
| Name | Type | Required | Description |
|---|---|---|---|
plan | string | Yes | Plan identifier, e.g. APP-PLAN-3. |
edit_plan_step
Replaces one exact string in a step's description or title, then returns the plan's tree.
| Name | Type | Required | Description |
|---|---|---|---|
plan | string | Yes | Plan identifier. |
step_id | integer | Yes | The step's id from get_plan. For #18, pass 18, as an integer. |
old_string | string | Yes | Exact text to find. Must match exactly once unless replace_all is true. |
new_string | string | Yes | Replacement. |
field | string | No | description (default) or title. |
replace_all | boolean | No | Replace every occurrence. Default false. |
The double-escape repair applies to descriptions, not titles.
update_plan_step
Changes a plan, or one step in it. Without step_id the call targets the plan; with it, the step. A field that belongs to the other target is refused with an error rather than ignored.
| Name | Type | Required | Description |
|---|---|---|---|
plan | string | Yes | Plan identifier. |
step_id | integer | No | The step's id from get_plan. Omit to change the plan itself. |
title | string | No | New title for the plan or the step. |
status | string | No | Plan only: active, done or archived. Never closes the anchor issue. |
anchor_issue | string | No | Plan only: set the anchor issue. |
clear_anchor | boolean | No | Plan only: remove the anchor issue. |
done | boolean | No | Step only: mark done or not done. Marking done also closes a linked issue, and the response says so. |
attach_issue | string | No | Step only: link an issue, e.g. APP-42. |
detach_issue | boolean | No | Step only: remove the step's issue link. |
add_child_title | string | No | Add a step with this title: a child of step_id, or a top-level step when step_id is omitted. |
add_child_description | string | No | Description for the new step. Needs add_child_title. |
add_child_issue | string | No | Issue the new step mirrors. Needs add_child_title. |
move_parent_step_id | integer | No | Step only: move the step under this step. |
move_to_root | boolean | No | Step only: move the step to the top level. |
move_position | integer | No | Step only: new zero-based position among its siblings. |
delete | boolean | No | Step only: delete the step and everything under it. Other step fields are ignored. |
echo_tree | boolean | No | Return the whole tree instead of a receipt. Default false. |
By default the response is a receipt: what changed, then the plan's status and how many steps are done. Several step changes can go in one call.
Comments
add_comment
Adds a comment to an issue or page, authored by the caller. Returns the comment's numeric id.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | APP-1 for an issue, APP-DOC-1 for a project page, DOC-1 for a workspace page. |
content | string | Yes | Markdown. At most 262144 bytes. |
Commenting takes viewer. @username mentions in the body are resolved when the comment is saved.
list_comments
Returns one page of a thread, newest first by default.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Issue, project page or workspace page identifier. |
author | string | No | Only comments by this username. |
order | string | No | desc (default, newest first) or asc (oldest first). |
limit | integer | No | Default 50, capped at 500. |
offset | integer | No | Zero-based, in the requested order. |
When the page is not the whole thread, the first line says how many of the total it shows, and the last line gives the offset for the next call. A page is also cut short to stay within a 2 MiB response; the last line then says the size budget, not the row limit, ended it, and its offset counts only the rows actually returned. A comment that recorded closing evidence is marked [verification] after its timestamp.
edit_comment
Changes a comment by its id, in one of two modes. Only the author or an administrator can edit.
| Name | Type | Required | Description |
|---|---|---|---|
comment_id | integer | Yes | Comment id, from add_comment or list_comments. |
old_string | string | No | Exact-string mode: text to find. Must match exactly once unless replace_all is true. Needs new_string. |
new_string | string | No | Exact-string mode: replacement. Must differ from old_string. Needs old_string. |
replace_all | boolean | No | Exact-string mode only: replace every occurrence. Default false. |
content | string | No | Replacement mode: the new body, replacing the whole comment. At most 262144 bytes. |
Pass either old_string and new_string, or content. Mixing the two modes, passing neither, sending one of the string pair alone, or sending replace_all with content returns an error and leaves the comment unchanged. Parameters outside this table are rejected by the schema before the tool runs, so a misspelled old_str cannot silently become a full rewrite. Exact-string mode applies the same double-escape repair as edit_issue. Mentions and attachment links are re-read from the new body.
delete_comment
Deletes a comment. Only the author or an administrator can delete.
| Name | Type | Required | Description |
|---|---|---|---|
comment_id | integer | Yes | Comment id. |
Search and activity
get_briefing
Summarizes a project for an agent resuming work, in one bounded read of at most 6,000 characters. Call it first in a session instead of rebuilding the picture from searches, pages, issue lists and plans.
| Name | Type | Required | Description |
|---|---|---|---|
project | string | No | Project identifier. Falls back to the repository binding; required on an unbound session. |
since | string | No | ISO date or datetime. Adds a section for what changed strictly after it. Read like get_activity's since: UTC unless an offset is given. |
pages | string[] | No | Page identifiers to report on, such as the pages an AGENTS.md tells agents to read. At most 10 are reported; duplicates count once. Without it, the three most recently updated pages are shown. |
It takes viewer on the project. Everything is read from live records at call time; nothing is stored or summarized by a model.
The response opens with the current time and a since value to pass next time. That cursor is one second before the briefing, because since is strict and timestamps have one-second resolution: a change written later in the briefing's own second is reported next time instead of never, and changes from that second may be reported twice.
Sections follow in this order, and a section with nothing to report is left out:
| Section | What it lists |
|---|---|
| Since the cursor | Only with since. Counts of issues created, closed (done or cancelled) and otherwise moved, and pages edited, then the most recent of each kind. Status changes are net per issue, so an issue moved and moved back is not listed. When nothing changed, the header says so instead. |
| Active plans | Each with its progress and the next open step: the first unfinished step in tree order, descending into its first unfinished child. When that step mirrors an issue in a project you cannot view, the issue is not named. |
| Due to check | Open issues with a date wait whose window has arrived, most overdue first, each with its wait line. These no longer block, so this is where the follow-up surfaces; clear the wait with unlink_issues when it is done. |
| Blocked | Open issues with an unresolved blocker or a wait still holding, each naming its blocking issues and their status, and its holding waits (waiting on @ada-lovelace (note), waiting until 2026-09-28..29). A blocker in a project you cannot view is counted, not named. |
| Workable, not yet active | Issues ready to start, highest priority first. Active issues have their own section. |
| Key pages, or recently updated pages | Identifier, title, seq, size in characters, and whether the page changed since the cursor (or when it was last updated). Page bodies are never included. A page that does not exist and one you cannot view both read not found. |
| Active | Active issues, highest priority first. |
Issue sections show up to 8 lines, plans up to 5, each kind of change up to 5, and titles are cut at 80 characters. If the whole response is still over 6,000 characters, lines are dropped from the longest section until it fits. A section that shows fewer items than it has ends with how many were left out and the call that lists them all, such as (+12 more: list_issues(project='APP', blocked=true)). Issue and plan counts stop at 100; 100+ means at least that many.
search
Searches issues, pages, comments and attachments by text.
| Name | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Text to find. At most 4 KiB in fts mode and 256 bytes in literal mode. |
project | string | No | Only this project. Omitted, it searches every project you can see, even in a bound session. |
result_type | string | No | issue, page, comment or attachment. |
sort | string | No | relevance (default) or recent. |
mode | string | No | fts (default): full-text, tokenized, with prefix matching. literal: case-insensitive substring, for punctuation-heavy text such as core:sodom, [RequiredSpecs] or --trace-plans that tokenizing throws away. |
limit | integer | No | Default 20, capped at 500. |
offset | integer | No | Zero-based, capped at 100000. |
An attachment matches on its filename or on the extracted text of a small text upload. Its hit names the attachment id and the issue or page it is attached to; attachments not yet linked anywhere are never returned. Without result_type, attachment hits follow all the issue, page and comment hits rather than being ranked among them. A comment hit links the comment and names its parent issue or page.
In fts mode every word is a required prefix term. When no result in the requested scope contains every word, the search runs again with any word allowed, ranks those hits by relevance (or recency, with sort=recent), and opens with No result contains every word; showing partial matches ranked by relevance. (or most recent first.). Paging walks the same partial list. A one-word query and literal mode never fall back.
A project you cannot see returns No results found., the same as a project with no matches.
get_activity
Reads the audit log for an issue, a page or a whole project, newest first, with old and new values. With since, it reads forward from a point in time instead.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | An issue (PRO-42), a page (PRO-DOC-3 or DOC-3), or a bare project identifier (PRO) for the project's whole feed. |
since | string | No | Only entries strictly after this ISO date or datetime, returned oldest first. |
limit | integer | No | Default 30, capped at 200. |
offset | integer | No | Zero-based. |
Audit timestamps are UTC with one-second resolution, and since is read on the same clock. A bare date such as 2026-06-01 means midnight UTC. A datetime without an offset is UTC, and one with Z or an offset such as -05:00 is converted to UTC. Fractional seconds are dropped. Entries in the same second as the cursor are excluded, so to resume after the last entry you read without missing anything written later in its second, pass a cursor one second earlier and expect that second's entries to repeat. Anything else is an error.
Each entry names the actor and the door the change came through: mcp, web, api, cli or system. Writes by a connected agent are attributed to the agent's own identity, not to the person who connected it.
export
Exports as Markdown, choosing by the identifier's shape.
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | An issue (PRO-42), a page (PRO-DOC-3), or a bare project identifier (PRO). |
offset | integer | No | Project only: zero-based document offset. |
limit | integer | No | Project only: documents per response. Default 20, capped at 100. |
For an issue or a page, the response is the exported Markdown.
For a project, the response is the Markdown of its issues and pages, one document each, a page of documents at a time. Every document opens with frontmatter naming its identifier. The first line gives the issue, page and document totals and which documents follow. When more remain, the last line gives the offset for the next call. A response also stops early at 256 KiB of Markdown, but always carries at least one document, however long. To write a project to disk as files, run lific export project.
Issue frontmatter lists relations (blocks, blocked_by, relates_to, duplicates, duplicated_by). Issues in projects you cannot view are left out of those lists, with no placeholder or count.
Attachments
Uploads share the REST limits: 10 MiB per file, and a type allowlist decided from the bytes. The allowlist is PNG, JPEG, GIF, WebP, SVG, PDF, ZIP, plain text, MP4, WebM video and audio, Ogg audio, MP3, SQLite databases and legacy HWP. The filename's extension decides the type only for formats with no signature of their own: .svg is SVG, .txt, .log, .md, .csv and .json are plain text, and .hwp marks a file with the OLE compound-document signature as HWP. Everything else is read from its magic bytes, so a renamed executable is refused.
upload_attachment
Uploads a file from base64 and returns its id and a Markdown snippet to embed:  for images, [name](/api/attachments/12) otherwise.
| Name | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | File name, e.g. crash-log.txt. |
content_base64 | string | Yes | The file, base64-encoded. At most 10 MiB decoded. |
entity | string | No | Link the upload to this issue (APP-42) or page (APP-DOC-3, DOC-3). |
comment_id | integer | No | Link the upload to this comment instead. |
Pass at most one of entity and comment_id. Linking to an issue or page takes maintainer in its project; linking to a comment takes viewer. An upload with no link stays unlinked, and a sweep deletes unlinked files after 24 hours, so embed the snippet in an issue, page or comment before then. Embedding it links it.
get_attachment
Reads one attachment.
| Name | Type | Required | Description |
|---|---|---|---|
attachment_id | integer | Yes | Attachment id, e.g. 12 for /api/attachments/12. |
offset | integer | No | Text only: zero-based first line. |
limit | integer | No | Text only: lines to return. Default 200, capped at 500. |
Text files are returned in line ranges. Use the returned offset to read the next range. PNG, JPEG, GIF and WebP are returned as MCP image content. Other files arrive as base64 resources over stdio, including remote and multi-instance proxies; clients can decode the blob to save the file. Direct HTTP MCP requests with a validated origin return an absolute download URL, using the configured public URL or an allowlisted Host. Without an origin, HTTP also returns the embedded resource. The download endpoint checks the caller's permission to read the attachment.
Reading takes viewer in any project the attachment is linked into. An attachment linked nowhere has no project to check, so only its uploader or an administrator can read it.
list_attachments
Lists the attachments on one issue or page, or across a whole project. Each row gives the id, filename, type, size, uploader, creation time and the entities it is linked to. Links into projects you cannot see are left out.
| Name | Type | Required | Description |
|---|---|---|---|
entity | string | Depends | An issue (APP-42) or page (APP-DOC-3, DOC-3). |
project | string | Depends | A project identifier, e.g. APP. |
Pass exactly one of entity and project. Either takes viewer on the project involved. The list is not paged.