Platform

Documentation

Create and share your design systems.

Platform stores your design system as a JSON token document per project. You author it visually, share it with teammates, and read it from AI tools through the built-in MCP server, with no export step in between.

How it works

Platform has one core idea: your tokens live in one place, and everything else reads from it. The loop looks like this.

  1. Create a project. Each project is a single JSON token document. New projects start from a small default palette you can replace immediately.
  2. Author your tokens. Organize them into groups: color, typography, spacing, radius, shadow, or anything your team invents. Color values render a live swatch as you type.
  3. Share the project. Invite teammates by email. Shared projects show up in their workspace and they can edit alongside you.
  4. Consume the tokens anywhere. Export for code, copy CSS custom properties, call the REST API, or connect an AI coding agent over MCP. All four read the same document, so nothing drifts.

Everything is scoped to your account. You always see the projects you own plus the ones shared with you, and never anything else.

Projects & tokens

A project holds token groups: color, typography, spacing, radius, shadow, or any group you add. Each group declares a type, an optional description, and an array of tokens:

{ "color": { "type": "color", "description": "Brand palette", "tokens": [ { "name": "primary", "value": "#4ED391" }, { "name": "background", "value": "#F7F8FA" } ] }, "radius": { "type": "borderRadius", "description": "", "tokens": [ { "name": "sm", "value": "4px" }, { "name": "pill", "value": "999px" } ] } }

The group's type tells the editor how to preview each value: a color chip, a rounded corner, the real shadow, which is why it is declared rather than guessed: 16px is equally valid as spacing, a radius, a border width or a font size, and nothing about the value itself says which. It is set when the group is created and fixed from then on, so every token in a group shares it.

A token has just a name and a value: anything explanatory belongs to the group, not to each value. Values are free-form strings, so a token can hold a hex color, a length, a font stack, or a full shadow definition.

basic is the exception: it is the one type with nothing to preview, so each of its tokens may carry a description, shown in the column the swatch would otherwise occupy.

The editor

The workspace has three panes: token groups on the left, the tokens in the selected group in the middle, and the AI agent on the right.

Every variable is named --type-name. The type is the namespace, so --color-blue-500 is known to be a color and --size-gutter a length without reading anything else. A group's name is a label for you and never appears in a variable, which is why two groups of the same type cannot both hold a token called 500.

:root { --color-primary: #4ED391; --size-md: 16px; --border-radius-lg: 16px; }

Rules

Tokens are values; Rules is where you say what they're for: which green is for destructive actions, when to reach for the large radius, what never to touch the display face. It's a page like Preview, always there below your token groups, written as plain Markdown text with syntax highlighting to make the structure visible as you type. Nothing renders it as HTML anywhere, so there's nothing to sanitize and nothing to style beyond the editor itself.

Rules travel differently from everything else in the project. They're sent with MCP reads, ahead of the tokens themselves, since an agent writing code needs to know which token to reach for before it needs the list of values, and they're kept in the backup file. They are never included in the CSS, code or Figma exports: only you and your collaborators, and whatever you connect over MCP, ever see them.

One exception, and it is on purpose: a duplicate inherits them. Duplicating is one person deciding to take your system and use it, so the copy arrives with the reasoning as well as the values, which is the half that cannot be worked out by reading the tokens. Write them for that reader too.

Recording a licence

A system that points at a typeface or a photograph carries a question with it: who may use this, and where? Answering it in the project is worth the two minutes, and where you answer it decides who ever sees the answer.

And the simplest answer of all is to pick assets that need no explanation. Anything under the SIL Open Font License, most of Google Fonts, can be exported and copied by whoever you share your system with, which is what a design system is for. See the terms for what you are agreeing to when you upload or link an asset.

Importing & exporting

A system you already have does not need retyping, and one you build here does not need to stay here. Both directions are deterministic: the same file produces the same tokens every time, with no AI in the path. The agent is for judgement (naming a scale, filling a gap); moving a file is not judgement, so nothing here guesses.

Importing

In an empty project, press Import. In a project that already has groups, use Import tokens in the project menu. Paste the text or choose a file. The format is worked out for you, so there is nothing to pick.

Importing replaces every token in the project. It is meant for the first move of a system into Platform, not for merging two. The dialog says so, and names the number of groups it is about to replace.

Afterwards you are told how many tokens landed in how many groups, and how many could not be typed. That last number is the one worth reading: anything unrecognisable goes to a group called Other rather than being dropped or guessed at, so it stays visible and is one drag from the right place.

Exporting

Four formats, for four different readers, all in the project menu. Three of them write JSON, so each is named for where it goes rather than for its format: Export for code and Export backup are both .json files and are not interchangeable.

Using tokens in JavaScript

Export for code gives you one flat object, keyed by the same js_property name the MCP server reports, so an agent and a build script see one convention rather than two:

{ "colorGreen500": "#4ED391", "sizeMd": "16px", "borderRadiusPill": "999px", "boxShadowLg": "0px 8px 24px 0px rgba(21, 23, 26, 0.16)" }

It is .json rather than .js deliberately: every JavaScript environment imports JSON, so there is no module format to choose and no TypeScript to require of anyone.

Values keep their units. "16px", not 16. That is lossless and directly correct for the web; React Native's dimension props want numbers, so a project there applies parseFloat to the handful that need it. Stripping units here would be guessing which consumer is reading the file.

Colors, font stacks and shadows cross untouched: modern React Native takes a CSS shadow string in boxShadow verbatim, which makes a shadow the one token that is identical in Figma, CSS and React Native at the same time.

Two types are rewritten: filter and transform. React Native takes a list of one-key objects for both, not a string, so the CSS text does nothing there at all: brightness(120%) blur(4px) comes out as [{ brightness: 1.2 }, { blur: 4 }]. Percentages become ratios, lengths lose their unit, and angles stay text ({ rotate: "12deg" }). A chain holding something the array form cannot say (drop-shadow(), matrix(), a 3d function) keeps its CSS string whole instead, so you can see what was meant rather than half a value that looks applied.

One value does not translate: lineHeight. CSS reads 1.5 as a multiplier; React Native reads it as 1.5 pixels. Converting needs the font size, which a line-height token does not know. It is exported anyway, because it is correct for CSS and every web consumer, but multiply it yourself before handing it to React Native.

If two token names differ only in hyphenation (green-500 and green500), they camelCase to the same key and one replaces the other. Platform counts what went in against what came out and tells you when that happens; rename one of them.

The Figma round trip

Figma has no built-in token export, so this needs a plugin. We suggest DTCG Design Token Manager, because it goes both ways: it can read a token file into Figma variables and write your variables back out. A one-directional plugin only solves half of this.

Figma → Platform. Export your variables from the plugin as DTCG, then paste the file into Platform's importer, or choose it with the file button.

Platform → Figma. Use Export to Figma, then import the file with the same plugin. Everything is written as a literal value, never a reference, because that is what variable plugins can reliably read.

We favour DTCG over the alternatives for one reason: it is typed. The file states that 4 is a dimension and #4ED391 is a color, so almost nothing has to be inferred. A stylesheet, by contrast, gives us 4px and leaves us to work out whether that is a radius, a gap or a border width, a guess we make from the name, and get wrong sometimes.

Naming your Figma variables tier/type/name is what makes the trip clean in both directions. primitives/color/green/500 becomes a token called green-500 in a group called Primitives Color, and exports as --color-green-500. The type word is dropped from the name because Platform puts it back; leaving it in would give you --color-color-green-500.

Words that name a type: color, gradient, opacity, radius, border-width, shadow, spacing, font-family, font-size, font-weight, line-height, letter-spacing. One to avoid: border on its own means border width, so a group of border colors called border will preview as lines rather than swatches. Call it something else.

Figma writes lengths as plain numbers with the unit implied, so a spacing of 4 arrives without its px. Platform puts it back, since pixels are what Figma counts in and it has no way to say otherwise.

What Figma cannot carry

Figma variables come in four kinds: color, number, string and boolean, and that is the whole of what round-trips. In practice it covers colors, spacing, radii, border widths, type sizes, weights, line heights, letter spacing and font families, which is most of a design system.

The rest has to be added in Platform directly:

This is a limit of Figma's data model rather than of the format, and no plugin gets round it. Treat Platform as the source of truth for the whole system, and Figma as the place the variable-shaped part of it is designed.

Sharing with your team

Open a project and use Share to invite a teammate by the email they registered with. You can invite someone before they have an account, and the project is waiting when they sign up. Shared projects appear in their workspace with a shared badge.

Each person you invite gets a role, which you can change or revoke at any time from the same dialog:

MCP follows the same rules: a viewer's token gives read access through list_projects and get_design_tokens, which is usually all a developer consuming the system needs. Deleting and managing access stay with the owner.

AI agent

The right-hand panel is an assistant that knows your current tokens. Ask it to build a palette, extend a type scale, or check a set for gaps. When it proposes changes it returns a complete token document, and the editor applies it as soon as it arrives, as swatches and values rather than as JSON to read. Nothing is saved by this: the project is marked unsaved, so Save and Revert both still mean what they meant. The editor is locked while the agent is working, so a click on New group mid-answer cannot land on a project that is about to change underneath it.

Use the Generate button in the toolbar to hide or show the panel; your conversation is kept either way. The agent requires OPENAI_API_KEY on the server, and tells you if it isn't configured.

MCP server

Platform exposes a Model Context Protocol server at https://platformdesign.app/mcp over Streamable HTTP. Any MCP client can connect and read your design tokens as live context, so the agent writing your components uses your real values instead of guessing.

It is a remote HTTP server, not a local one: there is nothing to install to connect to it, and no npm package in the loop. A client needs only the URL above and your access token.

1. Get your access token

Open the app and click Copy MCP token in the top bar. That copies the same JWT the API uses. You can also get one from the command line:

curl -X POST https://platformdesign.app/api/account/authenticate \ -H "Content-Type: application/json" \ -d '{"email":"you@team.com","password":"…"}' # → { "success": true, "jwt": "…" }

Tokens expire after 72 hours. When a client starts returning authorization errors, copy a fresh one.

2. Connect a client

Claude Code: add the server from your terminal:

claude mcp add --transport http platform https://platformdesign.app/mcp \ --header "Authorization: Bearer YOUR_TOKEN"

Cursor, Claude Desktop, or any client using a JSON config: add an entry under mcpServers:

{ "mcpServers": { "platform": { "type": "http", "url": "https://platformdesign.app/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } }

Restart the client after editing its configuration. If you are running Platform yourself, substitute your own host. Locally that is http://localhost:8080/mcp.

A few older clients only speak the local stdio transport and cannot connect to a remote server directly. Those need a stdio-to-HTTP bridge such as mcp-remote; every current client listed above connects natively.

3. Available tools

4. Verify the connection

In Claude Code, run /mcp to confirm platform is connected. Then ask the agent something that needs your tokens, "what are my color tokens?", and it should call the tools and answer with real values. You can also check the endpoint directly:

curl -X POST https://platformdesign.app/mcp \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A 401 means the token is missing or expired. A successful call returns the tools above.

5. Add the Agent Skill

The MCP hands an agent your design system. The Agent Skill teaches it what to do with one: read your Rules before the values, use each token's exact exported name rather than a name it invents, and tell you when your system has no token for something instead of quietly hardcoding it. Installing it is one command:

npx skills add Platform-DS/platform-design-skills --skill platform-design --global

Restart your client afterwards so it picks the skill up. It is optional — the server works without it — but it is the difference between an agent that can read your tokens and one that reliably does. The skill is open source, so you can read exactly what it tells your agent to do at github.com/Platform-DS/platform-design-skills.

6. Sync your tokens

MCP doesn't push updates: a connected client only reads your tokens when you ask it to. There's no background sync and no ID to remember: just refer to your project by the name shown in the app, and the agent looks up its id for you.

Sync the color tokens from "My design system" into tokens.css

Re-run this any time your tokens change on Platform. Nothing updates your code automatically. If you have two projects with the same name, the agent will ask which one you mean.

REST API

Every endpoint takes an Authorization: Bearer <token> header, using the same token as MCP.

Self-hosting

Platform is self-hostable and ships with everything needed to run it yourself. See the repository README for the current setup steps. The server needs DATABASE_URL and JWT_SECRET, plus OPENAI_API_KEY if you want the agent sidebar.