Skip to main content
Glama

inkwire

A local MCP server with a shared drawing canvas, for collaborating visually with an AI agent on a codebase. You draw a system freehand in the browser; the server infers structure; Claude reads the board as data over MCP, edits it, and discusses it. Both of you write to the same board.

Quick start

Inkwire needs Node 26 or later: Node runs the TypeScript source directly, so there is no build step. Claude Code starts the plugin in the folder of your session, and a version manager can then give the Node of that project's .nvmrc. The plugin uses the node on PATH when it is Node 26 or later, else mise x node@26 when mise is installed. So if your projects pin an older Node, install mise.

Inkwire ships as a Claude Code plugin: the MCP server, a Stop hook, the /use-inkwire and /back-to-claude-code commands, and the model-invocable trace-path skill. This repo is also the plugin's marketplace. Install it one time; it then loads in every session:

claude plugin marketplace add TheTechChild/inkwire
claude plugin install inkwire@inkwire

Claude Code copies the plugin/ folder of this repo into its plugin cache and installs the runtime dependencies there from plugin/package.json and plugin/package-lock.json (with npm ci --ignore-scripts). You do not run a build or yarn install for the plugin.

For a one-off run without installing, claude --plugin-dir /path/to/inkwire/plugin does the same for that session (it uses the node_modules of the checkout, so run yarn install in the checkout first). On the default data dir, an installed build always comes before a checkout build: when the plugin is also installed, that session uses the daemon of the installed plugin. To run the daemon of the checkout, use yarn dev:claude, which has its own port and data dir. Tool names carry the plugin prefix: mcp__plugin_inkwire_inkwire__boards_create.

Updates

The plugin has no version number, so Claude Code uses the commit SHA: each merge to main is an update.

  • To get updates automatically, turn on auto-update for the inkwire marketplace in /plugin → Marketplaces. Or set "autoUpdate": true on the marketplace in extraKnownMarketplaces in ~/.claude/settings.json:

    {
      "extraKnownMarketplaces": {
        "inkwire": {
          "source": { "source": "github", "repo": "TheTechChild/inkwire" },
          "autoUpdate": true
        }
      }
    }
  • To update by hand, run claude plugin marketplace update inkwire, then claude plugin update inkwire@inkwire.

  • After an update, run /reload-plugins in a session that is open (or start a new session).

  • A change to the daemon needs one Restart in the panel. The daemon does not restart by itself (see After an update).

The Session tab (below) needs two settings. Put them in ~/.claude/settings.json to make them permanent, or in the .claude/settings.json of the project you use inkwire from:

{
  "env": { "CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS": "0" },
  "permissions": { "defaultMode": "auto" }
}

The canvas works without them. This repo's own .claude/settings.json already carries both.

Inkwire runs as one daemon. The first Claude Code session starts it, and it stops 30 s after the last session closes. All sessions share the daemon and port 4691. Each session runs a small relay (plugin/src/link/relay.ts) that connects to the daemon.

Then ask Claude to create a board (boards_create). boards_create needs a project_root: the absolute path of the checkout that the board's code refs point into. The tool result contains the panel URL — open it in your browser:

http://127.0.0.1:4691/?board=<board id>

Draw with the pen (P), then press infer_structure (or ask Claude to run it). Closed shapes become nodes; connecting lines become edges. Claude renames the nodes after reading a screenshot.

Related MCP server: excalidraw-mcp

Many sessions

Many Claude Code sessions can use inkwire at the same time, in different repos. Each board has a maximum of one AI Author. The first Claude Code session that writes to a board becomes its Author. The other sessions are readers: they can read the board, but a write fails and names the Author. The panel shows the Author and the number of readers.

To give the board to a different session, click Release in the panel. Then the next write from a different session claims the board. The released session cannot claim that board again until a different session claims it, the released session closes, or you click Allow pid N in the panel. No agent can take authorship away from a different session.

For a git worktree, use boards_clone with the worktree as the new project_root. The session that clones the board becomes the Author of the clone.

After an update

The daemon does not restart by itself after a plugin update or a change to the source. The build id is a hash of the source in plugin/src/, of plugin/package.json and plugin/package-lock.json, and of the fonts, so an update that changes only a dependency is a new build too. A newer build is the build that the last new Claude Code session started (an installed build always comes before a checkout build). So after a downgrade or a pin to an older commit, the older build is the newer one. When a Claude Code session starts with a newer build, the panel shows a stale-build notice. To use the new build, click Restart in the panel, or run yarn daemon:restart. When all Claude Code sessions close, the daemon stops after 30 s, and the next session starts the new build. A restart drops the undo history and every pending session_send on every board.

After a reconnect or a restart, each session keeps its current board. It does not keep its authorship or its inkwire mode. The next write claims the board again if the board is free (see Many sessions).

Session tab: talking in the panel

Type /use-inkwire in the terminal. Claude flips a server-held mode flag, and from then on delivers replies through the blocking session_send tool into the panel's SESSION tab, where you answer from the composer. A reply can carry a highlight: node and edge ids the canvas lights up. It can also carry a path: an ordered walk over a layer's edges, one caption per hop, that plays in the panel. The Layers tab lists each layer's paths; click ▸ on a path row to open the scrubber as a vertical walk under the row, one row per node and one per hop with its caption. Hold ▸ to peek the walk on the canvas. Drag the walk or press ← → to step; esc closes it. The /trace-path skill writes one when you ask "walk me through" or "what happens when". /back-to-claude-code (typed, or the button in the strip) brings replies back to the terminal.

Two requirements, both checked by the server when the mode goes on, and both covered by the settings above:

  • Permission mode auto or bypassPermissions — nobody is at the terminal to approve prompts. Switching modes inside the session works too; the check runs when /use-inkwire does.

  • CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS=0 — otherwise Claude Code moves the blocking call to a background task after two minutes.

With the inkwire mod on (ADR 0004), these two requirements do not apply. session_send does not block: it posts the message and the turn ends. Your reply, from the browser composer, goes to Claude Code as the next prompt. You see permission prompts in the terminal. The mod shows one status line under the prompt: inkwire · <board> · <state>, where the state is pty, claude is working, waiting on you, reply queued or reply refused · /inkwire send. Type /inkwire for the status and the browser panel URL. If Claude Code refused your reply (for example, you pressed Esc), /inkwire send sends it again and /inkwire discard drops it. Without the mod (mods off, or an older Claude Code), the blocking model above applies.

The mode is per Claude Code session, and only the board's Author can turn it on. The mode is not persisted: after a reconnect or a daemon restart, the session is in the terminal again. A session_send that waits 20 minutes with no answer returns idle and flips the mode back.

Development

yarn install
yarn dev          # dev daemon from the source; restarts on a server change, bundles the panel again on a panel change
yarn dev:claude   # Claude Code with plugin/ as the plugin, on the dev daemon
yarn test         # full vitest suite
yarn health       # run every MCP tool through a real daemon and relays
yarn typecheck
yarn check-plugin   # the marketplace, the manifest, the hooks and the plugin's npm package
yarn check-install  # install plugin/ as Claude Code does (npm ci) and run it one time
yarn lock-plugin    # write plugin/package.json and plugin/package-lock.json again

Run yarn lock-plugin after a change to the root dependencies or to yarn.lock. The root dependencies are the runtime dependencies of the plugin, and plugin/package.json pins them to the exact versions that yarn.lock resolves. Each package in plugin/package-lock.json, also a transitive one, has the version that yarn.lock resolves, so the tests run the same packages that users get. yarn check-plugin fails in CI when they do not agree. yarn check-install copies only the files of plugin/ that git tracks, as Claude Code does, and stops when plugin/ has a file that git does not track. plugin/package.json is not a Yarn workspace: Yarn ignores it.

yarn dev runs the dev daemon on port 4692 with ~/.inkwire-dev, INKWIRE_IDLE_GRACE_MS=off and INKWIRE_UI_WATCH=1 (reload the page after a panel change), so it does not touch the daemon on 4691 or the data in ~/.inkwire. Panel URL: http://127.0.0.1:4692/?board=<id>.

yarn dev:claude starts Claude Code with --plugin-dir ./plugin and the dev port and data dir. The relay and the hook use the dev daemon. If yarn dev does not run, the relay starts a daemon from the source on 4692. The relay runs from the source, so a change to the relay takes effect in the next Claude Code session.

The daemon bundles the panel (plugin/src/ui/) with esbuild in memory when it starts. A change to the panel shows after a daemon restart.

Tests use random ports and temp data dirs. They never use 4691, 4692, ~/.inkwire or ~/.inkwire-dev.

Health check: run yarn health after a change to the link, the daemon, the gate or any tool. It starts a real daemon and two or more relays on a random port with a temp dir, and runs every MCP tool through the full link. A failed case names the tool or the part of the daemon lifecycle that is broken.

Configuration (env vars): INKWIRE_PORT (default 4691), INKWIRE_DATA_DIR (default ~/.inkwire — SQLite plus an images/ directory), INKWIRE_IDLE_GRACE_MS (daemon only; default 30000, off turns the idle stop off). There is no project-root env var: each board stores its own project_root (set it with boards_create, boards_clone, boards_import or boards_update), and every code ref on the board resolves against it.

The design handoff that specifies this project lives in design_handoff_inkwire/. See CLAUDE.md for architecture notes.

Upgrade notes

To run from source

The plugin root moved from the repo root to plugin/, and there is no dist/ now. If you added the marketplace from a local checkout, remove it (claude plugin marketplace remove inkwire) and add it from GitHub (claude plugin marketplace add TheTechChild/inkwire), so that updates come with each merge to main and you do not need git pull and a build. Update the inkwire marketplace and the plugin in Claude Code (/plugin). A project .mcp.json that starts inkwire with node .../dist/link/relay.js must change to "command": "sh" with the args .../plugin/src/link/relay.sh. After the update, click Restart in the panel's stale-build notice, or close all sessions, to start the daemon from the new source.

Claude Code installs each update into a new folder and removes an old folder 14 days later. A running daemon does not need its folder after it starts: it keeps the panel in memory and copies the fonts to ~/.inkwire/fonts/. Each relay records its build in ~/.inkwire/builds.json, and the daemon that a relay starts is always the newest recorded build whose folder is still there. If that daemon cannot start, the relay starts the next build. A project .mcp.json that starts inkwire must run sh .../plugin/src/link/relay.sh (it finds a Node 26), not node .../relay.ts.

To the shared daemon

  1. Close all Claude Code sessions that use the old inkwire server. If an old session holds port 4691, the new relay stops with "port 4691 is held by an old inkwire server; close the old Claude Code sessions".

  2. Install the plugin from the GitHub marketplace (see Quick start and To run from source).

  3. A project .mcp.json that starts the old server (node .../dist/server/index.js) must change to "command": "sh" with the args .../plugin/src/link/relay.sh. Also remove INKWIRE_PROJECT_ROOT from its env: it has no effect now.

  4. Start one new Claude Code session and do the post-migration step.

Post-migration step: run boards_list and set a root on every root: unset board with boards_update. At migration time this is one board, b_8946f6 "Discover Config API — generateDiscoverConfig", root /Users/clayton.noyes/angel-studios (its 8 refs all start with content-collections/; verified 2026-10-08).

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A shared whiteboard for you and your AI agent I wanted my AI agent and me to be able to point at the same thing. Any MCP-capable agent can read the canvas, draw on it, drop thought bubbles, animate elements, and react when you sketch something.
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to create and manipulate live visual diagrams on an Excalidraw canvas in real-time via MCP tools.
    3,607 npm
    86 PyPI
    15
    BSD 3-Clause
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to draw animated, hand-drawn diagrams on a live whiteboard through MCP tools, allowing users to visually explain concepts and edit scenes interactively.
    43 npm
    AGPL 3.0