Lific
MCP

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:

ToolWhen the binding applies
list_issues, create_issue, get_board, create_plan, get_briefingAlways.
list_resourcesFor 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.

NameTypeRequiredDescription
projectstringNoProject identifier, e.g. APP. Falls back to the repository binding; required on an unbound session unless members or roles is given.
statusstringNobacklog, todo, active, done or cancelled.
statusesstring[]NoSeveral statuses at once, or all. Cannot be combined with status or be empty.
prioritystringNourgent, high, medium, low or none.
modulestringNoModule name.
labelstringNoLabel name.
workablebooleanNotrue returns open issues (not done or cancelled) whose blockers are all done and that have no wait still holding.
blockedbooleanNotrue 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_sincestringNoISO date or datetime, inclusive, e.g. 2026-06-01.
created_untilstringNoISO date or datetime, exclusive.
updated_sincestringNoISO date or datetime, inclusive.
updated_untilstringNoISO date or datetime, exclusive.
order_bystringNosort_order (default), sequence, created, updated or priority.
orderstringNoasc (default) or desc.
limitintegerNoDefault 50, capped at 500.
offsetintegerNoZero-based.
membersstring[]NoIssues in every project you can see where any of these users has a role. "me" is you; name others by username, with or without @.
rolesstring[]NoThe roles that count for members: lead, maintainer, viewer or all (default). Without members, means your own roles.
assigneestringNonone (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:

TokenMeaning
waiting_on:@ada-lovelaceWaits on that person until the wait is cleared.
waiting_until:2026-09-28..29A date wait that still holds. A one-day window prints as a single date.
due_since:2026-09-28A date wait whose window has started. It no longer blocks.
overdue_since:2026-09-30A 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 import

With 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.

NameTypeRequiredDescription
identifierstringYesIssue identifier, e.g. PRO-42.
include_commentsstringNorecent (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-29
  • Overdue 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.

NameTypeRequiredDescription
projectstringNoProject identifier. Falls back to the repository binding; required on an unbound session.
titlestringUnless issues is givenIssue title.
descriptionstringNoMarkdown.
statusstringNobacklog (default), todo, active, done or cancelled.
prioritystringNourgent, high, medium, low or none (default).
modulestringNoModule name.
labelsstring[]NoLabel names. A name that matches no label in the project is ignored.
start_datestringNoISO 8601 date, e.g. 2026-06-01.
target_datestringNoISO 8601 date, e.g. 2026-06-15.
assigneesstring[]NoWho 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.
issuesobject[]NoA 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.

NameTypeRequiredDescription
identifierstringYesIssue identifier, e.g. PRO-42.
titlestringNoNew title.
descriptionstringNoNew Markdown description.
statusstringNobacklog, todo, active, done or cancelled.
prioritystringNourgent, high, medium, low or none.
modulestringNoModule name. Pass "" to unassign.
labelsstring[]NoReplaces the whole label set.
start_datestringNoISO 8601 date.
target_datestringNoISO 8601 date.
expected_seqintegerNoConcurrency check. The update is refused if the issue's seq is no longer this value. Omit for last-writer-wins.
assigneesstring[]NoReplaces who must do it. [] frees it for agents, ["human"] marks it for any person, usernames name people.
evidencestringNoHow 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.

NameTypeRequiredDescription
projectstringYesProject identifier. Never taken from a binding.
filter_statusstringNoOnly issues with this status.
filter_prioritystringNoOnly issues with this priority.
filter_modulestringNoOnly issues in this module, by name.
filter_labelstringNoOnly issues carrying this label, by name.
set_statusstringNoStatus to set.
set_prioritystringNoPriority to set.
set_modulestringNoModule to set, by name.
set_assigneesstring[]NoAssignment 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.

NameTypeRequiredDescription
identifierstringYesIssue identifier.
old_stringstringYesExact text to find. Must match exactly once unless replace_all is true. Cannot be empty.
new_stringstringYesReplacement. Must differ from old_string.
fieldstringNodescription (default) or title.
replace_allbooleanNoReplace 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.

NameTypeRequiredDescription
projectstringNoProject identifier. Falls back to the repository binding; required on an unbound session.
group_bystringNostatus (default), priority or module.
include_closedbooleanNoInclude done and cancelled issues. Default false.
max_per_columnintegerNoCap 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.

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.

NameTypeRequiredDescription
sourcestringUnless this is a waitSource issue identifier, e.g. PRO-1. Omit it for a wait.
targetstringYesTarget issue identifier, e.g. PRO-2. For a wait, the issue that waits.
relation_typestringYesblocks, relates_to or duplicate. source blocks target, or duplicates it. A wait must use blocks.
userstringNoWait on this username (a leading @ is fine) until someone clears the wait.
fromstringNoWait for a window of days starting on this date, YYYY-MM-DD. The issue is blocked before this day.
untilstringNoLast expected day of a from wait. After it the wait is overdue. Defaults to from.
notestringNoA 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_type other than blocks, or a source alongside user or from;
  • both user and from, until without from, or until with user;
  • a username with no active account, a date not in YYYY-MM-DD form, or an until before from;
  • a note over 500 characters;
  • a wait the issue already has: the same person, or the same window.

Removes the relation between two issues, or clears a wait on target.

NameTypeRequiredDescription
sourcestringUnless clearing a waitFirst issue identifier. Omit it to clear a wait.
targetstringYesSecond issue identifier, or the issue that waits.
userstringNoClear the target's wait on this username.
fromstringNoClear 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'.

NameTypeRequiredDescription
resource_typestringYesproject, module, label, folder, page, issue or plan.
projectstringNoRequired 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.
folderstringNoPages only: folder name. Needs project.
labelstringNoPages only: label name.
statusstringNoPages: draft, active, complete or archived. Plans: active, done or archived.
order_bystringNoPages only: sort_order (default), title, status, created or updated.
orderstringNoPages only: asc (default) or desc.
limitintegerNoIssues and pages default to 100, plans to 50; all cap at 500. Ignored for projects, modules, labels and folders.
offsetintegerNoZero-based, for issues, pages and plans.
membersstring[]NoProjects only: keep projects where any of these users has a role. "me" is you; name others by username, with or without @. "all" is rejected.
rolesstring[]NoProjects only: the roles that count for members: lead, maintainer, viewer or all (default). Without members, means your own roles.
statusesstring[]NoProjects only: which per-status issue counts each row prints: backlog, todo, active, done, cancelled or all (default). [] prints none.
show_membersstring[]NoProjects 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 done

By 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.

NameTypeRequiredDescription
resource_typestringYesproject, module, label or folder.
actionstringYescreate or update.
current_namestringDependsThe existing name of the module, label or folder to update. Not used for projects.
projectstringDependsProject identifier. Required for every module, label and folder operation, and to select the project on a project update.
namestringDependsRequired on create. On update, the new name.
identifierstringDependsRequired when creating a project, e.g. PRO. On a project update, the new identifier.
descriptionstringNoProject or module description.
statusstringNoModules only: backlog, planned, active (default on create), paused, done or cancelled.
colorstringNoLabels only: hex color, e.g. #EF4444. Defaults to #6B7280 on create.
emojistringNoProject or module icon: lucide:<Name> or a literal emoji. Omit to leave it unchanged; pass "" to clear it.
Resource and actionRequiredFields applied
project, createname, identifierdescription, emoji. An authenticated caller becomes the project's primary lead.
project, updateprojectname, identifier, description, emoji. Passing current_name without project is an error.
module, createproject, namedescription, status, emoji
module, updateproject, current_namename, description, status, emoji
label, createproject, namecolor
label, updateproject, current_namename, color
folder, createproject, namenone
folder, updateproject, current_namename

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.

NameTypeRequiredDescription
resource_typestringYesissue, page, plan, project, module, label or folder.
identifierstringYesAPP-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.
projectstringDependsRequired 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.

NameTypeRequiredDescription
identifierstringYesAPP-DOC-1 for a project page, DOC-1 for a workspace page.
sectionstringNoRead one section: a heading's text (case-insensitive) or its anchor, such as current-state. Leading #s are ignored.
outlinebooleanNoReturn only the headings, each with its level and section size in characters, plus the page's seq. With section, outlines that section.
since_seqintegerNoA 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.
offsetintegerNoCharacter 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:

ReadResponse
Plain, 30,000 characters or fewerThe whole content.
Plain, over 30,000 charactersThe 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.
sectionA 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=trueOutline (seq N, X chars; sizes include subsections): and one indented line per heading.
offsetA line giving the position and seq, then up to 30,000 characters from there. No outline.
since_seqContent 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.

NameTypeRequiredDescription
projectstringNoProject identifier. Omitting it creates a workspace page, even in a bound session.
titlestringYesPage title.
contentstringNoMarkdown.
folderstringNoFolder name. Needs project.
statusstringNodraft (default), active, complete or archived.
labelsstring[]NoLabel 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.

NameTypeRequiredDescription
identifierstringYesPage identifier.
titlestringNoNew title.
contentstringNoNew Markdown content.
folderstringNoMove to this folder. Pass "" to move the page to the project root.
statusstringNodraft, active, complete or archived.
pinnedbooleanNoPin the page to the top of the page list, or unpin it.
labelsstring[]NoReplaces the whole label set. [] clears it.
expected_seqintegerNoConcurrency 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.

NameTypeRequiredDescription
identifierstringYesPage identifier.
old_stringstringYesExact text to find. Must match exactly once unless replace_all is true. Cannot be empty.
new_stringstringYesReplacement. Must differ from old_string.
fieldstringNocontent (default) or title.
replace_allbooleanNoReplace 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.

NameTypeRequiredDescription
projectstringNoProject identifier. Falls back to the repository binding; required on an unbound session.
titlestringYesPlan title.
anchor_issuestringNoIssue this plan breaks down, e.g. APP-42. Moving it to done archives the plan.
stepsPlanStep[]NoThe nested step tree.

A PlanStep object:

NameTypeRequiredDescription
titlestringYesShort, imperative step title.
descriptionstringNoLonger notes.
issuestringNoIssue 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.
donebooleanNoCreate the step already done. Default false.
stepsPlanStep[]NoChild 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.

NameTypeRequiredDescription
planstringYesPlan 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.

NameTypeRequiredDescription
planstringYesPlan identifier.
step_idintegerYesThe step's id from get_plan. For #18, pass 18, as an integer.
old_stringstringYesExact text to find. Must match exactly once unless replace_all is true.
new_stringstringYesReplacement.
fieldstringNodescription (default) or title.
replace_allbooleanNoReplace 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.

NameTypeRequiredDescription
planstringYesPlan identifier.
step_idintegerNoThe step's id from get_plan. Omit to change the plan itself.
titlestringNoNew title for the plan or the step.
statusstringNoPlan only: active, done or archived. Never closes the anchor issue.
anchor_issuestringNoPlan only: set the anchor issue.
clear_anchorbooleanNoPlan only: remove the anchor issue.
donebooleanNoStep only: mark done or not done. Marking done also closes a linked issue, and the response says so.
attach_issuestringNoStep only: link an issue, e.g. APP-42.
detach_issuebooleanNoStep only: remove the step's issue link.
add_child_titlestringNoAdd a step with this title: a child of step_id, or a top-level step when step_id is omitted.
add_child_descriptionstringNoDescription for the new step. Needs add_child_title.
add_child_issuestringNoIssue the new step mirrors. Needs add_child_title.
move_parent_step_idintegerNoStep only: move the step under this step.
move_to_rootbooleanNoStep only: move the step to the top level.
move_positionintegerNoStep only: new zero-based position among its siblings.
deletebooleanNoStep only: delete the step and everything under it. Other step fields are ignored.
echo_treebooleanNoReturn 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.

NameTypeRequiredDescription
identifierstringYesAPP-1 for an issue, APP-DOC-1 for a project page, DOC-1 for a workspace page.
contentstringYesMarkdown. 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.

NameTypeRequiredDescription
identifierstringYesIssue, project page or workspace page identifier.
authorstringNoOnly comments by this username.
orderstringNodesc (default, newest first) or asc (oldest first).
limitintegerNoDefault 50, capped at 500.
offsetintegerNoZero-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.

NameTypeRequiredDescription
comment_idintegerYesComment id, from add_comment or list_comments.
old_stringstringNoExact-string mode: text to find. Must match exactly once unless replace_all is true. Needs new_string.
new_stringstringNoExact-string mode: replacement. Must differ from old_string. Needs old_string.
replace_allbooleanNoExact-string mode only: replace every occurrence. Default false.
contentstringNoReplacement 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.

NameTypeRequiredDescription
comment_idintegerYesComment 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.

NameTypeRequiredDescription
projectstringNoProject identifier. Falls back to the repository binding; required on an unbound session.
sincestringNoISO date or datetime. Adds a section for what changed strictly after it. Read like get_activity's since: UTC unless an offset is given.
pagesstring[]NoPage 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:

SectionWhat it lists
Since the cursorOnly 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 plansEach 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 checkOpen 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.
BlockedOpen 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 activeIssues ready to start, highest priority first. Active issues have their own section.
Key pages, or recently updated pagesIdentifier, 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.
ActiveActive 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.

Searches issues, pages, comments and attachments by text.

NameTypeRequiredDescription
querystringYesText to find. At most 4 KiB in fts mode and 256 bytes in literal mode.
projectstringNoOnly this project. Omitted, it searches every project you can see, even in a bound session.
result_typestringNoissue, page, comment or attachment.
sortstringNorelevance (default) or recent.
modestringNofts (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.
limitintegerNoDefault 20, capped at 500.
offsetintegerNoZero-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.

NameTypeRequiredDescription
identifierstringYesAn issue (PRO-42), a page (PRO-DOC-3 or DOC-3), or a bare project identifier (PRO) for the project's whole feed.
sincestringNoOnly entries strictly after this ISO date or datetime, returned oldest first.
limitintegerNoDefault 30, capped at 200.
offsetintegerNoZero-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.

NameTypeRequiredDescription
identifierstringYesAn issue (PRO-42), a page (PRO-DOC-3), or a bare project identifier (PRO).
offsetintegerNoProject only: zero-based document offset.
limitintegerNoProject 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: ![name](/api/attachments/12) for images, [name](/api/attachments/12) otherwise.

NameTypeRequiredDescription
filenamestringYesFile name, e.g. crash-log.txt.
content_base64stringYesThe file, base64-encoded. At most 10 MiB decoded.
entitystringNoLink the upload to this issue (APP-42) or page (APP-DOC-3, DOC-3).
comment_idintegerNoLink 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.

NameTypeRequiredDescription
attachment_idintegerYesAttachment id, e.g. 12 for /api/attachments/12.
offsetintegerNoText only: zero-based first line.
limitintegerNoText 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.

NameTypeRequiredDescription
entitystringDependsAn issue (APP-42) or page (APP-DOC-3, DOC-3).
projectstringDependsA project identifier, e.g. APP.

Pass exactly one of entity and project. Either takes viewer on the project involved. The list is not paged.

On this page