MCP server
Published as @unpunnyfuns/swatchbook-mcp. Model Context Protocol server for swatchbook — exposes a DTCG project's tokens, axes, and diagnostics to AI agents without running Storybook.
What it's for
Agents that need to reason about your design tokens — figma-to-token round-trips, alias-chain navigation, CI lint hooks, AI-assisted authoring — without spinning up a Storybook iframe. Point it at a swatchbook.config.{ts,mts,js,mjs} or a bare DTCG resolver.json and it parses the project on startup, then answers MCP tool calls against the resolved graph.
Install
npm install -D @unpunnyfuns/swatchbook-mcp
Or run straight from npx — the package ships a swatchbook-mcp bin.
Run
# full config (shared with the Storybook addon's `configPath`)
npx @unpunnyfuns/swatchbook-mcp --config swatchbook.config.ts
# or a bare DTCG resolver — no wrapper needed, other options default
npx @unpunnyfuns/swatchbook-mcp --config tokens/resolver.json
CLI flags:
| Flag | What |
|---|---|
--config <path> | Required. A swatchbook.config.{ts,mts,js,mjs} (full config) or a DTCG resolver.json (bare — other options at defaults). |
--cwd <path> | Override the working directory for resolving relative resolver / tokens paths. |
--no-watch | Disable live-reload. By default the server watches the config + resolved source files and swaps in fresh data on edits. |
--help, -h | Print usage and exit. |
Wire into an MCP client
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"swatchbook": {
"command": "npx",
"args": [
"-y",
"@unpunnyfuns/swatchbook-mcp",
"--config",
"/absolute/path/to/swatchbook.config.ts"
]
}
}
}
Restart the client and ask it about your tokens — it'll call describe_project first, then drill into whatever you asked about.
Tools
| Tool | Inputs | Returns |
|---|---|---|
describe_project | (none) | Orientation: counts, axes, themes, presets, diagnostic totals, $types present. |
list_tokens | filter? path glob, type? DTCG $type, theme? name | Array of { path, type?, value } from the named theme (or default). Use first to discover paths. |
search_tokens | query, theme?, limit? | Case-insensitive substring search across paths, descriptions, and values. Returns matches + matchedIn hint. |
resolve_theme | tuple, filter?, type? | Resolved token map for an axis tuple ({ mode: "Dark", brand: "…" }). Fills omitted axes from defaults. |
get_token | path | Full detail: per-theme value, alias chain, aliased-by list, CSS var reference. |
get_alias_chain | path | Forward alias chain per theme (path → ... → primitive). Empty when the token is a primitive. |
get_aliased_by | path, maxDepth? | Backward alias tree — every token that resolves through this path. Breadth-first with cycle protection. |
get_consumer_output | path, tuple? | CSS var, resolved value, compound [data-…] selector + HTML attrs needed to pin the tuple on <html>. |
get_color_formats | path, theme? | Color token rendered in hex / rgb / hsl / oklch / raw, each with an outOfGamut flag. |
get_color_contrast | foreground, background, theme?, algorithm? | Pair-wise contrast between two color tokens. wcag21 → ratio + AA/AAA pass flags (normal + large text). apca → signed Lc + body / large-text / non-text pass flags. |
get_axis_variance | path | Classify how the token's resolved value depends on each axis. Returns kind (constant / single / multi), varying vs constant axes, and a per-axis breakdown with the value seen in each context. |
list_axes | (none) | Axes + contexts + themes + presets from the project config. |
get_diagnostics | severity? 'error' | 'warn' | 'info' | Parser / resolver / validation diagnostics. |
emit_css | (none) | Full project stylesheet — :root default + per-tuple compound-selector blocks. |
Path globs accept * (one segment), ** (any number of segments trailing or mid-path), or exact dot-paths.
Programmatic use
For embedding the server in a larger toolchain:
import { createServer, loadFromConfig } from '@unpunnyfuns/swatchbook-mcp';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const { project } = await loadFromConfig('swatchbook.config.ts');
const server = createServer(project);
await server.connect(new StdioServerTransport());
loadFromConfig accepts the same .ts / .mts / .js / .mjs / .json shapes the CLI does. createServer(project) returns the server augmented with setProject(next) — call it after re-loading the project to swap in fresh data without restarting the MCP transport. The CLI uses this to live-reload on token edits.
See also
@unpunnyfuns/swatchbook-core— the loader this server wraps.- Model Context Protocol — upstream spec.
- MCP Inspector — web UI to poke the server interactively.