Files
upstream-mirror a774ac673a
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
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

3.8 KiB

tldraw.com auth.md

You are an agent. This page describes how to obtain a token for the one authenticated API tldraw.com exposes to agents: the MCP server at https://www.tldraw.com/api/app/mcp, which reads the pages, shape clusters and rendered screenshots of tldraw.com boards.

There is no anonymous tier. Every call needs a token belonging to a signed-in tldraw.com account, and the server additionally checks that the account can see the board being asked about.

What tldraw.com does and does not support

  • OAuth 2.1 with PKCE, against Clerk as the authorization server. tldraw is the resource server only; it never issues a token, it only verifies one.
  • Client ID Metadata Documents (CIMD) are how a client identifies itself. Your client_id is an HTTPS URL serving your client metadata.
  • Dynamic client registration is not supported. There is no registration endpoint to POST to. A client that can only do dynamic registration cannot connect until it supports CIMD.
  • There is no agent self-registration or claim ceremony. A human signs in to their own tldraw.com account through the normal browser consent flow; you never assert an identity on their behalf.

Discover

Call the MCP endpoint without a token. The 401 carries everything you need:

POST /api/app/mcp HTTP/1.1
Host: www.tldraw.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://www.tldraw.com/.well-known/oauth-protected-resource/api/app/mcp", scope="openid profile email offline_access"

Fetch the resource_metadata URL for the RFC 9728 protected resource metadata:

{
	"resource": "https://www.tldraw.com/api/app/mcp",
	"authorization_servers": ["https://clerk.tldraw.com"],
	"scopes_supported": ["openid", "profile", "email", "offline_access"],
	"bearer_methods_supported": ["header"],
	"resource_documentation": "https://tldraw.dev"
}

The same document is served at https://www.tldraw.com/.well-known/oauth-protected-resource for clients that fall back to the path-less form.

Then fetch https://clerk.tldraw.com/.well-known/oauth-authorization-server for the authorization and token endpoints.

Authorize

Run the standard authorization code flow with PKCE against Clerk, with your CIMD URL as client_id.

Request exactly the four scopes named in the challenge — openid profile email offline_access — and no others. Clerk advertises more scopes than any one client is granted, and asking for an ungranted scope fails the whole request with invalid_scope before the user ever sees a consent screen. Only sub is read from the resulting token; the rest are there because they are what Clerk's defaults already hand most clients.

Send the access token as Authorization: Bearer <token>.

What refusals mean

  • 401 — no token, or a token that failed verification. Re-run the flow. The WWW-Authenticate header points at the metadata again.
  • 403 with "error": "forbidden" — the token is valid and the account is real, but that account is not enabled for the MCP server yet. Access is being rolled out behind a flag; there is nothing a client can do about this, and retrying will not help. The account holder should ask tldraw for access.
  • A board that is reported as not found — one message covers every way a board fails to resolve, including boards that exist but that this account cannot see. It is not a signal to retry with different parameters.

Protocol versions

The server speaks 2026-07-28 and 2025-11-25. Older revisions, including the pre-authorization 2024-11-05, are not served.

Machine-readable metadata

  • Server Card — https://www.tldraw.com/api/app/mcp/server-card
  • API catalog — https://www.tldraw.com/.well-known/api-catalog
  • AI catalog — https://www.tldraw.com/.well-known/ai-catalog.json