Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Tldraw App

FreeNot checked

Provides a standalone MCP server for tldraw diagrams, enabling clients to create, edit, and persist multiple canvases over Streamable HTTP with semantic protoco

GitHubEmbed

About

Provides a standalone MCP server for tldraw diagrams, enabling clients to create, edit, and persist multiple canvases over Streamable HTTP with semantic protocol v2.

README

This repository is an independently runnable MCP Server and MCP App distribution for tldraw. Clients connect to it over strict MCP 2026-07-28; they do not embed this repository's editor or persistence code. The server does not import OpenChamber or OpenCode. Any compatible client that supports the MCP Apps UI extension can connect to its Streamable HTTP endpoint.

The current distribution does not provide a stdio transport or legacy SSE. Run one HTTP service and configure clients with its URL.

Documentation:

The repository intentionally keeps the existing interoperability contract:

  • standalone distribution version: 1.3.0
  • bundled upstream editor version: tldraw v5.0.2
  • MCP endpoint: http://127.0.0.1:39512/mcp
  • health endpoint: http://127.0.0.1:39512/health
  • App resource: ui://openchamber/interop-tldraw-contract-v5.0.2
  • model-only contract Tool: tldraw_read_me
  • preferred model/App Tools: tldraw_create_view and tldraw_patch_shapes (semantic protocol v2)
  • compatible model/App Tools: tldraw_open_canvas and tldraw_patch_diagram
  • model-only catalog/lifecycle Tools: tldraw_list_canvases, tldraw_archive_canvas, tldraw_unarchive_canvas, tldraw_request_delete_canvas, and tldraw_delete_canvas
  • App-only Tools: tldraw_get_canvas_state, tldraw_apply_operations, tldraw_save_canvas, tldraw_export_snapshot, tldraw_list_canvas_revisions, tldraw_restore_canvas_revision, and the tldraw_asset_{begin_upload,append_chunk,commit_upload,read_chunk,abort_upload} content-addressed asset protocol
  • persistent, revision-fenced, multi-canvas state with bounded exact history

The openchamber segment in the resource URI is retained only as a stable protocol identity so existing sessions and pins continue to resolve. It is not a runtime dependency.

This public source repository is provided for inspection, evaluation, and interoperability testing under its all-rights-reserved LICENSE. Public visibility does not make it open source or grant redistribution or production-use rights. Review LICENSE.tldraw.md and THIRD_PARTY_NOTICES.md before any distribution or production use.

Quick start

Requirements: Node.js 22.18 or newer.

npm ci
npm run build
npm start

In another terminal:

npm run status
npm run probe
npm stop

npm start runs a managed background process. Logs, PID metadata, and canvas state default to ~/.local/share/tldraw-mcp-app on macOS/Linux and %LOCALAPPDATA%\\tldraw-mcp-app on Windows. It refuses to stop a process it does not own.

Use an isolated runtime and port for tests:

TLDRAW_MCP_RUNTIME_DIR=/tmp/tldraw-mcp-test \
TLDRAW_MCP_PORT=39513 \
npm start

TLDRAW_MCP_URL=http://127.0.0.1:39513/mcp npm run probe

TLDRAW_MCP_RUNTIME_DIR=/tmp/tldraw-mcp-test \
TLDRAW_MCP_PORT=39513 \
npm stop

Client connection

The server is transport-level independent of OpenChamber. A compatible client only needs the MCP URL and MCP Apps support. An OpenCode-shaped example is in examples/opencode.json:

{
  "mcp": {
    "interop-tldraw-2026": {
      "type": "remote",
      "url": "http://127.0.0.1:39512/mcp",
      "oauth": false,
      "timeout": 30000,
      "enabled": true
    }
  }
}

Existing OpenChamber/OpenCode installations should keep the interop-tldraw-2026 connection key shown above. Changing only the service URL is safe; renaming the key can break restoration of historical MCP App parts.

Clients without MCP Apps negotiation may still use the model-facing Tools. They receive the same validated structuredContent canvas state plus a text fallback instead of the interactive ui:// surface. App-only Tools are marked with _meta.ui.visibility: ["app"] and must never be exposed to the model tool picker.

The package is named @zunbaran/tldraw-mcp-app. It remains marked private: true to prevent accidental npm publication; that flag is independent of this GitHub repository's public visibility. The process, data directory, Docker image, client configuration, and release artifact are standalone.

Transport and capability contract

Capability Support
MCP protocol strict 2026-07-28 discovery/negotiation
Transport Streamable HTTP at /mcp
MCP Apps io.modelcontextprotocol/ui, offline ui:// resource
stdio not implemented
legacy SSE / legacy initialize rejected
OAuth not implemented by this private loopback service

The protocol and extension versions are independent. "MCP 2026-07-28" in this repository refers to the Core protocol and Streamable HTTP wire contract. "MCP Apps" refers to the separately versioned io.modelcontextprotocol/ui stable specification dated 2026-01-26. The bundled View pins @modelcontextprotocol/ext-apps 1.7.5; capabilities that exist only in the Apps draft are described as draft SDK capabilities, not as part of Core 2026-07-28.

In particular, ui/download-file is an optional, capability-negotiated API implemented by the pinned ext-apps SDK. It is not guaranteed by the MCP Apps stable 2026-01-26 contract. Hosts that do not advertise it still get a fully usable canvas, but SVG/PNG saving is reported as unavailable rather than being silently simulated. The same rule applies to the SDK's model-context modality capability fields: the App filters to advertised modalities, while remaining compatible with a stable Host that exposes only the base updateModelContext operation.

/health proves process and artifact identity only. It does not prove that a client negotiated Apps, filtered App-only Tools, loaded the sandboxed UI, or connected AppBridge.

Model diagram workflow

tldraw_read_me is a read-only discovery Tool. With a canvasId, it returns the current canvas and revision when present, plus the exact patch limits and revision rules.

For new work, use tldraw_create_view with semantic elements protocol v2. It supports geo shapes, text, notes, arrows, lines, frames, groups, hierarchy, bindings, style and ordering with stable IDs. Use tldraw_patch_shapes for later create, update, delete, bind, and reorder transactions. Every patch carries exact expectedRevision and unique transactionId fences; the whole final graph commits once or not at all. See the protocol guide and the checked examples:

tldraw_open_canvas keeps its existing behavior for compatible node/edge clients: it opens a known canvas, creates one only when createIfMissing is true, and can seed a new canvas with a bounded initialDiagram.

Use tldraw_patch_diagram for subsequent model-authored edits. Every call must include the canvas's exact expectedRevision. The nodes and edges objects each accept upsert, update, and remove arrays. Upserts are complete items; updates contain an existing id plus only the changed fields. A patch is applied transactionally, with a maximum of 100 final nodes, 200 final edges, 100 node operations, and 200 edge operations. Edge endpoints are validated against the final graph, so a patch can remove a node and its edges, or add nodes and their connecting edges, in one revision.

Failed validation and stale revisions change nothing. Successful note and diagram operations advance only their target canvas and retain any saved real tldraw editor snapshot.

Semantic and legacy operations share the same canvas revision. The server maintains a compatibility projection instead of creating two independent canvases.

Canvas catalog/lifecycle operations are scoped to the server-configured workspace. Archive is revision fenced and recoverable. Deletion requires an already archived canvas plus a separate request that returns the confirmation token consumed by tldraw_delete_canvas; the record is moved to private recoverable trash rather than erased. The configured default canvas is protected from archive and delete.

Canvas identity and persistence

tldraw_open_canvas returns a stable canvasId and revision. Every mutation must send the same identity plus expectedRevision. Stale or cross-canvas writes are rejected. Save persists the same canvas; it does not invent a new canvas ID. tldraw_save_canvas also requires an idempotencyKey, so retrying a lost response with the same input returns the original committed result rather than creating another revision. Diagram and note mutations never erase a saved editor snapshot. The state file is atomically replaced and written with owner-only permissions.

Inline mode uses lightweight SVG/DOM and never mounts the tldraw editor. ontoolinputpartial incrementally draws complete, valid items from streaming Tool arguments; incomplete nodes and dangling edges are withheld until they are safe to render. Edit requests fullscreen, the only mode that mounts the verified official tldraw editor. Model-authored changes use the revision-bound diagram patch Tool; fullscreen Save submits canonical diagram changes, App-only note operations, and the lossless editor snapshot together in one atomic tldraw_save_canvas request without changing canvasId.

The editor supports up to 16 lossless pages. Exactly one page carries the openchamberSemanticPrimaryPage marker: semantic v2 and legacy model Tools read and patch only that primary page, while Save persists every page, page record, shape, binding, and document setting. Existing unmarked snapshots are migrated by preferring the page that already contains canonical semantic shapes, then the snapshot's active page. Inline mode previews only the primary page, so pages are never visually overlaid. Fullscreen restores the normal tldraw page menu; SVG/PNG exports include only the currently selected page and include its page-name slug in the filename.

Save retry and in-flight edit detection fence the complete snapshot document, not only primary-page shapes. Session camera, selection, and current-page state are excluded from that document fingerprint, and a page switch made while Save is in flight is restored after the accepted snapshot loads. A snapshotless new canvas resets the prior document, page records, assets, and view state before canonical reconciliation. Server-originated resets and reconciliation bypass shape locks and do not enter the user's Undo history.

Each successful revision archives the superseded full canvas state. The Server retains at most 32 historical revisions per canvas within a 4 MiB aggregate history budget. tldraw_get_canvas_state can read an exact retained revision, tldraw_list_canvas_revisions identifies retained and current entries, and tldraw_restore_canvas_revision copies a retained revision into a new, monotonically increasing current revision. Restore requires the current expectedRevision and an idempotencyKey; it never rewinds revision numbers or merges concurrent work.

A historical Tool result or pin stays on its exact retained revision. It is labelled read-only even when a newer current revision exists; Refresh latest is the explicit action that leaves historical review, while Restore creates the new current revision described above. Separate App instances refresh the same canvasId on focus, return to visibility, HostContext changes, and a fixed five-second poll, so chat and pin/workbench copies do not remain silently stale.

If newer Server state arrives while the editor is dirty, the App preserves the local draft and queues the authoritative state. There is no automatic merge. Save is available only for a conflict-free dirty draft; after a concurrent update it is disabled while Review server shows the semantic delta and Discard explicitly loads the authoritative revision. Fullscreen exit is blocked while edits remain open. SVG and PNG use tldraw's real renderers, record an App-only export receipt, and then call the official ui/download-file flow. A compatible Host must confirm and perform the download; a receipt alone is not a downloaded file.

App-only asset protocol

The Server exposes a private, App-only chunk protocol for PNG, JPEG, and WebP bytes. tldraw_asset_begin_upload binds a declared byte length, MIME type, and SHA-256 digest to one existing canvasId and returns an upload id plus a short-lived bearer token. Only that begin result contains the token, and its human-readable content never repeats it. Append, commit, read, abort, and all errors return allowlisted fields without filesystem paths.

tldraw_asset_append_chunk accepts canonical base64 and an exact byte offset; decoded chunks are limited to the asset store's advertised chunkSize (currently 192 KiB). Commit publishes content only after byte length, digest, and detected media type all match. Reads return bounded base64 chunks from a content-addressed mcp-asset:<sha256> object. Asset data lives under the same configured store root and scope/workspace namespace as canvases and survives a Server restart.

tldraw_save_canvas accepts an optional, strict assetManifest containing sha256, mimeType, and byteLength. The Server extracts only asset records from snapshot.document.store, permits only image records whose props.src is exactly mcp-asset:<sha256>, and requires the manifest digest set to match those references exactly without duplicates. Every manifest item must already exist in the workspace asset store with identical metadata. Omission is compatible with older clients only when the snapshot has no asset records.

The validated allowlist is stored in current canvas state and every retained revision, so historical exact reads and restores keep the asset binding that was valid at that revision. The bundled App accepts PNG, JPEG, and WebP images from its file picker, drag-and-drop, and clipboard paths. Each image is limited to 12 MiB, uploaded through bounded 192 KiB App-only chunks, and replaced with its verified mcp-asset:<sha256> URI before it may be saved. URL images, SVG, video, embeds, and other open media paths remain disabled. The Server never deletes assets during Save. Instead, after the canvas store is completely restored at process startup, revision-aware garbage collection scans every active and archived canvas, its current state, and all retained revisions before computing one exact keep-set. Unreferenced committed blobs remain protected for at least 24 hours, so a newly uploaded image that has not yet been saved cannot be removed by the next restart. The startup pass can delete, audit in dry-run mode, or be disabled; /health reports only aggregate counts and byte totals, never asset paths or upload authority.

Compatible host requirements

Server health alone does not mean a model or App can use this integration. A compatible host must complete all of these independent steps:

  1. negotiate strict MCP 2026-07-28 and carry the Apps extension capability on the requests that resolve the Tool result;
  2. expose only model/model + app Tools to the model while keeping App-only Tools behind a session/message/server/resource-bound AppBridge;
  3. load the verified offline ui:// resource in an isolated document and provide HostContext updates for inline, fullscreen, and workbench sizes;
  4. implement the pinned Apps SDK's optional ui/download-file capability if SVG/PNG downloads are advertised, including a user confirmation before writing; this API is capability-gated and is not required by Apps stable 2026-01-26;
  5. preserve the exact retained revision represented by historical Tool results and pins, expose an explicit latest-state refresh, and refresh clean live instances from the same canvasId on lifecycle signals;
  6. advertise only the updateModelContext modalities it supports. The App emits only advertised text and/or structuredContent, never a snapshot or an unadvertised modality. Its bounded semantic summary preserves canvasId and revision and reports added, removed, moved, resized, and renamed items plus added, removed, repointed, and renamed edges.

The verified App HTML is multi-megabyte. Raising the MCP resource limit was necessary but not sufficient: directly encoding the 4 MB+ App into a data: URL also failed in the reference Host. Do not infer a universal browser URL threshold; keep large App markup out of bootstrap URLs.

OpenChamber's reference path is:

Host-owned raw App HTML with injected resource CSP
  → small opaque data: Loader
  → source/nonce/identity-authenticated chunk transfer
  → sandboxed about:srcdoc App

The Host sends the raw HTML through nonce- and identity-bound messages. The Loader validates the complete transfer, drops the raw chunks, and the Host commits the final document through iframe.srcdoc. The final App stays in an opaque sandbox origin and remains cross-origin with the Host.

tldraw's StyleEmbedder creates an internal about:blank frame while exporting so it can measure isolated browser-default styles. Because the final App iframe uses sandbox="allow-scripts" without allow-same-origin, its active sandbox origin flag also applies when it creates a nested browsing context. The helper frame receives a new opaque origin, so frame.contentDocument is unavailable; adding about: to CSP does not make the two opaque origins same-origin.

The reviewed pinned-source patch handles that expected condition by treating isolated default styles as unavailable and using NO_STYLES for default-style elision. It may inline more filtered computed CSS, but it keeps tldraw's normal style, font, media, pseudo-element, and SVG-to-PNG pipeline intact. Do not add allow-same-origin: that would weaken the Host/App security boundary rather than repair the exporter. Loader navigation, nonce, source, session, message, server, resource, and Tool checks remain fail-closed.

The MCP resource uses verified data: URLs for embedded assets and currently also records about: as a non-network frame source. The latter is neither an origin grant nor a requirement of the exporter fallback, and a Host may reject or normalize unsupported scheme sources. connectDomains stays empty. Hosts must not interpret these declarations as permission to reach any network origin.

The full editor keeps bundled icon/font data and the pinned English, Simplified Chinese, and Traditional Chinese translations on verified data: URLs. Test the actual disconnected editor—source-string or artifact-hash checks cannot prove that toolbar and minimap icons render or that HostContext locale changes select the intended language.

Three exact, hash-pinned source patches are part of this distribution: the opaque-sandbox style exporter fallback, offline translation decoding for verified JSON data: URLs, and export handling that preserves already-resolved media data: URLs instead of trying to fetch them under connect-src 'none'. Their patch bytes plus pristine and patched target hashes are recorded in config/upstream-lock.json; an unknown preimage fails the build closed.

Export acceptance must also run in a real browser with the exact sandbox="allow-scripts" topology, production CSP, and a canvas containing a rich-text label that exercises foreignObject style embedding. Trigger both SVG and PNG, assert that frame must have a document is absent and the bridge remains usable, and inspect the decoded SVG/PNG rather than only matching the patch source. Automated renderer/receipt/download-policy checks still do not prove a file was saved; manual acceptance must choose a path and inspect both resulting files.

Environment contract

Primary variables:

Variable Default Purpose
TLDRAW_MCP_HOST 127.0.0.1 Listen address
TLDRAW_MCP_PORT 39512 Listen port
TLDRAW_MCP_RUNTIME_DIR platform data directory Managed process files
TLDRAW_MCP_STORE_ROOT next to state path Namespaced catalog, canvas records, history, recoverable trash, and content-addressed assets
TLDRAW_MCP_SCOPE_ID local-user Stable deployment/user namespace identity
TLDRAW_MCP_WORKSPACE_ID default-workspace Stable workspace namespace identity
TLDRAW_MCP_INSTALLATION_ID resolved store root Stable installation identity seed used to partition App caches
TLDRAW_MCP_STATE_PATH runtime state file Legacy monolithic state import source and rollback evidence
TLDRAW_MCP_CANVAS_ID interop-acceptance Initial/default canvas
TLDRAW_MCP_ALLOWED_HOSTS loopback hostnames HTTP Host allowlist; required for non-loopback bind
TLDRAW_MCP_ALLOWED_ORIGINS loopback hostnames HTTP Origin-host allowlist; required for non-loopback bind
TLDRAW_MCP_APP_HTML_PATH sibling dist/app.html Verified App bundle
TLDRAW_MCP_PROVENANCE_PATH sibling provenance App integrity metadata
TLDRAW_MCP_ASSET_GC_MODE delete Startup-only revision-aware asset maintenance: delete, dry-run, or off
TLDRAW_MCP_URL loopback MCP URL Probe target

For migration, corresponding OPENCHAMBER_TLDRAW_MCP_* variables are accepted as lower-priority aliases. New deployments should use TLDRAW_MCP_*.

Scope and workspace are server configuration, not model Tool arguments. Keep them stable across restarts and upgrades: changing either intentionally selects a different logical canvas catalog. Literal namespace values are validated and hashed before storage paths are derived.

Builds and verification

The checked-in dist/app.html is the verified offline tldraw v5.0.2 App. It embeds its icons, fonts, translations, CSS, and JavaScript. Normal server development does not rebuild it:

npm test
npm run build:server
npm run verify:dist

For the complete standalone gate, including an isolated managed service and real protocol probe, run:

npm run accept

The acceptance runner executes tests, build, verification, start, probe, and stop serially to keep memory bounded. It prints the path to a private JSON report. Real sandbox/editor/download acceptance remains a separate Host-level gate described in docs/ACCEPTANCE.md.

npm run build:app reuses an already verified upstream checkout in .cache. npm run bootstrap:app explicitly downloads and prepares the pinned upstream source. Bootstrap is intentionally heavyweight and is never run implicitly by npm ci, tests, the normal server build, or Docker.

npm run probe checks the strict protocol and Apps negotiation, semantic v2 create/patch/idempotent replay, catalog archive/unarchive/double-confirmation recoverable delete, App-only history/restore visibility, the required Save idempotencyKey, exact historical reads, revision listing, snapshot retention, and the verified App resource/hash. The protocol and App source-contract tests additionally cover multi-instance stale-revision recovery, bounded history and restore semantics, streaming partial rendering, the lightweight inline/fullscreen boundary, conflict choices, model-context modalities/deltas, and export ordering.

See docs/DEVELOPMENT.md for repository maintenance and config/upstream-lock.json for the pinned source and artifact hashes.

Docker

docker compose up --build -d
docker compose ps
TLDRAW_MCP_URL=http://127.0.0.1:39512/mcp npm run probe
docker compose down

The Compose service binds only to loopback, uses a named volume for canvas state, and does not rebuild the tldraw App. The process binds 0.0.0.0 inside the container, so Compose explicitly supplies loopback Host and Origin allowlists. If a reviewed reverse proxy is added, set both allowlists to the exact proxy-facing hostnames (hostname only, without scheme, port, path, or wildcard) before starting.

This image and Compose file are for private development/interoperability only. They do not confer a tldraw production license and must not be published as a public production service.

from github.com/ZunbaRan/tldraw-mcp-app

Installing Tldraw App

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/ZunbaRan/tldraw-mcp-app

FAQ

Is Tldraw App MCP free?

Yes, Tldraw App MCP is free — one-click install via Unyly at no cost.

Does Tldraw App need an API key?

No, Tldraw App runs without API keys or environment variables.

Is Tldraw App hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Tldraw App in Claude Desktop, Claude Code or Cursor?

Open Tldraw App on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Tldraw App with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All development MCPs