You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(mcp-app): improve host detection, dev logging, and mobile handling (tldraw#8199)
In order to fix ChatGPT copy-paste issues and improve the MCP app's
host-detection reliability, this PR refactors hostname resolution,
replaces the debug module with an integrated dev log panel, and adds
mobile platform handling.
Reimplemented from
[`max/fix-mcp-app-chatgpt-copy-paste`](https://github.com/tldraw/tldraw/tree/max/fix-mcp-app-chatgpt-copy-paste)
with a clean commit history.
### Change type
- [x] `improvement`
### Test plan
1. Run `yarn dev` in `apps/mcp-app` and connect from Cursor/Claude
Desktop — verify the canvas loads and tools work
2. Connect from ChatGPT via `yarn dev:tunnel` — verify host detection
resolves to `chatgpt`
3. Verify the dev log panel appears when `MCP_IS_DEV=true` and can be
toggled via the toolbar button
4. Verify fullscreen toggle works in desktop clients and is disabled on
mobile platforms
- [ ] Unit tests
- [ ] End to end tests
### Release notes
- Improve MCP app host detection for ChatGPT clients
- Add integrated dev log panel visible in dev mode
- Resolve host name client-side for more reliable detection
- Disable fullscreen on mobile platforms
- Clean up verbose server-side logging
- Update README with separate Claude Desktop local/remote setup
instructions
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Medium risk because it changes how host identity is resolved, what
bootstrap data is injected into the widget, and how
fullscreen/display-mode is handled (including new mobile restrictions),
which can impact client compatibility (ChatGPT/Claude/Cursor/VS Code).
No auth or data-storage model changes beyond logging/bootstrapping.
>
> **Overview**
> Improves MCP host detection by splitting hostname resolution into
server-side (`resolveMcpAppHostNameFromServerInfo`) vs client-side
(`resolveMcpAppHostNameFromClientInfo`) parsing and broadening ChatGPT
detection; the widget now resolves host name from `app.getHostVersion()`
instead of relying on server-injected `hostName`.
>
> Replaces the old ad-hoc widget `debug.ts` logging with an integrated
**dev log panel** gated by a new `isDev` bootstrap flag (injected by the
canvas resource) and a toolbar toggle.
>
> Adjusts UI behavior for mobile hosts by disabling fullscreen
capability and forcing an exit from fullscreen if the host platform
becomes `mobile`, and removes verbose server-side `console.error`
checkpoint/debug logging. Adds an MIT `LICENSE.md`, updates the `README`
setup instructions, and enables `preview_urls` in `wrangler.toml`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
450b792. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copy file name to clipboardExpand all lines: apps/mcp-app/README.md
+32-36Lines changed: 32 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
-
# MCP app
1
+
# tldraw MCP app
2
2
3
-
This is the tldraw MCP app. It exposes an interactive tldraw canvas to AI agents via the [Model Context Protocol](https://modelcontextprotocol.io/), so agents in Cursor, Claude Desktop, ChatGPT, and VS Code can draw diagrams and shapes on a canvas during a conversation.
3
+
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.
4
4
5
5
## Architecture
6
6
@@ -19,31 +19,31 @@ Both entry points share tool registration logic in `src/register-tools.ts`.
19
19
20
20
### Widget
21
21
22
-
The widget is a React app (`src/widget/mcp-app.tsx`) that renders a full tldraw canvas inside the MCP host's iframe. Vite bundles it into a single HTML file (`dist/mcp-app.html`) using `vite-plugin-singlefile`, which the server injects bootstrap data into before serving.
22
+
The widget is a React app (`src/widget/mcp-app.tsx`) that renders a full tldraw canvas inside the MCP host's iframe.
23
23
24
-
The widget handles streaming previews (shapes appear as the model streams tool arguments), checkpoint persistence to `localStorage`, and syncing state back to the server.
24
+
The widget handles streaming previews (shapes appear as the model streams tool arguments), and syncing state back to the server.
|`yarn dev`| Build widget + start local Cloudflare worker (HTTP MCP on `localhost:8787`) |
36
+
|`yarn dev:stdio`|Build widget + Start a local stdio MCP server |
37
+
|`yarn dev:tunnel`|Build widget + Start a Cloudflare tunnel + local worker with `WORKER_ORIGIN` set to the tunnel URL |
38
+
|`yarn deploy`| Build widget + deploy the Cloudflare worker to production |
39
39
40
40
`yarn dev:tunnel` requires the `cloudflared` CLI to be installed on your machine.
41
41
42
42
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.
43
43
44
44
### Cursor setup
45
45
46
-
Add up to three servers in `~/.cursor/mcp.json`:
46
+
Add these three servers in `~/.cursor/mcp.json`:
47
47
48
48
```json
49
49
{
@@ -74,17 +74,13 @@ Add up to three servers in `~/.cursor/mcp.json`:
74
74
75
75
`--cwd` ensures Cursor launches in the app folder. `-s` stops yarn from writing non-JSON noise to stdout, which breaks the stdio transport.
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`.
107
+
108
+
1. Open Claude Desktop
109
+
2. In the sidebar, go to **Customize**
110
+
3. Open **Connectors**
111
+
4. Click the button to add a connector, then choose **Add custom connector**
112
+
5. Give it a name such as `tldraw`
113
+
6. Paste `https://tldraw-mcp-app.tldraw.workers.dev/mcp` as the server URL
114
+
115
+
The **Add custom connector** option is not available on the free plan, so you may need Max or another paid plan.
116
+
117
+
If you need Notion access in Claude Desktop, use the Notion MCP connector for that separately.
118
+
108
119
### ChatGPT local dev
109
120
110
-
ChatGPT requires an HTTPS origin, so you need a Cloudflare tunnel. You must be a workspace admin.
121
+
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.
111
122
112
123
1. Run `yarn dev:tunnel` in `apps/mcp-app`
113
124
2. It prints a `https://...trycloudflare.com` tunnel URL
114
125
3. In ChatGPT web (not the desktop app), go to **Apps** and add your app using that tunnel URL
115
-
4. You can then test in both ChatGPT web and the desktop app
126
+
4. You can then test in both ChatGPT web and the desktop or mobile apps
116
127
117
128
`dev:tunnel` automatically wires `WORKER_ORIGIN` to the tunnel URL and sets `MCP_IS_DEV=true` for the local worker.
118
129
119
-
### Auth and environment flags
120
-
121
-
-`MCP_AUTH_TOKEN` controls bearer auth for the HTTP worker. If it is unset, the worker accepts unauthenticated local requests.
122
-
-`MCP_IS_DEV` controls local-only widget behavior, such as suppressing `ui.domain` for local HTTP/tunnel connectors.
123
-
124
-
These flags are intentionally separate so auth configuration does not change widget-domain behavior.
125
-
126
130
### Iteration loop
127
131
128
132
1. Make code changes in `apps/mcp-app`
129
133
2. Run the relevant script (`dev`, `dev:stdio`, or `dev:tunnel`)
130
134
3. Disconnect and reconnect the MCP server in your client (or reload the page/app)
131
-
4. When making widget changes, make sure to rebuild — `yarn dev` does this automatically
135
+
4. When making widget changes, make sure to rebuild, either by running `yarn build` or rerunning any of the dev scripts.
132
136
133
137
Reconnecting the server after changes is the most reliable way to pick up new code, especially when the widget HTML changes.
134
138
135
-
## License
136
-
137
-
The code in this folder is Copyright (c) 2024-present tldraw Inc. The tldraw SDK is provided under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md).
138
-
139
-
## Trademarks
140
-
141
-
Copyright (c) 2024-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our [trademark guidelines](https://github.com/tldraw/tldraw/blob/main/TRADEMARKS.md) for info on acceptable usage.
142
-
143
139
## Contact
144
140
145
141
Find us on Twitter/X at [@tldraw](https://twitter.com/tldraw).
'Use read_me for shape format examples. For create_shapes, update_shapes, and delete_shapes, send JSON array strings (build the array first, then JSON.stringify). Use create_shapes before update_shapes or delete_shapes when the canvas is empty.'
40
+
'Use diagram_drawing_read_me for shape format examples. For create_shapes, update_shapes, and delete_shapes, send JSON array strings (build the array first, then JSON.stringify). Use create_shapes before update_shapes or delete_shapes when the canvas is empty.'
0 commit comments