#8536·tldraw

Improve first-run tldraw MCP agent success with docs, MCP hints, and optional plugin skill

Author: jameswilsonCreated Apr 13, 2026Updated Sep 28, 2026
Labelsdocs

Summary

After Cursor Marketplace → Try in Chat (“Show me how the tldraw plugin works”), the model summarizes a practical MCP flow (search → exec → reuse canvasId). A natural follow-up—“turn this into a diagram in tldraw”—often fails first because agents assume the full Editor API (toRichText, createShapeId, type: 'geo', etc.) even though the exec runtime is narrower. Retries using the documented simplified createShape({ _type, shapeId, text }) path then hit separate footguns: JSON newline escaping (literal \n in labels) and shape ID listing (getCurrentPageShapeIds() / id vs shapeId in the serialized bridge).

This is mostly a discoverability / guidance problem, not “tldraw can’t draw.” Scope: tighten MCP-facing guidance, add a small canonical doc for agents and humans, and optionally ship one Cursor-plugin skill (consumer-facing only) generated from that doc so it does not duplicate the existing .claude/skills trees used for repo / MCP-app development.

Steps to reproduce

  1. Install tldraw from the Cursor Marketplace → click Try in Chat (pre-filled prompt: “Show me how the tldraw plugin works”).
  2. Confirm the answer includes a practical flow similar to: open/create canvas → search → exec with small scripts → reuse canvasId to refine.
  3. Dog-food prompt: turn this practical flow into a new diagram in tldraw.

Expected

  • First exec succeeds, or the model quickly recovers using search + the simplified createShape API from the MCP tool examples (_type, shapeId, text, arrows, etc.).
  • If the model emits multi-line shape text, the canvas shows real line breaks, not a literal \n.

Actual (example from one session)

  1. First exec used full-editor-style APIs, e.g. toRichText(...), type: 'geo', createArrowBetweenShapes, etc. Runtime error: toRichText is not defined; canvas not updated (user may get a canvasId for retry).
  2. Retry using simplified API from docs still produced wrong text rendering when the payload carried over-escaped newlines (e.g. \\\\n in the JSON-wrapped code string → literal backslash-n in shape text).
  3. Clearing/redrawing on the same canvasId hit getCurrentPageShapeIds() / id assumptions: e.g. getCurrentPageShapeIds() not a usable array, simplified serialized shapes exposing shapeId instead of id, so naive deleteShapes(shapes.map(s => s.id)) fails.

Problem statement

  • Onboarding path (Try in Chat) teaches a flow that does not spell out the supported exec subset vs stock tldraw Editor examples.
  • MCP tool descriptions are the main cross-client signal; they are too easy to misread as “full Editor in a sandbox.”
  • Bridge / serialization quirks (IDs, page shape listing) are undocumented, so agents burn retries.

Proposed resolution (sensibly scoped)

A. MCP + docs (all clients; primary)

  1. Expand MCP exec / search tool descriptions (and any server instructions equivalent) with a short, explicit subsection:

    • Prefer search first when unsure.
    • exec environment is not guaranteed to expose every Editor / helper symbol; start from the documented simplified createShape examples unless search confirms otherwise.
    • canvasId: omit = new board; pass through = continue.
    • Footguns: JSON escaping for newlines inside the code string; use shapeId / getCurrentPageShapes() patterns for bulk ops if page ID helpers are unreliable in the bridge.
  2. Add one canonical doc in-repo, e.g. apps/mcp-app/docs/MCP_AGENT_GUIDE.md (name flexible), linked from apps/mcp-app/README.md under “For AI agents / MCP”. Keep it short; deep API remains search + spec.

B. Cursor plugin packaging (optional; Cursor-only)

  1. Add a single consumer skill under the published Cursor plugin tree using Cursor’s layout (skills/<name>/SKILL.md next to .cursor-plugin/), e.g. tldraw-mcp-canvas, whose only job is to steer agents to (A)—either inline 5–15 lines of non-duplicative summary or “follow MCP_AGENT_GUIDE.md” + triggers.

  2. Do not copy the existing repo root .claude/skills/* or apps/mcp-app/.claude/skills/* into the Marketplace plugin. Those target contributors / MCP-app authors and would bloat end-user installs and duplicate maintenance. If any future skill is needed in both places, use one SSOT file + a build/pack step that copies into plugins/.../skills/ (avoid two hand-edited copies).

C. Code fixes (optional stretch; only if you want fewer retries)

  1. Harden the bridge (if technically feasible without breaking compat): align serialized shape field names with what deleteShapes expects, or document/fix getCurrentPageShapeIds() in the MCP exec context so bulk delete patterns match tldraw docs.

Non-goals

  • Replacing search with a giant inline API dump.
  • Mirroring the full monorepo .claude/skills catalog into the Cursor plugin.
  • Rewriting the Try in Chat prompt into a novel product surface (unless trivial copy edits help).

Acceptance criteria

  • After Try in Chat + the dog-food prompt, a fresh model session succeeds without needing full-Editor-only APIs or recovers in one self-correction using documented patterns.
  • Multi-line text in simplified createShape renders as wrapped lines, not literal \n, when the agent uses correct JSON escaping (documented in MCP text + guide).
  • MCP_AGENT_GUIDE.md exists and is linked from apps/mcp-app/README.md.
  • MCP tool/server text mentions at least: supported exec subset expectation, canvasId, newline escaping, ID/listing caveat or bridge is fixed so the caveat goes away.
  • (Optional) Cursor plugin ships one skills/.../SKILL.md for end users; no wholesale copy of .claude/skills.

Context

  • Cursor plugin / MCP app: apps/mcp-app/, plugin metadata under apps/mcp-app/.cursor-plugin/ (Cursor plugin docs for bundling skills/).
  • Separate Claude Code skill trees: repo .claude/skills/ vs apps/mcp-app/.claude/skills/ — contributor tooling, not the same audience as Marketplace users.