Skip to main content
Glama

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.

Related MCP server: Design System MCP Server

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

Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    B
    maintenance
    MCP server that exposes your design system components and tokens to AI agents, preventing duplicate component creation and hardcoded token values.
    Last updated
    18
    9
    MIT
  • A
    license
    -
    quality
    A
    maintenance
    A read-only MCP server that provides AI coding agents with a queryable contract for design system tokens, components, patterns, and anti-patterns.
    Last updated
    30
    1
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • Read-only MCP server for Robinhood Chain token discovery, research, and due diligence via GMGN.

  • Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.

  • MCP server for AgentDocs (agentdocs.eu): read, search, write, comment on & share Markdown docs.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jtrainer357/ds-canon'

If you have feedback or need assistance with the MCP directory API, please join our Discord server