Rootr MCP Server
# rootr-cli
Official CLI and MCP server for [Rootr](https://rootr.io) — a team documentation workspace that
AI agents can read **and write**.
Point Claude, Cursor, or any MCP client at your workspace and it can browse the document tree,
read and edit markdown, add rows to a database, open and close issues, build a slide deck, run a
spreadsheet, publish a public page, sign a document, or ask a question and get an answer with
citations back to the exact paragraph.
*(한국어 문서: [README.ko.md](https://github.com/gwmage/rootr-cli/blob/main/README.ko.md))*
- `rootr` CLI — documents, databases, forms, signed documents, publishing, permissions, memory
- `rootr mcp` — stdio MCP server, **167 tools** (see [MCP tools](#mcp-tools))
Plain Node.js ESM, no build step. Node 18+ (uses the built-in `fetch`).
## Links
- Source: <https://github.com/gwmage/rootr-cli>
- npm: <https://www.npmjs.com/package/rootr-cli>
- Remote connector (nothing to install): `https://rootr.io/mcp`
- Docs: <https://rootr.io/en/docs> · authoring reference: <https://rootr.io/llms.txt>
- License: MIT (see `LICENSE`)
## Two ways to connect
**1. Remote connector — nothing to install.** Rootr hosts an MCP endpoint with OAuth:
```
https://rootr.io/mcp
```
Add it as a custom connector in Claude (Streamable HTTP), sign in, done. Same tools as the local
server. This is the recommended path for most people.
**2. Local CLI** — use this package when you want the client running on your own machine or
infrastructure, or when you want the shell commands.
```bash
npx rootr-cli mcp # run the MCP server straight from npm
npm install -g rootr-cli # or install the `rootr` command globally
```
Or from a clone:
```bash
npm install
node bin/rootr.js --help
```
Docker:
```bash
docker build -t rootr-cli .
docker run -i --rm -e ROOTR_API_KEY=rootr_xxx -e ROOTR_WORKSPACE=ws_123 rootr-cli
```
## Configuration
Precedence: **environment variables > `~/.rootr/config.json`**
| Env var | Config key | Description |
|---|---|---|
| `ROOTR_API_KEY` | `apiKey` | API key (`rootr_...`), sent as the `x-api-key` header |
| `ROOTR_WORKSPACE` | `workspace` | Workspace id |
| `ROOTR_BASE_URL` | `baseUrl` | Defaults to `https://rootr.io/api/v1` |
| `ROOTR_LANG` | — | `en` or `ko`. Without it the CLI follows your locale (`LC_ALL`/`LC_MESSAGES`/`LANG`): Korean if that starts with `ko`, English otherwise |
Save them to the config file:
```bash
rootr config --api-key rootr_xxxxxxxxxxxxxxxx --workspace ws_123
# self-hosted or dev server
rootr config --api-key rootr_xxx --workspace ws_123 --base-url https://dev.rootr.io/api/v1
```
`~/.rootr/config.json` is written with mode `600`.
### Language
Help, messages and errors come out in English by default, and in Korean when your locale says Korean.
Force it either way with `ROOTR_LANG`:
```bash
ROOTR_LANG=ko rootr --help # Korean
ROOTR_LANG=en rootr --help # English
```
The MCP server reads the same variable, so a client that spawns `rootr mcp` can set it in `env`.
Tool descriptions are English either way — that is the schema an agent consumes.
### Two kinds of key
- **Workspace key** — scoped to one workspace. Scopes: `docs:read`, `docs:write`, `graph:read`,
`ask`, `webhooks:manage`. Enough for almost every tool.
- **Account key (PAT)** — acts for the whole account. With the `workspaces:create` scope it can
create new workspaces (`rootr_create_workspace`). Trying that with a workspace key returns 403
with a hint saying an account key is required.
Show the current configuration (only the first 12 characters of the key are printed):
```bash
rootr config
```
### Working with several workspaces
Register each workspace's key once:
```bash
rootr config --add-key rootr_xxx --label "Sales"
rootr config # list registered keys
```
After that, `rootr use` (and the `rootr_use_workspace` MCP tool) accepts a **name, an id, or a
rootr.io URL**; the key that opens that workspace is selected and remembered automatically. An
account key (PAT) switches to any workspace you belong to.
A workspace key does not open anything outside its own workspace. Because of that, `rootr ws`
marks entries your registered keys cannot open — **being listed and being reachable are not the
same thing**.
Web URLs (`https://rootr.io/en/w/<ws>/d/<doc>`) can be pasted directly into any node argument.
If the URL belongs to another workspace, the error tells you to switch first.
> The remote connector (`https://rootr.io/mcp`) is bound to the single workspace you picked when
> you authorized it. For another workspace, issue a new connector for that workspace.
## CLI
Path arguments starting with `/` are resolved as **paths** through the by-path API (needs a
workspace). Anything else is treated as a **node id**.
### Documents
```bash
rootr ls # whole tree, TYPE<TAB>path
rootr ls /notes # one subtree
rootr read /notes/todo.md # markdown to stdout
rootr read /notes/todo.md --json # full JSON with metadata
echo "# Title" | rootr write /notes/new.md
rootr write /notes/new.md --file ./local.md
rootr write <nodeId> --file ./local.md --if-match '"abc123"' # optimistic concurrency
rootr append /notes/log.md "deploy finished"
rootr append /notes/log.md "- one more item" --heading "## Todo"
cat diff.md | rootr append /notes/log.md
rootr edit /notes/todo.md --find "- [ ] deploy" --replace "- [x] deploy"
rootr edit /notes/todo.md --find "TODO" --replace "DONE" --all
rootr comments /notes/spec.md # comments, including ones left on the public page
rootr search "deployment runbook"
rootr mv /notes/old.md /archive/old.md # move and/or rename
rootr rm /notes/old.md --yes # to the trash, recoverable
rootr restore <id> # undo a delete (id, not path)
rootr attach ./diagram.png --doc /notes/spec.md --heading "## Design"
```
`append` never rewrites the whole document, so concurrent writers cannot clobber each other —
prefer it over `write`. `--heading` appends to the end of that heading's section.
`edit` returns 409 if the `--find` text is missing or not unique; the server's reason is printed
as-is. `write --if-match` returns 412 if the document changed in the meantime.
### Workspaces and questions
```bash
rootr ws # id<TAB>name
rootr use "Sales" # switch — id, name, or rootr.io URL
rootr ask "why did latency go up after last week's deploy?"
rootr ask "why did payment failures rise?" --workspace ws_123
```
`ask` queries the workspace knowledge graph (GraphRAG) and prints the answer plus citations in
`document path: quote` form. Requires the `ask` scope.
### Databases
```bash
rootr db create --file spec.json --parent /projects # columns in the given order, rows included
rootr db rows /projects/tasks --body --backlinks # rows, with row bodies and inbound links
rootr db row-add /projects/tasks --values '{"Title":"Ship v2","Status":"Doing"}'
rootr db rows-new /projects/tasks --file rows.json # many at once — all or nothing
rootr db rows-set /projects/tasks --ids r1,r2 --values '{"Status":"Done"}'
rootr db rows-each /projects/tasks --json '[{"rowId":"r1","values":{"Score":3}}]'
rootr db rows-rm /projects/tasks --ids r1,r2 --yes
rootr db view /projects/tasks Calendar --scale month # views: table, board, calendar, timeline
rootr db lock /projects/tasks --schema managers --rows own --locked "Approved,Score"
rootr db notify /projects/tasks --file rules.json # notification rules
rootr db buttons /projects/tasks --file buttons.json # buttons that do work when pressed
rootr db button-run /projects/tasks fill-summary --rows r1,r2
rootr db row-page <rowId> --type RECORDING # give one row a typed detail page
```
### Forms
```bash
rootr form new /intake/request --collect db \
--fields '[{"name":"Email","type":"email","required":true},{"name":"How urgent?","type":"scale"}]'
rootr form show /intake/request # read the questions before changing them
rootr form set /intake/request --fields '[...]' # replaces the question set
rootr form collect /intake/request --to sheet # create and wire a destination
rootr form responses /intake/request --latest # --latest = newest one per person
rootr form response-edit <responseId> --values '{"Email":"a@b.com"}'
```
`--fields` takes a JSON array as a **string**, not a file path.
18 question types, including `text`, `email`, `phone`, `url`, `rating`, `scale`, `time`, `file`,
`matrix`, `ranking`, `csat` and `nps`. `--collect db|sheet` creates the destination and wires it
up, mapping questions to columns.
### Publishing and access
```bash
rootr publish /handbook --children # works for every node type
rootr publish /handbook --status # is it public, at what URL, children included?
rootr publish /projects/tasks --layout board --skin paper # 9 layouts × 6 skins
rootr publish /handbook/policy.md --comments --expires 2026-12-31T00:00:00Z
rootr publish /handbook --off
rootr access /handbook --private # or --public / --inherit
rootr access /handbook --grant <userId> --level WRITE
rootr access /handbook --revoke <userId>
rootr readreq /handbook/policy.md --users u1,u2 --due 2026-10-01T00:00:00Z --ack
rootr readreq /handbook/policy.md # who has read it, completion rate
```
### Signed documents and research notes
A signed document is a markdown document whose body **freezes the moment it is submitted**; the
people on the approval line then sign in order. Every signature carries a trusted timestamp (TSA)
and hash chaining, so tampering is provable, and signed documents are never deleted (30-year
retention). Corporate approval flows and national R&D electronic research notes are the same
feature.
```bash
# create — body and approval line in one step; a research note only needs a reviewer
rootr sign new "/notes/2026-08-25 experiment" --file note.md --reviewer <reviewer userId>
# build the approval line by hand (array order = signing order)
cat > line.json <<'EOF'
[ { "label": "Author", "members": [{ "userId": "u1", "role": "AUTHOR" }] },
{ "label": "Manager", "rule": "ANY", "members": [{ "userId": "u2" }, { "userId": "u3" }] } ]
EOF
rootr sign line "/notes/2026-08-25 experiment" --file line.json
# submit: request a confirmation code by email, then pass those 6 digits
rootr sign code "/notes/2026-08-25 experiment"
rootr sign submit "/notes/2026-08-25 experiment" --code 123456
# next signer: see only what is waiting on you, then sign (or reject with a reason)
rootr sign list --pending
rootr sign code <id> && rootr sign approve <id> --code 123456
rootr sign reject <id> --comment "supporting data is missing" --code 123456
# many at once (up to 100 with a single code)
rootr sign code --bulk
rootr sign bulk <id1> <id2> <id3> --code 123456
# proof
rootr sign verify <id> # body, signatures, timestamps and ledger intact?
rootr sign ledger # recompute the whole workspace ledger
```
**The CLI cannot mint the confirmation code for you.** It is emailed to the signer only. In a
design where the server holds the private key, that code is the single piece of evidence that a
human pressed the button. It is valid for 10 minutes, dies after 5 wrong attempts, and a repeat
request within 30 seconds is refused.
Approval-line members are `{"userId"}` or `{"email","displayName"}` — with an email address a
person can sign without a Rootr account.
A signed document is never edited in place. You issue a **revision**
(`rootr sign revise <id> --note "…"`) and the original stays as `SUPERSEDED`, because the
regulations require both the original and the trace of the change to remain visible.
### Team memory
```bash
rootr memory # recall everything
rootr memory --query seminar --kind 지시 # filtered
rootr memory add "Report times in KST only" --kind 지시 --source /rules.md
rootr memory edit <id> --content "corrected sentence" # only the fields you pass change
rootr memory forget <id> # retire (--hard deletes the row)
```
`--kind` is one of four literal values: `지시` (how the team wants work done), `사람` (someone's
role or preference), `프로젝트` (an ongoing effort), `참고` (a reference). The same four values
are the enum on the `rootr_remember` / `rootr_recall` MCP tools.
One-line facts that outlive a session and are shared by the whole workspace. This is the same
data as the `rootr_remember` / `rootr_recall` / `rootr_forget` MCP tools and the "Memory" menu in
the app. **Long things belong in a document; one-liners belong in memory.**
### Scaffolding a workspace
```bash
rootr scaffold plan "a workspace for running a hardware startup's QA process"
rootr scaffold review --file tree.json # get graded before anything is created
rootr scaffold apply --file tree.json --root /qa
rootr scaffold built --root /qa # inspect the shape of what was created
rootr scaffold test --root /qa # actually exercise relations, rollups, formulas, forms
```
### Reporting a problem
```bash
rootr feedback "rootr_add_row could not find my column by name; column ids worked." --kind friction
rootr feedback --file report.md --kind bug --email me@example.com
cat error.log | rootr feedback --kind bug
```
Sends bugs, friction and ideas to the Rootr team. **No login, no workspace, no API key required**
— it works even when a wrong key means nothing else does, and that is exactly the report we most
want. It does not count against usage limits and does not consume credits.
`--kind` is one of `bug`, `friction`, `idea`, `praise`, `other` (default `other`). Without
`--title` the first line of the body becomes the title. Pass `--email` only if you want a reply.
The same body sent twice within 10 minutes is merged into one report, so retry loops do not
pollute the queue.
## Usage limits
Only **read-type** calls (`docs:read` / `graph:read` / `ask`) made programmatically (API key or
MCP) are metered. Writes and uploads are not limited.
| Plan | Limit |
| --- | --- |
| Free | **180 calls per day** (resets at midnight KST) |
| Team | 5,000 per hour |
| Business | 50,000 per hour |
| Platform | Unlimited |
At 80% of the limit, MCP tool responses append a note with the remaining count. At the limit you
get a 429 with an upgrade link. The response headers carry the same information
(`X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-RateLimit-Window`).
## MCP tools
`rootr mcp` speaks MCP over stdio. 167 tools:
| Group | Count | Tools |
|---|---|---|
| Documents | 22 | list, read, write, append, edit, search, comment, version history, commits & merges, publish, trash & restore |
| Databases | 26 | create, columns, views, rows (single & bulk), row pages, permissions, notifications, buttons |
| CRM | 18 | companies, contacts, deals (incl. pipeline moves), activities, tasks, CSV import |
| Signed documents & research notes | 14 | create, sign, revise, reject, reopen, approval lines, signing codes, ledger verification, research-note preset |
| Forms | 10 | create, read, update, responses, share links, collectors |
| Presentations & images | 10 | decks, slides, diagram slides, image generation and background removal |
| Workspaces & scaffolding | 9 | list, create, delete, switch, plan / review / apply a document tree, health check |
| Pages | 6 | HTML page nodes: create, read, update, append and patch blocks, live data |
| Spreadsheets | 7 | create, read, update, per-sheet create/update/delete, patch cells & formulas |
| Recordings | 7 | create, upload & download audio, transcribe, summarize, rename speakers |
| Issue tracker | 6 | create tracker, list / create / get / update issue, comment |
| Permissions & visibility | 10 | grant / revoke node permissions, read access, visibility, public status, read requirements |
| LOG datastores | 5 | create store, update fields, add entries, query, stats |
| Templates | 4 | list, apply, save as template, update |
| Whiteboards | 3 | create, read and update the shapes & edges scene |
| Attachments | 2 | upload a file, attach an image straight into a document |
| Agent memory | 3 | remember, recall, forget |
| Webhooks | 3 | list, create (secret shown once), delete |
| Root-cause Q&A | 1 | ask the workspace knowledge graph, answer with citations |
| Feedback | 1 | send feedback to the Rootr team |
Guidance baked into the tool descriptions: agents should reach for `rootr_append` and
`rootr_edit`, and use `rootr_write` only for a genuine full rewrite (ideally with `ifMatch`).
Tools that take a `workspace` argument fall back to `ROOTR_WORKSPACE` / the config file, and
return a clear error if neither is set. Every tool description is written in English (it is the
schema an agent consumes), and tools that return JSON reply with pretty-printed JSON text.
Scope notes: document, database, spreadsheet, whiteboard, form, page, presentation, recording,
signed-document, issue, CRM and LOG tools need `docs:read` / `docs:write`; `rootr_ask` needs
`ask`; webhook tools need `webhooks:manage`; `rootr_create_workspace` needs an account key with
`workspaces:create`.
### Pages
A PAGE is **a screen for people outside the workspace** — an event announcement, a product page,
a sign-up. You do not write HTML: you stack **14 predefined block types** — `hero`, `text`,
`features`, `gallery`, `pricing`, `testimonials`, `faq`, `timeline`, `cta`, `footer`, the
table-backed `liveStats`, `liveTable`, `liveChart`, the `form` block, and the free-form `custom`.
`custom` is an **isolated room**: write any HTML/CSS you like, but it renders inside a `sandbox`
iframe (no scripts, not same-origin, no outbound calls), so even bold CSS cannot leak onto the
rest of the page. `<script>` and `on*=` attributes are stripped on save. Inside the room you can
use the page's palette through `var(--pg-text)`, `var(--pg-card)`, `var(--pg-accent)`.
Per-block props are tabulated in the **"Pages" section of [`/llms.txt`](https://rootr.io/llms.txt)**.
```bash
# create a page (omit blocks to start from a hero/text/footer skeleton)
curl -X POST "$BASE/workspaces/$WS/pages" -H "x-api-key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"name":"Autumn meetup","config":{"theme":"midnight"},
"blocks":[{"type":"hero","props":{"title":"Autumn Developer Meetup","subtitle":"Oct 12 · Seoul"}}]}'
# append blocks (existing ones untouched) / replace config / read live values
curl -X POST "$BASE/pages/$ID/blocks" -d '{"blocks":[{"type":"faq","props":{"items":[]}}]}' ...
curl -X PATCH "$BASE/pages/$ID" -d '{"config":{"theme":"warm"}}' ...
curl "$BASE/pages/$ID/live-data" -H "x-api-key: $KEY"
```
For `liveStats` / `liveTable` / `liveChart`, `source.nodeId` is a DATABASE or LOG node in the
same workspace and columns are referenced **by name** (not by column id). Point a `form` block's
`formNodeId` at a form node and visitors submit **right there** — the form does not have to be
published separately. Tables and forms that live under a PRIVATE subtree are silently dropped
from a public page, on purpose.
Publishing works like any other node: `POST /v1/nodes/:id/publish` → `rootr.io/p/{slug}`.
### Presentations
Decks are authored with `rootr_create_presentation`, `rootr_read_presentation`,
`rootr_append_presentation_slides`, `rootr_update_presentation_slide`,
`rootr_reorder_presentation_slides` and `rootr_update_presentation`. Artwork can be produced with
`rootr_generate_image` (image from a sentence) and `rootr_remove_image_background` — both spend
AI credits. The same thing works over plain REST with the same API key (`x-api-key`,
`docs:write`):
```bash
# create a deck (slides optional)
curl -X POST "$BASE/workspaces/$WS/presentations" -H "x-api-key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"name":"Proposal","slides":[{"id":"SLD-001","kind":"cover","title":"..."}]}'
# patch one slide (merge) / append / reorder
curl -X PATCH "$BASE/presentations/$ID/slides/SLD-001" -H "x-api-key: $KEY" \
-H 'Content-Type: application/json' -d '{"slide":{"notes":"speaker notes"}}'
curl -X POST "$BASE/presentations/$ID/slides" -d '{"slides":[]}' ...
curl -X POST "$BASE/presentations/$ID/reorder" -d '{"order":["SLD-002","SLD-001"]}' ...
```
#### Diagrams on a slide
For architecture, workflow, sequence, dataflow and lifecycle diagrams, do not hand-write SVG.
Pass a **typed JSON spec** to `rootr_create_diagram_slide`; the server validates it, renders it,
and drops it onto the slide — it survives PDF and PPTX export too. A slide's `diagram` (mermaid)
field is **not rendered by the viewer**, so this tool is the only way.
Each type has its own spec shape, so **fetch the schema and an example first with
`rootr_diagram_schema`**. A spec that does not fit is rejected with which field is wrong and how
to fix it, so you can correct it and call again.
```bash
# see the spec shape (architecture | workflow | sequence | dataflow | lifecycle)
curl "$BASE/diagram-types/workflow/schema" -H "x-api-key: $KEY"
# render it onto a slide (pass slideId to redraw an existing slide)
curl -X POST "$BASE/presentations/$ID/diagram-slides" -H "x-api-key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"type":"workflow","spec":{},"title":"An issue reaches main only through review",
"blocks":[{"heading":"Review is the gate","body":"..."}]}'
```
`quality` defaults to `standard`; `showcase` applies the stricter bar meant for hand-tuned hero
diagrams and will reject specs that `standard` accepts. Text inside a rendered diagram does not
reach the knowledge graph, so keep the facts in `title`, `blocks` and `notes` as well.
**For high-quality decks, bake self-contained 1280×720 HTML into the slide's `html` field** — the
viewer renders it verbatim instead of the default template, giving you a completely free design.
The rules (inline everything because outbound requests are blocked, images as data URIs, a font
fallback stack, `<span class="pn">` for page numbers, and keeping the text fields filled so the
deck still feeds the knowledge graph) are documented in the **"Presentations" section of
[`/llms.txt`](https://rootr.io/llms.txt)** — read it before authoring. AI image generation uses
`POST /v1/workspaces/:ws/images/generate` and spends AI credits.
### Register with Claude Code / Claude Desktop
```json
{
"mcpServers": {
"rootr": {
"command": "npx",
"args": ["-y", "rootr-cli", "mcp"],
"env": {
"ROOTR_API_KEY": "rootr_xxxxxxxxxxxxxxxx",
"ROOTR_WORKSPACE": "ws_123"
}
}
}
}
```
With a global install, use `"command": "rootr"` and `"args": ["mcp"]`. From a clone, point
`command` at `node` and `args` at `["/path/to/rootr-cli/bin/rootr.js", "mcp"]`.
## Errors
On a non-2xx response the CLI prints the API's `message` to stderr and exits 1, with hints:
- `401` / `403` — check the API key and workspace scopes
- `412` — document changed since you read it; read again and retry (If-Match mismatch)
- `409` — the server's reason is shown verbatim (e.g. `edit` found no unique match)
- `429` — usage limit reached; the message carries the reset time and an upgrade link
## Development
- Dependencies: `@modelcontextprotocol/sdk` (MCP server) and `zod` (tool input schemas). The CLI
itself (`bin/rootr.js`, `lib/config.js`, `lib/client.js`, `lib/resolve.js`) has none.
- No build step — plain ESM JavaScript.
```
bin/rootr.js entry point, command dispatch
lib/config.js config load/save
lib/credentials.js API key / workspace resolution
lib/context.js per-call request context
lib/client.js Rootr REST client
lib/resolve.js path/id target resolution
lib/mcp.js MCP stdio server, assembles the tool modules
lib/mcp-tools/*.js one module per tool group (shared, documents, pages, workspaces,
databases, spreadsheets, whiteboards, forms, presentations,
recordings, signed, templates, attachments, public, memory,
logs, issues, crm, webhooks, ask, feedback, misc)
```
Smoke-test the MCP server without any credentials — it must start and list its tools:
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| node bin/rootr.js mcp
```
## License
MIT — see [`LICENSE`](LICENSE).
TDQS
Scored across 81 tools
Most tools have distinct purposes, with clear domain separation (documents, databases, CRM, presentations, etc.). Overlaps exist within domains (e.g., rootr_read_database vs rootr_list_rows) but descriptions clarify when to use each. Overall, an agent can distinguish tools well.
All tools start with 'rootr_' and use snake_case verb_noun pattern. However, verb choice varies (list/read/get, upsert/create/update) and some tools mix actions (e.g., rootr_crm_upsert_company covers create, update, delete). Mostly consistent but with minor deviations.
81 tools is excessive for most use cases. While the server aims to be comprehensive, this many tools can overwhelm agents and increase selection errors. A more focused set (20-30) would be more appropriate for typical workspace management.
The tool set covers CRUD and advanced operations for many node types: documents, databases, spreadsheets, presentations, CRM (companies, contacts, deals, activities, tasks), issue trackers, logs, forms, whiteboards, and more. Minor gaps like user management exist, but overall it's quite complete for the workspace domain.