Skip to main content
Glama
README.md
# zentra-mcp

MCP server exposing Zentra's Task and Vault APIs as tools Claude can call
directly, from any project, not just from inside the Zentra repo:

| Tool | What it does |
|------|--------------|
| `zentra_task_create` | Create a task on the board, optionally filed straight into a sprint |
| `zentra_task_list` | List tasks, filtered by tag/status/type/parent |
| `zentra_task_move` | Move a task to a new status, optionally assigning it to a sprint |
| `zentra_sprint_list` | List sprints, optionally filtered by status (e.g. OPEN) |
| `zentra_vault_create_page` | Create a Vault page, optionally with content, a parent folder (by id or name), and a tag |
| `zentra_vault_list_pages` | List the Vault page tree, optionally narrowed to one page's children |
| `zentra_vault_upload_attachment` | Upload a local file as an attachment on a Vault page |

`zentra_sprint_list` resolves sprints (e.g. the OPEN one) by id, and that id can
then be passed as `sprintId` to `zentra_task_create` or `zentra_task_move` to
put a task in a sprint.

Extracted from the Zentra monorepo (`packages/mcp`) so that Zentra's sibling
projects can use it without checking out Zentra. It builds standalone: the few
task-board and Vault types it needs are vendored in `src/board-types.ts` rather
than imported from `@zentra/shared`.

## Setup (one time)

1. Install and build:

   ```bash
   yarn install && yarn build
   ```

2. Authenticate. Two ways:

   **Interactive** — run in a real terminal, not through Claude, since this is
   the only place your password is typed:

   ```bash
   node dist/cli.js login
   ```

   This writes a JWT to `~/.config/zentra-mcp/credentials.json` (mode `600`).
   The JWT expires after 7 days; re-run when a tool call reports "Session
   expired."

   **Unattended** — set `ZENTRA_TOKEN` to a long-lived service token, which
   takes precedence over the credentials file. This is how the scheduled daily
   runs authenticate; see `docs/service-token.md` in the Zentra repo for how to
   mint one.

3. Register the server with Claude Code at **user scope**, so it's available in
   every project:

   ```bash
   claude mcp add zentra-mcp --scope user -- sh "$(pwd)/bin/zentra-mcp.sh"
   ```

   `bin/zentra-mcp.sh` installs dependencies when `node_modules` is missing and
   builds when `dist/` is missing **or stale** (any `src/*.ts` newer than
   `dist/cli.js`), then execs the server. The missing case matters for fresh
   checkouts, where MCP stdio servers are spawned before anything has had a
   chance to build; the stale case matters after every `git pull`, since an
   out-of-date `dist/` otherwise gets served silently and new tools never show
   up. Build output goes to stderr so stdout stays a clean protocol channel.

   Claude Code reads a server's tool list once, when it spawns the server at
   session start. **Restart the session after changing tools** — a running
   session will not pick them up.

## Configuration

- `ZENTRA_TOKEN` — bearer token to authenticate with, preferred over
  `~/.config/zentra-mcp/credentials.json`. Used by unattended automation.
- `ZENTRA_API_URL` — override the API base URL (defaults to the production
  Render deployment). Useful for developing against a local API.

## Project tags

Every task carries a `tag` naming its project, and a Vault page created via
`zentra_vault_create_page` can optionally carry one too.

Zentra's API is migrating tags from free text (`tags: string[]`) to a
registry (`GET /api/tags`, `tagIds: string[]`), but this server ships on its
own schedule, not in lockstep with that API — so it detects which one it's
talking to (by probing `GET /api/tags` once per session) and works either
way, with no configuration needed:

- **Registry available:** a `tag` name is resolved to its registered id
  case-insensitively. Creating a task, or tagging a page, with a name that
  isn't registered fails with a clear error; listing tasks by an unregistered
  tag is treated as a legitimate "nothing matches" query and returns an empty
  list instead. Register a project's tag in Zentra before using it here.
- **Registry not (yet) deployed:** tags are matched by exact string, after
  trimming whitespace — any free text is accepted, there is no enforced set,
  and asking for the wrong one returns an empty list rather than an error,
  same as this server behaved before the registry existed.

This is transparent to callers either way — the same `tag` input works in
both worlds. See the comment above `resolveTagForWrite`/`resolveTagForFilter`
in `src/tag.ts` for the mechanics, and for the condition under which the
legacy path can eventually be deleted.

## Manual verification

There's no automated integration test against the live production API — that
would require storing real credentials in CI. After setup, verify by hand:

1. Start a Claude Code session in each project you use this from.
2. In each, ask Claude to create a task via `zentra_task_create` (it should
   infer the `tag` from the project you're in).
3. Confirm each task appears correctly — right title, tag, status — on the
   Zentra web UI's task board.
4. Ask Claude to list and move a task via `zentra_task_list`/`zentra_task_move`
   and confirm the board reflects it.
5. Ask Claude to create a Vault page via `zentra_vault_create_page`, then attach
   a local file to it via `zentra_vault_upload_attachment`. Confirm the page and
   its attachment appear in the Vault on the web UI. Note that `filePath` is
   read on the machine running this server, not on Claude's side.
6. Ask Claude to create a Vault page with `folder` set to the name of an
   existing folder and `tag` set to a registered tag. Confirm the page lands
   under that folder, tagged, on the web UI.
7. Ask Claude to list sprints via `zentra_sprint_list` with `status: "OPEN"`,
   then create or move a task with that sprint's id as `sprintId`. Confirm the
   task shows up in that sprint on the web UI.

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation5/5

The three tools have clearly distinct purposes: create, list, and move (status update). There is no overlap in functionality, making it easy for an agent to select the right operation.

Naming Consistency5/5

All tool names follow the identical pattern `zentra_task_<verb>` with snake_case. This consistent verb_noun structure makes the API predictable and easy to navigate.

Tool Count4/5

At 3 tools, the server is concise but not under-powered for a focused task management use case. The count is within the well-scoped range, though slightly lean.

Completeness4/5

The set covers the core lifecycle: creation, listing/filtering, and status transitions. Missing operations like full detail retrieval or deletion are minor gaps that can often be worked around via listing, but a dedicated get/update would improve completeness.

Maintenance

ActivitySlowing
ResponsivenessNo issues