Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 23 additions & 39 deletions apps/mcp-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,43 @@ The app has two parts: a **server** and a **widget**.

### Server

The server registers MCP tools (`create_shapes`, `update_shapes`, `delete_shapes`, `diagram_drawing_read_me`) and serves the widget HTML as an MCP App resource.
The server runs in Cloudflare Workers via `src/worker.ts`, using a Durable Object (`TldrawMCP`) backed by SQLite for persistent checkpoint storage.

There are two entry points:
It exposes:

- `main.ts` — Node.js stdio transport, for local clients like Claude Desktop and Cursor
- `src/worker.ts` — Cloudflare Workers with a Durable Object (`TldrawMCP`) backed by SQLite for persistent checkpoint storage

Both entry points share tool registration logic in `src/register-tools.ts`.
- `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.

The widget handles streaming previews (shapes appear as the model streams tool arguments), and syncing state back to the server.
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

Run all commands from `apps/mcp-app`.
### 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:stdio` | Build widget + Start a local stdio MCP server |
| `yarn dev:tunnel` | Build widget + Start a Cloudflare tunnel + local worker with `WORKER_ORIGIN` set to the tunnel URL |
| `yarn dev:tunnel` | Build widget + start a 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.
Expand All @@ -43,7 +53,7 @@ The worker defaults to production-safe behavior in `wrangler.toml`, including se

### Cursor setup

Add these three servers in `~/.cursor/mcp.json`:
Add these two servers in `~/.cursor/mcp.json`:

```json
{
Expand All @@ -55,47 +65,21 @@ Add these three servers in `~/.cursor/mcp.json`:
"tldraw-local": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8787/mcp"]
},
"tldraw-local-stdio": {
"command": "yarn",
"args": [
"--cwd",
"<path-to-tldraw-repo>/tldraw/apps/mcp-app",
"run",
"-s",
"tsx",
"main.ts",
"--stdio"
]
}
}
}
```

`--cwd` ensures Cursor launches in the app folder. `-s` stops yarn from writing non-JSON noise to stdout, which breaks the stdio transport.

### Claude Desktop local setup

For local Claude Desktop development, use `claude_desktop_config.json` for the local HTTP and stdio servers:
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"]
},
"tldraw-local-stdio": {
"command": "yarn",
"args": [
"--cwd",
"<path-to-tldraw-repo>/tldraw/apps/mcp-app",
"run",
"-s",
"tsx",
"main.ts",
"--stdio"
]
}
}
}
Expand Down Expand Up @@ -130,7 +114,7 @@ ChatGPT requires an HTTPS origin, so you need a Cloudflare tunnel. You must be a
### Iteration loop

1. Make code changes in `apps/mcp-app`
2. Run the relevant script (`dev`, `dev:stdio`, or `dev:tunnel`)
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.

Expand Down
16 changes: 0 additions & 16 deletions apps/mcp-app/main.ts

This file was deleted.

7 changes: 4 additions & 3 deletions apps/mcp-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"private": true,
"type": "module",
"scripts": {
"build:widget": "node ../../packages/tldraw/scripts/copy-css-files.mjs && vite build && mv dist/index.html dist/mcp-app.html",
"extract-api": "tsx scripts/extract-editor-api.ts",
"generate-agent-sdk-docs": "yarn run -T tsgo --build ../../packages/editor/tsconfig.json ../../packages/store/tsconfig.json ../../packages/tlschema/tsconfig.json && yarn extract-api",
"build:widget": "yarn generate-agent-sdk-docs && node ../../packages/tldraw/scripts/copy-css-files.mjs && vite build && mv dist/index.html dist/mcp-app.html",
"build": "yarn build:widget",
"dev": "yarn dev:http",
"dev:stdio": "yarn build && tsx main.ts --stdio",
"dev:http": "yarn build && wrangler dev --var \"MCP_IS_DEV:true\"",
"dev:http": "yarn build && wrangler dev --var \"MCP_IS_DEV:true\" --var \"WORKER_ORIGIN:http://localhost:8787\"",
"dev:tunnel": "yarn build && bash dev-tunnel.sh",
"deploy": "yarn build && wrangler deploy",
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts",
Expand Down
Loading
Loading