Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Excalidraw Collab

БесплатноНе проверен

Standalone backend for a self-hosted Excalidraw fork with per-board access control, providing an MCP remote endpoint that lets AI agents draw on real collaborat

GitHubEmbed

Описание

Standalone backend for a self-hosted Excalidraw fork with per-board access control, providing an MCP remote endpoint that lets AI agents draw on real collaboration boards as specific users.

README

Standalone Node + TypeScript backend for a self-hosted Excalidraw fork with per-board access control (Firebase project excalidraw-team). It provides:

  • MCP remote endpoint (ALL /mcp) that lets an AI agent draw on a real collab board as a specific user. The agent's writes respect the board's read-only policy and are attributed in shared history as Бот <name>.
  • MCP connect-token mint / list / revoke endpoints.
  • Filesystem-backed image file service that replaces Firebase Storage, with the same per-board ACL the room server enforces.

The service never modifies the frontend or the room fork; it matches their wire formats (encryption, socket protocol, Firestore scene/history doc shapes).

How it works

Socket auth = exchanged Firebase ID token

The collab (socket.io) server authenticates clients with a Firebase ID token and runs its own ACL on join-room. The Admin SDK can only mint a custom token for a uid, so the bot:

  1. admin.auth().createCustomToken(uid)
  2. exchanges it for an ID token via Identity Toolkit (accounts:signInWithCustomToken?key=${FIREBASE_WEB_API_KEY})
  3. connects with auth: { token: idToken }

The room server therefore resolves the bot as the user, so its existing read/write enforcement applies automatically: a viewer-token bot's server-broadcast frames are dropped by the room server, and this service also refuses to broadcast/persist when the token role is viewer.

Encryption

src/encryption.ts replicates the frontend (packages/excalidraw/data/encryption.ts) exactly using Node Web Crypto (globalThis.crypto.subtle): a 22-char base64url AES-128-GCM key imported via JWK { alg: "A128GCM", k, kty: "oct" }, 12-byte random IV. Verified byte-compatible by round-trip.

Scene + history persistence

src/scene.ts ports the Admin-SDK equivalent of excalidraw-app/data/firebase.ts:

  • scenes/{roomId} = { sceneVersion, ciphertext, iv } (encrypted elements).
  • shared history index scenes/{roomId}~history + per-entry payload scenes/{roomId}~history~{entryId}, matching SceneHistory entry shape and MAX_SCENE_HISTORY_ENTRIES so the frontend HistorySidebar renders bot entries (with author).

Byte fields are written as Node Buffer (the Admin SDK has no web-only Bytes class); the underlying Firestore bytesValue is identical to what the web SDK Bytes produces, so data.ciphertext.toUint8Array() on the frontend reads the same bytes.

Endpoints

Method Path Auth Purpose
POST /mcp/tokens Firebase ID token (Bearer) Mint a connect token for { boardId }. Returns { token, mcpUrl, role, configSnippet }. Caller must canRead; role = editor if canWrite else viewer.
GET /mcp/tokens?boardId= Firebase ID token List caller's tokens.
DELETE /mcp/tokens/:token Firebase ID token Revoke a token the caller owns.
ALL /mcp connect token (Bearer or ?token=) MCP Streamable HTTP endpoint; lazily attaches a CollabBot.
PUT /files/* optional Firebase ID token Store raw opaque bytes. files/rooms/{roomId}/... requires canWrite; files/shareLinks/... open.
GET /files/* optional Firebase ID token Return raw bytes. files/rooms/{roomId}/... requires canRead; files/shareLinks/... open.

The file bytes are already client-encrypted + compressed; the service stores and returns them verbatim.

MCP tools

  • describe_scene, query_elements — current elements (viewer + editor). fields projects the columns you need and limit/offset page a large scene.
  • batch_create, update_elements, delete_elements, delete_region — single-commit batch writes (editor only; a viewer token gets read-only access). Batch covers N=1, so there are no singular create/update/delete tools. batch_create also binds arrows to shapes inline (fromId/toId) and drops elements into a frame (frameId); update_elements edits a container's label ({ id, label }) and honors an explicit index, and skips ids that no longer exist (returning them in missing) instead of aborting the batch.
  • bring_to_front, send_to_back, reorder — z-order by re-indexing only, so ids/bindings/frame membership stay stable and a container's label rides along.
  • group_elements, ungroup_elements, create_frame, frame_add_children — grouping/frames. New frames sink to the bottom of the z-order.

See docs/verification-tools.md for the read/measure/render/validate tools, bound text (containerId/label), line points, and the lint rules.

Each mutating tool: applies the change (bumps version, fresh versionNonce, updated, fractional index after the last element), broadcasts a SCENE_UPDATE over server-broadcast, merge-persists the scene into Firestore in a transaction (so a concurrent human session is never clobbered), and appends a history entry attributed Бот <name>.

The bot keeps stable ownership of the ids it creates. Only for a short grace window after it last wrote an element (RESURRECTION_WINDOW_MS) does it resist an incoming deletion — that window covers the stale-tombstone race where an out-of-sync live session drops a just-created element. Once the window passes, a human deleting or editing a bot element is respected and wins immediately, so edits made after the bot is done are never rolled back. scene_diff reports ownership (byOrigin.bot, owned) and any contested ids (conflicts); batch_create surfaces the same conflicts.

Setup

cp .env.example .env   # fill in the values
npm install
npm run build
npm start              # or: npm run dev

Required env (see .env.example):

  • GOOGLE_APPLICATION_CREDENTIALS — absolute path to the service-account JSON (Admin SDK).
  • FIREBASE_WEB_API_KEY — the web apiKey (AIzaSy...) from the SDK config; required for the custom-token → ID-token exchange.
  • WS_SERVER_URL — the collab server (default http://localhost:3002).
  • FIREBASE_PROJECT_ID (default excalidraw-team), PORT, CORS_ORIGIN, DATA_DIR, PUBLIC_BASE_URL.

Mint a token + paste the MCP config

curl -X POST http://localhost:3015/mcp/tokens \
  -H "Authorization: Bearer <FIREBASE_ID_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"boardId":"<roomId>"}'

The response configSnippet is a ready-to-paste remote-MCP client config:

{
  "mcpServers": {
    "excalidraw-board": {
      "type": "http",
      "url": "http://localhost:3015/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

The agent connecting with that config draws on the board as the token's user.

Deploy notes

  • Run behind TLS and set PUBLIC_BASE_URL so mcpUrl in token responses is correct.
  • Mount DATA_DIR on persistent storage (it replaces Firebase Storage).
  • The service holds in-memory CollabBot instances keyed by connect token; it is intended to run as a single process. Horizontal scaling would need a shared bot registry / sticky routing (not implemented).
  • Firestore security rules must allow the service account to read boards, boardKeys, teams and read/write scenes* and mcpTokens.

Not yet verified live

End-to-end testing needs real credentials and a running room server, which are not available in this build environment. The following paths are structurally complete and type-checked but not exercised against live infrastructure:

  • Firebase Admin init with a real service account and verifyIdToken.
  • Custom-token → ID-token exchange against Identity Toolkit, and the room server accepting that ID token and applying read-only for viewer tokens.
  • Live socket handshake (init-roomjoin-roomfirst-in-room/new-user/room-user-change) and client-broadcast decryption / reconciliation timing. The handshake resolves on the first membership event or after a 4s fallback.
  • Actual Firestore writes to scenes/{roomId} and scenes/{roomId}~history* and the frontend HistorySidebar rendering the Бот <name> entries.
  • The frontend reading files written by PUT /files/* (path-shape and opaque byte passthrough are implemented; the exact Content-Type/CORS headers the frontend expects on GET were set permissively but not validated against a live client).
  • Fractional index ordering interop: the public [email protected] package is used; the frontend uses @excalidraw/[email protected] (a fork with identical key output), assumed byte-compatible but not co-tested.

from github.com/rus-artur4ik/excalidraw-mcp-collab

Установка Excalidraw Collab

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/rus-artur4ik/excalidraw-mcp-collab

FAQ

Excalidraw Collab MCP бесплатный?

Да, Excalidraw Collab MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Excalidraw Collab?

Нет, Excalidraw Collab работает без API-ключей и переменных окружения.

Excalidraw Collab — hosted или self-hosted?

Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.

Как установить Excalidraw Collab в Claude Desktop, Claude Code или Cursor?

Открой Excalidraw Collab на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Excalidraw Collab with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai