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.
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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。