# noid's MCP server noid ships a local stdio MCP server, `mcp.js`, so an agent can work with a vault's graph - connections, their types and notes, who wrote what - without noid's window being involved. It works whether or not the noid app is running. Every file in a vault is a card, of any type, except dot paths, `node_modules` and files ignored by the vault's git ignore rules (its `.gitignore` files at any depth, the repo's `.git/info/exclude`, and the `.gitignore` files above the vault inside its repo). Agents keep changing files with their own tools; the MCP tools cover what the file system can't express: the graph, authorship, activity, and `move_file` (a rename that keeps a file's connections). Any existing folder may be a vault. Every tool call takes a `vault` argument - the absolute path of the folder to work in - so one server process can serve any number of vaults; there's no `list_vaults` call, so the agent must be told (or figure out) the path itself. ## Build it ```sh npm run build # compiles main.js, out/renderer and mcp.js ``` ## Register it with an agent One entry per agent, not per vault - the agent names its vault on every call. Claude Code, at user scope, so it's available in every session regardless of project (it only loads when a session starts, so restart after adding it). The usual way is the installed launcher. `npm run install-local` release-builds noid, installs it under `~/.local/share/noid` with `noid` and `noid-mcp` in `~/.local/bin`, and registers `noid-mcp` at user scope as `--agent claude`: ```sh claude mcp add noid --scope user -- ~/.local/bin/noid-mcp --agent claude ``` The installed copy is separate from the repo, so rebuilding in development never touches it; re-run the installer to update it. A project picks its own agent name with a `.mcp.json` in its root; a project entry wins over the user one: ```json {"mcpServers": {"noid": {"command": "noid-mcp", "args": ["--agent", "noid-dev"]}}} ``` To run the server from the repo instead (no install): ```sh claude mcp add noid --scope user -e ELECTRON_RUN_AS_NODE=1 -- /mcp.js --agent ``` `` is the real Electron binary acting as node, since there usually isn't a `node` on the agent's PATH. `node_modules/.bin/electron` won't work for this - it's a node script itself (`#!/usr/bin/env node`), so it fails in exactly the case this is for. Use the real binary instead: - Linux: `/node_modules/electron/dist/electron` - macOS: `/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron` - Windows: `\node_modules\electron\dist\electron.exe` The name comes before `-e`, since `-e` takes several values and would otherwise swallow it. If `node` *is* on the agent's PATH, this also works without `ELECTRON_RUN_AS_NODE`: ```sh claude mcp add noid --scope user -- node /mcp.js --agent ``` Options: - `--vault ` (optional): the default vault for a call that omits its own `vault`. Handy for a per-vault registration; without it, every call must name its own vault. - `--agent ` (default `default`): the agent's identity, matching `[a-z0-9-]{1,40}`. - `-h`, `--help`: print the usage and exit without starting the server. ## Names are identities The `--agent` name is the agent's identity in the graph: `--agent claude-research` writes as `agent:claude-research`, shown on the board as its own ◆ and stored in `graph.edn` as `:agent/claude-research`. Give each agent its own name - two agents sharing a name share their notes and permissions. The name comes only from the launch argument; no tool argument can change it. ## Rules the tools enforce ("add, not override") - An agent may set a connection's type only when it has none or the agent set it; otherwise `forbidden` (use `write_note` to add your own view). - Every author has their own note on a connection; `write_note` only ever changes the caller's. - `disconnect` only removes connections the caller created (a connection with no recorded creator counts as the human's). - `claim_file` only claims files nobody created. - `move_file` is allowed on any file, because refusing would push agents to `mv`, which strands connections on a ghost card. - The human may override anything from the UI. ## The vault lock Only the holder of `/.noid/lock` writes `graph.edn`. noid holds it while a vault is open. The MCP server takes it for one tool call at a time (a 30 s lease), checks it still holds it right before writing, and releases it afterwards. A lock whose process is dead, or whose lease has run out, is stale and taken over (moved aside to `lock.stale-`). Two modes, chosen per call by who holds the lock: - **Direct** (noid closed, or open on another folder): the server takes the lock, reads `graph.edn` and scans the folder, plans the call, writes `graph.edn`, and releases. The next time noid opens the vault, it shows the result. - **Relay** (noid has the vault open): the server sends the call over noid's local socket - a unix socket under Electron's userData dir, falling back to the OS temp dir when that path would be too long for a unix socket (`/noid-.sock`, owner-only; recorded in the app's lock, but only once the app is actually listening on it - never a stale or dead path). The app applies it as an action, so the change appears on the board as it happens, and replies. Before planning a call, noid looks at the files it names on disk, so a note an agent has only just written is already known. If the app's socket doesn't answer, the lock counts as stale and the server falls back to direct mode. If noid doesn't answer within 10 s, the agent is told `unavailable`, but the change may still be applied afterwards. `move_file` on a card the human has unsaved text in answers `busy`. A read-only call (`vault_info`, `describe_file`, `find_connections`, `list_types`, `recent_activity`) against a folder with no `.noid/` yet never creates one, or the lock, or a lease - it just scans the folder and answers. `.noid/` (and `graph.edn`) only appears once a write actually happens there. If the vault is itself a git repository, `.noid/lock` shows as untracked; add it to that repository's ignore file. ## Limitations - Durability differs by mode. In relay mode the `ok` comes back before `graph.edn` is written (the app saves 300 ms after a change), so a crash inside that window loses a change the agent was told succeeded. Direct mode is durable when it replies. - If noid doesn't answer a relayed call within 10 s, the agent is told `unavailable`, but the change may still be applied afterwards, once noid gets around to it. - `recent_activity` reflects current state only: no deletions, only the latest type of a connection, no edits to a card's text. - Agents can still overwrite or `rm` files with their own tools; a deleted card shows as a ghost. - Any folder can be a vault; every call rescans it (skipping dot-folders, node_modules and git-ignored folders), so a very large folder such as your home directory makes calls slow. - Taking over a stale lock can, in a narrow race, move aside a lock another process just created; token checks before every graph.edn write (server and app) turn that into a failed or retried call, not a lost write. What remains is the gap between the token check and the write.