Skip to main content
ClaudeWave
Skill0 repo starsupdated 8d ago

cnvs-whiteboard

The cnvs-whiteboard skill enables real-time collaboration on cnvs.app whiteboards by reading shared board state and adding, updating, moving, or deleting text, sticky notes, links, images, strokes, and Mermaid diagrams. Use it whenever a user references a cnvs.app board URL, requests drawing or diagramming on a shared canvas, or provides a board ID, combining MCP subscriptions for listening to human edits with REST API calls for writing changes to avoid polling and tool-call overhead.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/lksrz/cnvs-whiteboard-skills /tmp/cnvs-whiteboard && cp -r /tmp/cnvs-whiteboard/cnvs-whiteboard ~/.claude/skills/cnvs-whiteboard
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# cnvs-whiteboard

Be a live AI collaborator on a cnvs.app board — discover changes, make edits, stay in the loop without polling, and don't wake yourself on your own writes. One skill, copy-pasteable patterns.

> **If you only read one thing.** Listen for edits via MCP subscriptions (the only real-time push channel). Act on the board via the REST API (universal — works from any runtime with outbound HTTP, no MCP client required). Don't use MCP tool-calls for writes if REST is available: it wastes tool-call slots, adds session bookkeeping, and blocks every non-MCP agent runtime from ever contributing. The hybrid **MCP-listen + REST-write** loop is what this skill wires up.

> **Service boundary.** cnvs.app is a third-party hosted service operated outside this skill / Anthropic / the user's own infrastructure. Anything written to a board (text, links, ink, images, Mermaid source) is stored on cnvs.app and is reachable by anyone who has the board ID — boards are unlisted, not private. Treat it like any other public URL: don't paste secrets, credentials, customer PII, or proprietary content unless the user has explicitly chosen cnvs.app as the surface for that content.

> **Access locks.** A board can OPTIONALLY be PIN-locked (6 chars, a-z0-9). Two modes: `write` (anyone reads, only key-holders write) and `all` (key required for both reads and writes). If you hit HTTP `401` with `{"code": "board_locked", "lockMode": "..."}` (REST) or JSON-RPC error `-32001` (MCP), the board is locked and you need the key. Pass it via the `X-Board-Key` header on REST and MCP POSTs, or as the `access_key` argument on individual MCP tools / `resources/read` / `resources/subscribe` params. WebSocket connections can't set custom headers, so the key rides in the `Sec-WebSocket-Protocol` subprotocol — open the socket as `new WebSocket(url, ['cnvs-key.<code>'])`; the server validates and echoes the same protocol back in the 101 response. The user owns the key — ask them for it rather than guessing. Lock management endpoints: `POST /api/boards/<id>/lock {mode}` returns the key ONCE on first lock; `POST /api/boards/<id>/unlock` clears the lock (header required); `POST /api/boards/<id>/verify-key {key}` is a pure check. There is no recovery — if every key-holder loses the key, the board becomes unreachable and gets auto-deleted after 30 days of inactivity.

## Need a new board

One call — no auth, no setup:

```bash
curl -s -X POST https://cnvs.app/api/boards
# → {"id":"<uuid>"}
```

That's it. Drop the returned id into `https://cnvs.app/#<id>` to share, and into every `/api/boards/<id>/...` mutation below. Use this whenever the task asks for a fresh surface and no URL was given.

Small print: the server generates the id — don't synthesise your own UUID client-side. And there is no MCP `create_board` tool, so even from an MCP-capable runtime, call this REST endpoint, then switch to MCP (`open_board(<id>)`, subscribe) for live reads.

## How to use this skill

Each time you receive a board ID (or URL), run through this checklist:

1. [ ] **Set your author tag.** Pick an `ai:<label>` (e.g. `ai:claude`, `ai:gpt4`, `ai:myagent`). Pass it on every mutation. It becomes immutable.
2. [ ] **Read the current snapshot.** `GET https://cnvs.app/json/<id>` (keep the returned `ETag`).
3. [ ] **View the SVG preview** if the board contains lines or images (see §1.4). Numbers alone won't tell you what a stroke actually depicts.
4. [ ] **Listen for changes** (optional, push-driven): install the `mcp-listen` skill and wire `cnvs://board/<id>/state.json` through `Monitor` with `--ignore-author-prefix ai:`.
5. [ ] **React** — re-read snapshot on every push (send `If-None-Match` to skip no-op pushes), then mutate via REST.

## TL;DR flow

```
┌──────────────────────┐        ┌──────────────────────────┐
│ mcp-listen skill     │        │ your agent logic         │
│ (MCP SDK + Monitor)  │── push ▶│ refresh, decide, respond │
└──────────────────────┘        └──────────────┬───────────┘
          ▲                                     │
          │ notifications/resources/updated     │
          │ (SSE, ~3 s debounced)               │ HTTP
          │                                     ▼
┌─────────┴────────────────────────────────────────────────┐
│                    cnvs.app server                        │
│  POST /mcp (subscribe, read)   POST /api/boards/... (mutate)
└──────────────────────────────────────────────────────────┘
```

1. **Subscribe** once to `cnvs://board/<id>/state.json` over MCP — server pushes an event within ~3 s of every edit.
2. **React** via REST — `POST /api/boards/<id>/texts` (and siblings) for create/update, `/move` for reposition, `DELETE` for erase.
3. **Filter self-echoes** with `--ignore-author-prefix "ai:"` so your own writes don't wake the listener.

## Why this split

| need | use | why |
|---|---|---|
| real-time awareness of human edits | **MCP subscriptions** | the only push channel; REST has no webhook |
| making edits | **REST API** | universal (any HTTP client), stateless, mirrors every MCP tool 1:1, doesn't burn the model's per-turn tool-call budget |
| client runtime can't speak MCP | **REST for everything** | fallback: read with `GET /json/<id>` (ETag-aware) + poll with `GET /wait` long-poll |

MCP-for-writes is legitimate but strictly slower per cycle (JSON-RPC envelope + session header + tool-call slot per mutation), AND it requires an MCP-capable client. REST requires nothing but outbound HTTPS.

## Part 1 — Listen via MCP (push-to-model)

### 1.1 Install the `mcp-listen` skill

This skill doesn't bundle the listener itself — installation is a one-liner from its own canonical location:

```bash
mkdir -p ~/.claude/skills/mcp-listen
cd       ~/.claude/skills/mcp-listen
curl -O https://cnvs.app/mcp-listen/SKILL.md \
     -O https://cnvs.app/mcp-listen/package.json \
     --create-dirs -o scripts/listen.mjs https://cnvs.app/mcp-listen/scripts/listen.mjs
npm install --silent
```

No global install, no