ds-canon

ds-canon

Read-only MCP server that exposes a design system's tokens, components, conventions, and deprecations as queryable tools, enabling agents to look up canonical values, assess change impact, and detect hardcoded value drift.

Category
Visit Server

README

ds-canon

Your design system's canon, queryable by agents. What exists, what is deprecated, what breaks if you touch it.

Design systems decay into tribal knowledge the moment the token sheet drifts from the code and the person who remembers why gets pulled onto another project. Agents writing UI code make this worse: they hallucinate plausible token names and confident-sounding component APIs because they have nothing authoritative to check against. ds-canon puts your design system's tokens, components, conventions, and deprecations behind a read-only MCP server, so agents and the humans directing them query the same system of record instead of guessing.

The 60-second demo

Run it straight from npm, nothing to clone:

npx -y ds-canon

You'll see a one-line banner on stderr confirming what loaded:

ds-canon v0.1.0 serving Nimbus DS (40 tokens, 9 components) from /path/to/ds-canon/fixtures, read-only

The server ships with a fixture design system called Nimbus DS so you can try it immediately, no setup required. Point an MCP-aware agent at it and ask it real questions.

"Which accent color should I use, and is anything deprecated?"

The agent calls list_tokens with group: "color", query: "color.accent", then whats_deprecated. Real output, verbatim:

{
  "tokens": [
    {
      "name": "color.accent.primary",
      "value": "#3B5BDB",
      "type": "color",
      "group": "color",
      "status": "active",
      "description": "Primary brand accent. Used for primary actions, active navigation state, and focus affordances."
    },
    {
      "name": "color.accent.secondary",
      "value": "#5C7CFA",
      "type": "color",
      "group": "color",
      "status": "active",
      "description": "Secondary accent for lower-emphasis interactive elements that still need to read as brand-colored."
    },
    {
      "name": "color.accent.legacy",
      "value": "#4C6EF5",
      "type": "color",
      "group": "color",
      "status": "deprecated",
      "description": "Original brand blue from the v1.x palette. Slightly less saturated than accent.primary; kept only for Banner and LegacyButton until both migrate.",
      "deprecatedBy": "color.accent.primary"
    }
  ]
}

(The query parameter is a substring match on name and description, so a looser query like "accent" also surfaces tokens whose descriptions mention accent usage. Scoping the query to the name prefix keeps the answer tight.)

{
  "deprecated": [
    { "name": "color.accent.legacy", "kind": "token", "deprecatedBy": "color.accent.primary", "dependentCount": 1 },
    { "name": "LegacyButton", "kind": "component", "deprecatedBy": "Button", "dependentCount": 0 }
  ]
}

The agent now knows to recommend color.accent.primary and to flag color.accent.legacy as on its way out, with one live component (Banner) still depending on it.

"I want to change space.inset.md. What will it affect?"

This is the question a design system actually needs to answer before anyone touches a shared value. The agent calls find_usages:

{
  "entity": "space.inset.md",
  "usages": [
    { "dependent": "Button", "dependentKind": "component", "relation": "consumes token" },
    { "dependent": "Card", "dependentKind": "component", "relation": "consumes token" },
    { "dependent": "Field", "dependentKind": "component", "relation": "consumes token" },
    { "dependent": "Modal", "dependentKind": "component", "relation": "consumes token" }
  ]
}

Blast radius, in one call: four components, named exactly. No spelunking through a component library to find every place 12px got typed in by hand.

"Write a secondary button that follows our conventions."

The agent calls get_component for Button (props, variants, the tokens it consumes, and its doNotUse guidance) and get_conventions for the color topic, then writes the component. Now suppose it (or a human) had instead hardcoded the color:

<button style={{ background: '#3B5BDB', padding: '12px' }}>Save</button>

Running that snippet through check_token_drift catches both literals:

{
  "findings": [
    {
      "severity": "warn",
      "raw": "#3B5BDB",
      "suggestion": "color.accent.primary",
      "message": "Hardcoded value #3B5BDB matches token \"color.accent.primary\". Use the token instead of the raw value."
    },
    {
      "severity": "warn",
      "raw": "12px",
      "suggestion": "space.inset.md",
      "message": "Hardcoded value 12px matches token \"space.inset.md\". Use the token instead of the raw value."
    }
  ]
}

#3B5BDB and 12px are exact token values, so the finding names the token, not just the problem.

Install

ds-canon runs as a local MCP server over stdio. There is no separate service to deploy.

mcp.json (Claude Desktop, or a project-level .mcp.json for Claude Code):

{
  "mcpServers": {
    "ds-canon": {
      "command": "npx",
      "args": ["-y", "ds-canon"]
    }
  }
}

Claude Code, one line:

claude mcp add ds-canon -- npx -y ds-canon

Claude Desktop: add the same mcpServers entry to your claude_desktop_config.json and restart the app.

Working from a clone instead (for development or custom fixtures): git clone, npm install, npm run build, then point command at node with args: ["/absolute/path/to/ds-canon/dist/index.js"].

Tools

Eight tools, all read-only.

Tool What it answers Key inputs
list_tokens What tokens exist? group?, status?, query?
get_token What is this token and who uses it? name
list_components What components exist? status?, tag?
get_component What does this component look like? name
find_usages What breaks if I change this? entity
whats_deprecated What should I stop using? none
get_conventions What are the house rules? topic?
check_token_drift Does this code drift from the token system? snippet, lang?

get_token, get_component, and find_usages look up by exact name; a miss returns a not_found error with up to three closest-name suggestions instead of an empty result, so a typo doesn't read as "this doesn't exist."

Point it at your own system

ds-canon reads three files from a fixture directory: tokens.json, components.json, and conventions.md. By default it loads the bundled Nimbus DS fixtures. Set DS_CANON_FIXTURES to point it at your own:

DS_CANON_FIXTURES=/path/to/your/design-system node dist/index.js

or in mcp.json:

{
  "mcpServers": {
    "ds-canon": {
      "command": "npx",
      "args": ["-y", "ds-canon"],
      "env": { "DS_CANON_FIXTURES": "/path/to/your/design-system" }
    }
  }
}

tokens.json is plain W3C DTCG format: nested groups, each leaf with $value, $type, and $description. If you already export tokens from Style Dictionary or Tokens Studio in DTCG format, that file works here with no transformation. Deprecation and aliasing use two extensions on top of the base spec: an $extensions["ds-canon"] block for status/deprecatedBy, and DTCG's own {token.path} reference syntax for aliases. components.json is a flat { meta, components } shape matching the DsComponent type in src/types.ts. conventions.md is Markdown: one ## topic section per convention (naming, spacing, color, accessibility, deprecation), each with Rule:, Rationale:, and Example: lines. The loader validates all three at startup and throws a specific, file-and-field-level error message on anything malformed, rather than serving a partially-loaded system.

Why read-only, why stdio, why no network

Every tool in ds-canon reads from an in-memory index built once at startup. Nothing in this server writes to the fixture files, calls out to a network, or accepts write operations of any kind. That's not an implementation gap, it's the point: a design system's system of record should not be mutable by the same agents that consume it, and a tool that only answers "what exists" cannot be tricked into becoming a tool that changes what exists.

Running over stdio instead of as a network service means ds-canon has no port to scan, no auth to misconfigure, and no attack surface beyond the local process that spawns it. The deployment model doubles as the governance model: install it, point it at your tokens, and every agent that would otherwise guess now has one, unwritable, source of truth to query instead.

How this was built

ds-canon was built by a multi-agent factory in an afternoon: parallel contract-first builders working from frozen type definitions, adversarial challengers doing black-box QA and architecture review against the built server, and agent-to-agent fix loops closing every finding before the next phase started. The full build log, prompts, and challenge reports are in factory/.

License

MIT. See LICENSE.

Jay Trainer, Sr. Director of Product Design (AI-Native). jaytrainerdesign.com

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured