# Gantora MCP server

One file, zero dependencies, Node 18+: `gantora-mcp.mjs`. It lets any MCP-speaking
assistant read and carefully extend the `.gantora` plans in your `DB` folder(s) —
locally, over stdio, with **no network and no token**. The folders you name on the
command line are the whole permission; nothing outside them is ever read or written.

```
node gantora-mcp.mjs /path/to/DB [/another/DB …] [--read-only]
```

| Tool | What it does | Writes? |
|---|---|---|
| `list_plans` | the plans in the folder(s): name, rows, modified, read-only flag, last writer | – |
| `read_plan` | one plan as structured JSON — tasks with dates, people, group tree (parent / level), status + dated history, labels, drift; a summary (leaf-task counts by schedule bucket, overdue / blocked / late-start ids, milestones) | – |
| `add_task` | one new task (optionally inside a group, with predecessors, people, labels, notes); `Planned` with its dated History, a fresh UID, Created / Modified stamps | ✅ |
| `set_status` | move one task to `Planned` · `In Progress` · `On Hold` · `Blocked` · `Done`; appends to History and History By, stamps Modified | ✅ |

The rules are the ones in [`skill/gantora-plans/SKILL.md`](../skill/gantora-plans/SKILL.md),
enforced in code: exactly five status words; History append-only; UIDs never change;
every `#VIEW` / `#DEL` / `#META` line carried through verbatim; a plan that declares
itself read-only (`#META|readOnly|yes`) is never written; every write is stamped
(`#META|writer|gantora-mcp 0.1.0`) and atomic (temp file, then rename); a write
rewrites **only the rows it changed** and copies every other byte of the file through.
`--read-only` starts a server that offers the two readers only.

Deleting rows, changing dates, restructuring groups and editing the shared
`_lists.csv` are deliberately **not** tools — they are the app's job.

## Wiring it into a client

The shape is the same everywhere: a command (`node`) and its arguments (the server
file, then your DB folder). Use absolute paths. Restart the client after editing.
*(These are the shapes the clients documented as of August 2026 — check against
your client's current docs on first use; the server itself does not care which
client speaks to it.)*

**Claude Desktop** — `claude_desktop_config.json` (Settings → Developer → Edit Config):

```json
{
  "mcpServers": {
    "gantora": {
      "command": "node",
      "args": ["/absolute/path/to/gantora-mcp.mjs", "/absolute/path/to/DB"]
    }
  }
}
```

**Claude Code** (CLI):

```
claude mcp add gantora -- node /absolute/path/to/gantora-mcp.mjs /absolute/path/to/DB
```

**Cursor** — `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` globally — the
same `mcpServers` object as Claude Desktop.

**Codex CLI** — `~/.codex/config.toml`:

```toml
[mcp_servers.gantora]
command = "node"
args = ["/absolute/path/to/gantora-mcp.mjs", "/absolute/path/to/DB"]
```

**Gemini CLI** — `~/.gemini/settings.json`: the same `mcpServers` object as Claude Desktop.

Add `"--read-only"` to `args` for an assistant that should only report.

## Trying it by hand

It speaks JSON-RPC 2.0, one message per line, on stdin / stdout:

```
printf '%s\n' \
 '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"me","version":"0"}}}' \
 '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_plans","arguments":{}}}' \
 | node gantora-mcp.mjs /path/to/DB
```

The test suite (`node gantora.js`, from the repo root) drives it both in-process and
over a real stdio child, and puts every file it writes through the app's own parser.
