@laver/mcp
以下摘录自该仓库的 README(片段) · 在 GitHub 读完整 README →
An MCP server for Laver. Gives an agent tools to read and
drive kanban boards, tickets and the workspace wiki.
Every tool is a thin call to the same public REST API the web app uses. There is
no local state, no cache, and no second implementation of anything — if Laver
refuses a write, the refusal comes back verbatim, because an agent can act on
"409, re-read and retry" and cannot act on "something went wrong".
Setup
Create a workspace-scoped API key in Laver under **Admin → API keys**. It acts
as the person who created it, so it can do exactly what they can do and nothing
more, and it can be revoked without touching their account.
LAVER_API_URL overrides the API host; it defaults to https://api.laver.app.
LAVER_API_KEY_FILE is an alternative to LAVER_API_KEY: a path to either a
file containing nothing but the key, or a .env-style file with a
LAVER_API_KEY=… line among others (an assignment line wins; quotes and an
export prefix are both fine). That is how the .mcp.json in this repo
registers the server without a secret in a tracked file.
A file with neither — no assignment line, and more than one token in it — yields
**no key at all**, and you get the "key is not set" error. It used to send the
whole file as the token, which is fine for a file holding one secret and is a
leak for anything else.
LAVER_API_URL must be https, except for localhost.
Working in this repo
.mcp.json at the repo root registers this server for anyone who opens the
project, reading the key from the gitignored .env. Nothing to export.
It runs the **published** package, npx -y @laver/mcp, rather than the
mcp/server.js beside it. That is deliberate: pointing it at the local file
meant everyone here ran the one code path no user takes, and that is precisely
how 0.1.0 shipped with an entry point that never connected its transport when
started through bin — which is the only way a real client starts it. Running
what we publish means we meet what users meet.
**If you are editing this server**, that same choice will fool you: your changes
do nothing until they are published. Point the client at the working copy while
you work on it —
— and put it back before you commit. npm run check and
frontend/tests/check-mcp-bin-entrypoint.mjs both run against the working copy
regardless, so the tests never depend on a publish.
**A client only connects to MCP servers at startup.** claude mcp add while a
session is already running does not retrofit the tools into that session — the
tool list was built before the server existed. Start a new session (or
reconnect from the client's MCP panel) and the tools appear.
Tools
**Reading**
| Tool | What it gives you |
| ------------------------ | ------------------------------------------------------------------------------------- |
| list_workspaces | Where to start when you have no uuids |
| list_boards | The boards in a workspace |
| get_board | A board with its status columns, labels, members and tickets |
| list_tickets | Tickets on a board, filterable, paged — updated_since is how you follow a board |
| list_workspace_tickets | Triage across every board at once — overdue, unassigned, or free text |
| get_ticket | One ticket in full, its subtasks, **including its version** |
| get_ticket_comments | Comments and activity history |
| get_ticket_flow | How long the ticket has spent in each column |
| list_custom_fields | A board's custom field definitions — the uuids update_ticket writes against |
| list_labels | Every label in the workspace, not only the ones already used on one board |
| search | Boards, tickets, wiki pages and comments across a whole workspace at once |
list_workspace_tickets is the stand-up read: it needs no board_uuid, and it
is the only thing here that answers "what is late" and "what does nobody own"
without walking every board. It is deliberately the small sibling of
list_tickets — one page, 50 by default and 200 at most, no cursor — so narrow
it rather than paging it.
get_ticket_flow derives its numbers from the moves already in a ticket's
history. Read visits rather than the by_status totals if you are adding
several tickets up: tickets worked in one batch overlap, and their totals do not.
gaps says when the history and the ticket's current column disagree, which
makes the totals a floor rather than a measurement.
To follow a board, call list_tickets again with updated_since set to the
server_time the previous call returned; you get back the tickets that changed
and nothing else. There is no tool for the server-sent event stream at