#10967·tldraw

Keep MCP cluster screenshot shape IDs out of the render URL

Author: kvnlooCreated Sep 28, 2026Updated Sep 28, 2026

Problem

The MCP cluster screenshot path can make the render URL grow linearly with the number of shapes being captured.

Current flow on main:

  1. parseClusterIds() accepts any non-empty list of cluster ids.
  2. pickClusterShapes() unions every shape id in the requested clusters.
  3. captureThumbnailScreenshot() puts that full shapeIds array into ThumbnailRenderJob.
  4. mintThumbnailRenderToken() JSON-serializes the whole job, base64url-encodes it, and signs it.
  5. buildThumbnailRenderUrl() puts that token in ?token=....
  6. The render page then sends the same token again to the snapshot endpoint in a query string.

There is no cluster-count or resulting-shape-count bound in this path. The SDK default allows up to 4000 shapes per page, so a large cluster / union of clusters can produce a very large URL before Browser Rendering does any useful work.

This is already called out as a follow-up in #10325 ("capping or relocating the MCP shapeIds render token (URL length)"), but I couldn't find a dedicated issue.

Suggested direction

Keep the signed render URL bounded and move the potentially large selection behind an opaque reference.

The MCP render path already has a per-capture R2 record keyed from the token and deletes it in finally. One possible shape is:

  • store the selected shape ids (or the full large render payload) in that per-capture record
  • put only a bounded reference plus integrity value/hash in the signed token
  • resolve the selection server-side before returning the render job to the page

The exact storage split is flexible; the important property is that adding shapes does not make the browser URL grow proportionally.

A count/serialized-length refusal could still be useful as defense in depth, but it would make large legitimate canvases unrenderable rather than removing the transport limit.

Acceptance criteria

  • Render URL/token size is bounded independently of the number of selected shapes.
  • The selected shape set remains integrity-protected; a caller cannot swap or widen it.
  • Concurrent identical MCP captures do not invalidate each other.
  • Per-capture records are cleaned up on success and failure as they are today.
  • Large cluster screenshots fail only on real render/resource limits, not query-string length.
  • Rolling deploys either understand both token forms or reject the new form explicitly rather than misreading it.