{
  "markdown": "<p align=\"center\">\n  <img src=\"assets/NapkinLogo.png\" alt=\"Napkin\" width=\"300\">\n</p>\n\n<p align=\"center\">\nNapkin is a shared Excalidraw canvas for agent + human collaboration.\n</p>\n\n## Why We Built It\nI love using AI assistants for everyday tasks — design, programming, and brainstorming. I’ve always found visual thinking helpful, but there was a gap: I could describe something, and the agent could describe it back, but we couldn’t point to the same thing. What if my agent and I could share a whiteboard — where I draw, it responds, and we build together?\n\nNapkin works with any MCP-capable agent. For true two-way canvas-to-agent, you'll also need a webhook.\n\n## Examples (all drawn by Perry the agent, with Napkin)\n\nBrainstorming\n\n<p align=\"center\">\n  <img src=\"assets/brainstorm.png\" alt=\"Brainstorm\" width=\"300\">\n</p>\n\nGaming\n\n<p align=\"center\">\n  <img src=\"assets/snake_game.png\" alt=\"Games\" width=\"300\">\n</p>\n\n\"Art\" - Don't know why Perry invented a Platabird, but there you are.\n\n<p align=\"center\">\n  <img src=\"assets/self_portrait.png\" alt=\"Art\" width=\"300\">\n</p>\n\nCollaboration\n\n<p align=\"center\">\n  <img src=\"assets/collab.png\" alt=\"Collaboration\" width=\"300\">\n</p>\n\n\n## What It Is\nNapkin is an [Excalidraw](https://excalidraw.com) whiteboard connected to an MCP server. Any MCP-capable agent can read the canvas, draw on it, annotate it, animate it, and respond to what you draw — all while you interact with the agent through your normal channel (Slack, WhatsApp, Telegram, Terminal, or any MCP client).\n\nThe conversation stays in your channel. The canvas is pure whiteboard.\n\n<p align=\"center\">\n  <img src=\"assets/overview.png\" alt=\"Overview\" width=\"600\">\n</p>\n\n\n## Quick Start\n\n### 1) Start the MCP server\n\n```bash\ncd mcp\nnpm install\nnpm run build\nnpm start\n```\n\nDefaults:\n- MCP HTTP: `http://localhost:3003`\n- WebSocket: `ws://localhost:3002`\n\n### 2) Start the browser UI\n\n```bash\ncd ui\nnpm install && npm run dev\n# Open http://localhost:5173\n```\n\n### 3) Connect your agent\n\nNapkin works with any MCP-capable agent. Add to your MCP client config:\n```json\n{\n  \"napkin\": {\n    \"type\": \"http\",\n    \"url\": \"http://localhost:3003\"\n  }\n}\n```\n\n**Two-way vs one-way:** MCP alone gives you agent→canvas (read, write, animate). For canvas→agent (the agent waking up when *you* draw something), you need a webhook receiver on your agent's side.\n\n| Mode | What you get | Requires |\n|------|-------------|---------|\n| MCP only | Agent can read and write the canvas | Any MCP client |\n| MCP + webhook | Agent also reacts to human canvas activity | Webhook receiver (see below) |\n\n### 4) Configure the webhook (two-way only)\n\nWhen you draw on the canvas, Napkin POSTs a trigger to your agent's webhook endpoint. The agent wakes up, reads the canvas diff, and responds.\n\n**OpenClaw** — built in. Set `NAPKIN_TRIGGER_WEBHOOK` in your environment and it routes automatically.\n\n**NanoClaw** — add the webhook channel via [PR #1488](https://github.com/qwibitai/nanoclaw/pull/1488):\n\n```bash\n# .env additions\nWEBHOOK_PORT=3200\nWEBHOOK_LINKED_JID=<your channel JID>   # the chat where the agent should post\n```\n\nThen pass the webhook URL when starting your session:\n\n```\nstart_session({\n  session_id: \"<your channel ID>\",\n  webhook_url: \"http://localhost:3200/webhook\"\n})\n```\n\n**Custom / other frameworks** — any HTTP server that accepts `POST /webhook` with `{ message, sender? }` JSON and forwards to your agent works fine.\n\n\n## Environment Variables\n\nCommon server variables:\n\n- `NAPKIN_TRANSPORT` (`http` or `stdio`, default `http`)\n- `NAPKIN_MCP_PORT` (default `3003`)\n- `MCP_WS_PORT` (default `3002`)\n- `AGENT_TRIGGER_DEBOUNCE_MS` (default `3000`)\n- `NAPKIN_TRIGGER_WEBHOOK` (optional global webhook URL)\n- `NAPKIN_COMPACT_TRIGGERS` (`true`/`false`, default `false`)\n- `NAPKIN_TRIGGER_INCLUDE_CANVAS` (`true`/`false`, default `false`)\n- `NAPKIN_SESSION_TTL_MS` (default `7200000`)\n- `NAPKIN_EXPORT_DIR` (optional base dir for relative exports)\n- `ANTHROPIC_API_KEY` (required for vision tools only - others may be used)\n\nSee `ARCHITECTURE.md` for full details.\n\n## Core Concepts\n\n### The Intent API\n\nNo coordinates. No boilerplate. Describe what you want.\n\n```\nadd_node(\"Auth Service\", shape: \"rectangle\", metadata: { intent: \"entry point\" })\nadd_node(\"Token Store\")\nconnect(\"Auth Service\", \"Token Store\", label: \"issues token\")\nlayout()\n```\n\nThe server handles placement, bindings, and layout. A 3-node diagram takes one short exchange.\n\n### Reading the Canvas\n\n`get_canvas()` returns a semantic structure — nodes, edges, zones — not raw coordinates:\n\n```json\n{\n  \"nodes\": [\n    { \"id\": \"abc\", \"label\": \"Auth Service\", \"type\": \"box\",\n      \"metadata\": { \"intent\": \"entry point\", \"status\": \"wip\" } }\n  ],\n  \"edges\": [\n    { \"id\": \"xyz\", \"from\": \"abc\", \"to\": \"def\", \"label\": \"issues token\" }\n  ]\n}\n```\n\nFor cheap reasoning passes, `get_canvas_summary()` returns nodes and edges only — no zones, sketches, or proximity properties.\n\n### Metadata\n\nEvery element carries a `customData` object — invisible in the UI, readable by agents:\n\n```\nadd_node(\"Deploy Production\", metadata: {\n  type: \"task\",\n  step: 8,\n  owner: \"platform-team\",\n  status: \"wip\"\n})\n```\n\nAgents use metadata to reason about *what* elements represent, not just what they look like. `trace_path()` can filter by metadata to find specific flows.\n\n### Triggers and Webhooks\n\nWhen you draw or annotate, Napkin notices. After a quiet period, it POSTs a trigger to your agent's webhook:\n\n```json\n{\n  \"session_id\": \"slack:D0AGREPG84R\",\n  \"source\": \"debounce\",\n  \"message\": \"[napkin] Canvas updated (idle)\",\n  \"changed_element_ids\": [\"rect-42\"],\n  \"changed_elements_compact\": [\n    { \"id\": \"rect-42\", \"type\": \"rectangle\", \"label\": \"Change the timer to 120s?\" }\n  ],\n  \"change_summary\": \"added text \\\"Change the timer to 120s?\\\"\",\n  \"change_type\": \"semantic\"\n}\n```\n\nThe agent wakes up, reads what changed, and responds — on the canvas and in your channel.\n\n**Trigger sources:**\n- `debounce` — canvas went quiet (configurable per session, default 3s)\n- `chat` — injected by your agent framework\n- `reconnect` — browser connected or reconnected\n\n**Echo suppression:** agent writes don't trigger webhooks. Only genuine human edits fire triggers.\n\n### Thought Bubbles\n\nAgents express tentative ideas as thought bubbles — dashed purple, clearly \"agent suggestion\":\n\n```\nadd_thought_bubble(\"Should this connect to the cache layer?\", near_node_id: \"db-node\")\n```\n\nYou can confirm them (makes permanent) or dismiss them. Keeps agent suggestions visually distinct from established content.\n\n### Batch Execution: `apply_intents`\n\nCollapse many sequential calls into one:\n\n```\napply_intents([\n  { tool: \"add_node\", args: { label: \"A\" }, ref: \"n1\" },\n  { tool: \"add_node\", args: { label: \"B\" }, ref: \"n2\" },\n  { tool: \"connect\", args: { from_id: \"$ref:n1.id\", to_id: \"$ref:n2.id\" } },\n  { tool: \"layout\", args: {} }\n])\n```\n\nRef substitution (`$ref:n1.id`) lets later ops use outputs from earlier ones — no round-trips needed to get IDs. A 10-node diagram with connections and layout ships in a single MCP call.\n\n### Swimlanes: `zone` + `row`\n\nPin nodes to explicit layout rows independent of edge structure. Nodes sharing the same `zone` and `row` are snapped to the same rank band after Dagre runs — useful for shared infrastructure (databases, event buses) that should sit on a common row across subtrees:\n\n```\nadd_node(\"API\",         zone: \"main\", row: 0)\nadd_node(\"Worker\",      zone: \"main\", row: 0)\nadd_node(\"Postgres\",    zone: \"main\", row: 1)   # shared infra row\nconnect(\"API\", \"Postgres\")\nconnect(\"Worker\", \"Postgres\")\nlayout(\"TB\")\n```\n\nRow index is the rank axis (top-to-bottom in `TB`, left-to-right in `LR`). Unzoned nodes fall back to natural Dagre placement.\n\n### Path Traversal: `trace_path`\n\nFind flows through the graph without manual graph walking:\n\n```\ntrace_path({\n  from_label: \"Button 1\",\n  direction: \"downstream\",\n  filter: { node_metadata: { status: \"error\" } }\n})\n```\n\nReturns ordered nodes and edges. Filter by metadata to isolate happy paths, error paths, or any subset. Combine with `apply_intents` to animate the result.\n\n---\n\n## Tool Examples\n\n### Flow Diagram\n\n```\nadd_node(\"Input\")\nadd_node(\"Process\")\nadd_node(\"Output\")\nconnect(\"Input\", \"Process\")\nconnect(\"Process\", \"Output\")\nlayout(\"LR\")\n```\n\n### State Machine (with animation)\n\n```python\n# Build a traffic light state machine\napply_intents([\n  { tool: \"add_node\", args: { label: \"RED\",    shape: \"ellipse\", style: { background: \"#ff6b6b\" } }, ref: \"red\" },\n  { tool: \"add_node\", args: { label: \"GREEN\",  shape: \"ellipse\", style: { background: \"#69db7c\" } }, ref: \"grn\" },\n  { tool: \"add_node\", args: { label: \"YELLOW\", shape: \"ellipse\", style: { background: \"#ffd43b\" } }, ref: \"yel\" },\n  { tool: \"connect\",  args: { from_id: \"$ref:red.id\", to_id: \"$ref:grn.id\", label: \"60s\" } },\n  { tool: \"connect\",  args: { from_id: \"$ref:grn.id\", to_id: \"$ref:yel.id\", label: \"45s\" } },\n  { tool: \"connect\",  args: { from_id: \"$ref:yel.id\", to_id: \"$ref:red.id\", label: \"5s\"  } },\n  { tool: \"layout\",   args: { style: \"LR\" } }\n])\n\n# Animate the cycle\napply_intents([\n  { tool: \"animate_element\", args: { id: \"$ref:red.id\", to: { opacity: 100 }, duration_ms: 700 } },\n  { tool: \"animate_element\", args: { id: \"$ref:red.id\", to: { opacity: 20  }, duration_ms: 200 } },\n  { tool: \"animate_element\", args: { id: \"$ref:grn.id\", to: { opacity: 100 }, duration_ms: 700 } },\n  ...\n])\n```\n\n### CI/CD Pipeline (real example)\n\n22 operations — 10 nodes, 11 connections, layout — in a single MCP call. The agent then calls `trace_path` to find the happy path and animates it with another single call.\n\n*(See `cicd-pipeline-demo.excalidraw` for the saved canvas.)*\n\n### Responding to Human Input\n\nA human types \"Change the timer to 120s?\" directly on the canvas. The agent's webhook fires. The agent reads `changed_elements_compact`, finds the text, acts on it, and responds with a thought bubble confirming the change — all without leaving the whiteboard.\n\n---\n\n## Agent Instructions Template\n\nAdd this to your agent's instructions (adapt webhook URL for your framework):\n\n```\n## Napkin (Collaborative Canvas)\n\nWhen the napkin MCP server is available, you have a shared Excalidraw whiteboard.\n\n**First — always call:**\n  start_session({ session_id: \"<your channel ID>\", webhook_url: \"<your webhook>\" })\n\n**Drawing — use the intent API (no coordinates needed):**\n  add_node, connect, move, resize, style, add_label, delete_element, layout\n\n**For multi-step operations:**\n  apply_intents([...ops...]) — build entire diagrams in one call, chain refs\n\n**Reading:**\n  get_canvas_summary() — cheap reasoning pass (nodes + edges only)\n  get_canvas() — full semantic output including zones and metadata\n  trace_path(from_label, direction, filter) — graph traversal\n\n**On webhook trigger:**\n  1. add_thought_bubble() — acknowledge visually before processing\n  2. Use changed_elements_compact from the payload — no round-trip needed\n  3. Skip triggers with change_type \"cosmetic\" if you only care about structure\n  4. Skip triggers with source \"reconnect\"\n\n**Metadata convention:**\n  intent, notes, status (wip|review|done|parking_lot), owner\n```\n\n---\n\n## Tool Reference\n\n### Read\n| Tool | Purpose |\n|------|---------|\n| `get_canvas` | Full semantic canvas — nodes, edges, zones, metadata |\n| `get_canvas_summary` | Compact read — nodes and edges only |\n| `get_canvas_raw` | Raw Excalidraw JSON |\n| `get_canvas_diff` | Elements changed since a timestamp |\n| `get_pending_triggers` | Poll for triggers (pull mode) |\n| `trace_path` | Traverse graph from a node, with optional metadata filtering |\n| `get_server_instructions` | Compact or verbose server-side guidance for agents |\n\n### Write\n| Tool | Purpose |\n|------|---------|\n| `apply_intents` | Execute ordered batch of operations in one call |\n| `add_node` | Add labeled node — server handles placement. Optional `zone`+`row` pin nodes to a shared layout rank (see Swimlanes). |\n| `connect` | Connect nodes with an arrow |\n| `move` | Move element by offset (dx, dy) |\n| `resize` | Resize element, center preserved |\n| `style` | Apply color, fill, opacity, stroke |\n| `add_label` | Floating text near an element |\n| `delete_element` | Remove element and bound text |\n| `patch_canvas` | Partial field updates on existing elements |\n| `update_canvas` | Add elements with full definitions |\n| `clear_canvas` | Remove everything |\n\n### Layout\n| Tool | Purpose |\n|------|---------|\n| `layout` | Auto-arrange via Dagre (`TB`, `LR`, `tree`, `hierarchy`) |\n\n### Thought Bubbles\n| Tool | Purpose |\n|------|---------|\n| `add_thought_bubble` | Dashed purple tentative suggestion |\n| `confirm_thought_bubble` | Make permanent |\n| `dismiss_thought_bubble` | Remove |\n| `list_thought_bubbles` | List current bubbles |\n\n### Vision\n| Tool | Purpose |\n|------|---------|\n| `describe_elements` | Render to PNG, send to Claude vision for description |\n| `describe_sketch` | Convenience wrapper for freehand elements |\n\n### Animation & Export\n| Tool | Purpose |\n|------|---------|\n| `animate_element` | Interpolate position/size/opacity/color over time |\n| `export_canvas` | Save to `.excalidraw`, `.svg`, or `.png` |\n\n### Sessions\n| Tool | Purpose |\n|------|---------|\n| `start_session` | Begin session with channel ID, webhook, debounce override |\n| `end_session` | End session |\n\n---\n\n## Troubleshooting\n\n**Canvas appears blank after agent draws something**\nThe browser is the source of truth. If it reconnected between your agent's write and the next read, the server cache was reset. Call `start_session` again and redraw.\n\n**Webhook isn't firing**\nConfirm `start_session` was called with the correct `session_id` and `webhook_url`. Triggers are suppressed on agent writes — only human canvas edits fire them.\n\n**Agent can't see what I drew**\nCheck `get_canvas_diff` with a recent timestamp. If the browser tab was closed and reopened, the canvas state was re-synced from the browser — call `get_canvas` for a fresh read.\n\n**`export_canvas` produces a blank file**\nExport immediately after drawing — don't let the browser reconnect between the write and export calls. Use an absolute path on the host machine.\n\n## Design Decisions\n\n**Canvas state lives in the browser.** The server caches it for agents but the browser is the source of truth. If the server restarts, the browser reconnects and re-syncs — no data loss as long as the tab is open. Export to `.excalidraw` for persistence.\n\n**No chat UI.** The conversation lives in your agent's channel. Napkin is a visual tool. The trigger/webhook system routes canvas events back to the right conversation automatically.\n\n**Echo suppression.** Agent writes don't trigger webhooks. The server tracks which element IDs were written by agents and ignores browser echoes of those writes for ~2s. Only genuine human edits fire triggers.\n\n**Per-session isolation.** Each `start_session` call gets its own debounce timer, webhook URL, and trigger context. Multiple agents can share a canvas with independent notification channels.\n\n\n## Contributing\n\nPRs welcome. The most useful directions right now: additional layout engines, integrations with other agent frameworks, and tighter agentic loop patterns. Open an issue first for anything structural.\n\n## Security\n\nPlease review `SECURITY.md` for reporting guidance.\n\n## License\n\nMIT\n",
  "bytes": 15313,
  "sha": "569a7328c0abb085e51f115a74ca89acfa2f5bbac829b3689b43748402463fbd",
  "repo_slug": "sshwarts/napkin",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sshwarts_napkin_34cabfef/readme"
}