snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
141
apps/mcp-app/README.md
Normal file
141
apps/mcp-app/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# tldraw MCP app
|
||||
|
||||
This is the tldraw MCP app. It exposes an interactive tldraw canvas to AI agents via the [Model Context Protocol app specification](https://github.com/modelcontextprotocol/ext-apps/), so you can work in tldraw with agents in any MCP client that supports the MCP app spec.
|
||||
|
||||
## Architecture
|
||||
|
||||
The app has two parts: a **server** and a **widget**.
|
||||
|
||||
### Server
|
||||
|
||||
The server runs in Cloudflare Workers via `src/worker.ts`, using a Durable Object (`TldrawMCP`) backed by SQLite for persistent checkpoint storage.
|
||||
|
||||
It exposes:
|
||||
|
||||
- `search` — query the extracted Editor API spec in a sandboxed dynamic worker
|
||||
- `exec` — execute JavaScript against the live editor in the widget via a pending-request callback bridge
|
||||
- `_exec_callback` — app-only tool the widget calls to resolve a pending `exec` request
|
||||
- `save_checkpoint` / `read_checkpoint` — app-only tools used by the widget for checkpoint persistence
|
||||
|
||||
### Widget
|
||||
|
||||
The widget is a React app (`src/widget/mcp-app.tsx`) that renders a full tldraw canvas inside the MCP host's iframe.
|
||||
|
||||
When the AI calls `exec`, the server creates a pending request and the widget picks it up, runs the code through a focused editor proxy (`src/widget/focused/`) that translates between an AI-friendly shape format (simple string IDs, flat `_type` shapes) and tldraw's internal `TLShape`/`TLShapeId` types, then calls `_exec_callback` to resolve the pending request with the result. Canvas state is checkpointed to the Durable Object's SQLite database and to the browser's local storage.
|
||||
|
||||
## Developing
|
||||
|
||||
### Prerequisites
|
||||
|
||||
The widget build depends on generated files (`editor-api.json`, `method-map.json`) that are extracted from the editor's TypeScript declarations. Before you can develop or build the mcp-app, you need to build the core packages first:
|
||||
|
||||
```bash
|
||||
# from the repo root
|
||||
yarn build
|
||||
```
|
||||
|
||||
This produces the `.tsbuild/` output that `yarn extract-api` reads from. The `build` and `dev` scripts run `extract-api` automatically, so you don't need to call it separately.
|
||||
|
||||
### Package scripts
|
||||
|
||||
Run all commands from `apps/mcp-app`.
|
||||
|
||||
| Command | What it does |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `yarn build` | Build the widget HTML |
|
||||
| `yarn dev` | Build widget + start local Cloudflare worker (HTTP MCP on `localhost:8787`) |
|
||||
| `yarn dev:tunnel` | Build widget + start a stable named Cloudflare tunnel + local worker with `WORKER_ORIGIN` set to the tunnel URL |
|
||||
| `yarn deploy` | Build widget + deploy the Cloudflare worker to production |
|
||||
|
||||
`yarn dev:tunnel` requires the `cloudflared` CLI to be installed on your machine and a one-time `cloudflared tunnel login`. It serves a stable per-user hostname (`<user>-mcp-app-dev.tldraw.xyz`) via a named tunnel, so the URL you register in hosted MCP clients stays the same across runs instead of changing every time. The user slug defaults to `whoami`; override it (or the zone/port) with `MCP_TUNNEL_USER`, `MCP_TUNNEL_ZONE`, and `PORT`. The zone must be a Cloudflare-hosted zone (`tldraw.xyz` is; `tldraw.dev`/`tldraw.com` are on Vercel and won't work).
|
||||
|
||||
The worker defaults to production-safe behavior in `wrangler.toml`, including setting `MCP_IS_DEV="false"`. Local HTTP dev scripts override that with `MCP_IS_DEV=true` so local Claude/ChatGPT connectors suppress `ui.domain` while production deployments keep it enabled.
|
||||
|
||||
### Cursor setup
|
||||
|
||||
Add these two servers in `~/.cursor/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"tldraw": {
|
||||
"transport": "http",
|
||||
"url": "https://tldraw-mcp-app.tldraw.workers.dev/mcp"
|
||||
},
|
||||
"tldraw-local": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "http://127.0.0.1:8787/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Claude Desktop local setup
|
||||
|
||||
For local Claude Desktop development, use `claude_desktop_config.json` with the local HTTP server:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"tldraw-local": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "http://127.0.0.1:8787/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Claude Desktop remote setup
|
||||
|
||||
If you'd like to try the remote MCP server in Claude Desktop, use the in-app connector flow rather than adding the production URL to `claude_desktop_config.json`.
|
||||
|
||||
1. Open Claude Desktop
|
||||
2. In the sidebar, go to **Customize**
|
||||
3. Open **Connectors**
|
||||
4. Click the button to add a connector, then choose **Add custom connector**
|
||||
5. Give it a name such as `tldraw`
|
||||
6. Paste `https://tldraw-mcp-app.tldraw.workers.dev/mcp` as the server URL
|
||||
|
||||
The **Add custom connector** option is not available on the free plan, so you may need Max or another paid plan.
|
||||
|
||||
If you need Notion access in Claude Desktop, use the Notion MCP connector for that separately.
|
||||
|
||||
### ChatGPT local dev
|
||||
|
||||
ChatGPT requires an HTTPS origin, so you need a Cloudflare tunnel. You must be an admin of your OpenAI org/workspace to do local dev.
|
||||
|
||||
First-time setup (once per machine):
|
||||
|
||||
1. Run `cloudflared tunnel login` and choose the `tldraw.xyz` zone
|
||||
|
||||
Then, each session:
|
||||
|
||||
1. Run `yarn dev:tunnel` in `apps/mcp-app`
|
||||
2. It serves your stable hostname, e.g. `https://<user>-mcp-app-dev.tldraw.xyz` (the first run also creates the named tunnel and its DNS route)
|
||||
3. In ChatGPT web (not the desktop app), go to **Apps** and add your app using that URL
|
||||
4. You can then test in both ChatGPT web and the desktop or mobile apps
|
||||
|
||||
Because the hostname is stable per user, you only add the app in ChatGPT once — subsequent `yarn dev:tunnel` runs reuse the same URL.
|
||||
|
||||
`dev:tunnel` automatically wires `WORKER_ORIGIN` to the stable tunnel URL and sets `MCP_IS_DEV=true` for the local worker.
|
||||
|
||||
### Iteration loop
|
||||
|
||||
1. Make code changes in `apps/mcp-app`
|
||||
2. Run the relevant script (`dev` or `dev:tunnel`)
|
||||
3. Disconnect and reconnect the MCP server in your client (or reload the page/app)
|
||||
4. When making widget changes, make sure to rebuild, either by running `yarn build` or rerunning any of the dev scripts.
|
||||
|
||||
Reconnecting the server after changes is the most reliable way to pick up new code, especially when the widget HTML changes.
|
||||
|
||||
## Session storage lifecycle
|
||||
|
||||
Each MCP session is a `TldrawMCP` Durable Object. It keeps up to `MAX_CHECKPOINTS` (50) canvas snapshots and destroys itself after `IDLE_TTL_MS` (7 days) without a checkpoint save — a schedule armed in `init()` and re-armed on every check.
|
||||
|
||||
## Contact
|
||||
|
||||
Find us on Twitter/X at [@tldraw](https://twitter.com/tldraw).
|
||||
|
||||
## Community
|
||||
|
||||
Have questions, comments or feedback? [Join our discord](https://discord.tldraw.com/?utm_source=github&utm_medium=readme&utm_campaign=sociallink). For the latest news and release notes, visit [tldraw.dev](https://tldraw.dev).
|
||||
Reference in New Issue
Block a user