MCP - Headless and bot integrations
The interactive flow described in the MCP setup guide is the right path for IDEs and chat clients that can open a browser for OAuth. For unattended workloads — Discord bots, CI jobs, server processes — there are two supported patterns.
⚠️ The MCP server still relays every tool call through the html.to.design Figma plugin running on the same Figma account. Whichever Figma user owns the bot must keep the plugin open and signed in.
Endpoints at a glance
The MCP server is an OAuth 2.1 protected resource. Programmatic clients should read metadata from:
https://mcp.to.design/.well-known/oauth-protected-resource
https://mcp.to.design/.well-known/oauth-authorization-server
The authorization server publishes a standard discovery document with
token_endpoint, registration_endpoint, grant_types_supported, etc.
| Item | Value |
|---|---|
| Resource | https://mcp.to.design |
| Resource scope | mcp |
| AS scopes | openid, offline_access, mcp |
| Grant types | authorization_code, refresh_token, client_credentials |
| Access-token TTL | 1 hour |
| Refresh-token TTL | 14 days (non-rotating) |
| Dynamic Client Registration | RFC 7591, public endpoint at registration_endpoint |
Pattern 1 — Authorization code + refresh token (recommended)
Best when the bot operator can sign in once via a browser on any machine and then ship the refresh token to the headless process.
-
Register a client (one-time) via Dynamic Client Registration:
curl -X POST https://mcp.to.design/reg \ -H 'Content-Type: application/json' \ -d '{ "client_name": "my-discord-bot", "redirect_uris": ["http://127.0.0.1:43117/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "openid offline_access mcp" }'The response includes
client_id,client_secret, and aregistration_access_tokenfor later updates. -
Do the auth-code dance once with PKCE, requesting
scope=openid offline_access mcpandresource=https://mcp.to.design. You can do this on a workstation; only the resulting refresh token needs to move to the bot host. -
In the bot, exchange the refresh token for a fresh access token before each MCP call (or cache it for ~50 minutes):
curl -X POST https://mcp.to.design/token \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -d "grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&resource=https://mcp.to.design" -
Call MCP with the access token in the
Authorization: Bearerheader, exactly like any other MCP client.
Refresh tokens are non-rotating with the default config, so the same value remains valid for the full 14-day TTL.
Pattern 2 — Service-account / client_credentials
Best when you cannot persist a user-derived refresh token, or you want the credential to be revocable independently of the human’s session.
A service account is a client_credentials OAuth client bound to a Figma
user. The binding step requires the human to authenticate interactively once;
after that, the bot uses the issued client_id/client_secret indefinitely.
-
Sign in once interactively (via the same auth-code flow as Pattern 1) to obtain a normal user-scoped access token.
-
Provision a service account by POSTing to
/service-accountswith that user token:curl -X POST https://mcp.to.design/service-accounts \ -H "Authorization: Bearer $USER_ACCESS_TOKEN"Response:
{ "client_id": "svc_…", "client_secret": "…", "token_endpoint": "https://mcp.to.design/token", "resource": "https://mcp.to.design", "scope": "mcp", "grant_type": "client_credentials" }Store the secret immediately — it is only returned once.
-
In the bot, mint short-lived access tokens with the standard OAuth client_credentials grant:
curl -X POST https://mcp.to.design/token \ -u "$SVC_CLIENT_ID:$SVC_CLIENT_SECRET" \ -d "grant_type=client_credentials&resource=https://mcp.to.design&scope=mcp" -
Call MCP with the access token as
Authorization: Bearer …. The server resolves the bound Figma uid from the service-account record, so the call reaches the same Figma plugin instance as the human’s interactive session.
If the binding is missing, the server responds with 401 Unauthorized. The
error_description is carried both in the WWW-Authenticate: Bearer … header
and in the JSON response body ({"error": "unauthorized", "error_description": "service-account client is not bound to a Figma user…"}).
Token lifecycle and revocation
- Access tokens: 1 hour. Always re-mint via
/token, don’t cache aggressively. - Refresh tokens: 14 days, non-rotating. Replace by re-running the auth-code flow.
- Revocation: the standard OAuth revocation endpoint is exposed at
revocation_endpointin the AS metadata. Revoking a service-account client is on the roadmap; in the meantime, email support@divriots.com to disable asvc_…client.