weave-design-system-mcp
Provides tools for querying React component prop contracts and variant values from TSX source files, and for validating React TSX snippets against the design system.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@weave-design-system-mcpWhat spacing tokens and button variants are available in the design system?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
weave-design-system-mcp
Coding agents generate UI that drifts from the design system, because the system lives in docs,
Storybook, and reviewer heads — none of that is queryable at generation time, and nothing checks
the output afterward. This is an MCP server that makes a design
system queryable by an agent before it writes code, and its output checkable against that same
system afterward. Nothing about a specific design system, token set, or component library is
hardcoded — everything comes from a designsystem.config.json in whatever workspace you point
it at.
Your workspace — the codebase holding the design system — keeps its own
designsystem.config.json, and you point the server at that folder when you register it.
Setup
Node ^22.18.0 or >=24.11.0 (pinned in .nvmrc).
There's nothing to install or keep running — your MCP client starts the server as a subprocess and
stops it with the session, and npx fetches it on first use.
1. Describe your design system
Add designsystem.config.json to the root of your workspace. Paths are relative to that file:
{
"tokens": [{ "source": "vanilla-extract", "path": "src/styles/theme.css.ts" }],
"components": [{ "source": "react-tsx", "include": ["src/components/**/*.tsx"] }],
"styles": { "source": "vanilla-extract" },
"classNames": { "source": "tailwind" }
}Only tokens and components are required — see the config reference for
every source and its fields. Writing this by hand is optional: point your coding agent at that
reference and ask it to write the config for the repo it's sitting in.
Components are read through the real TypeScript checker, so your workspace also needs its own dependencies installed for them to resolve.
2. Register it with your client
Claude Code:
claude mcp add weave-design-system-mcp -- npx -y @avcs/weave-design-system-mcp /path/to/your-workspaceAny client using mcpServers JSON (Claude Desktop, Cursor, Windsurf):
{
"mcpServers": {
"weave-design-system-mcp": {
"command": "npx",
"args": ["-y", "@avcs/weave-design-system-mcp", "/absolute/path/to/your-workspace"]
}
}
}DESIGN_SYSTEM_PATH works instead of the argument if you'd rather use an environment variable.
With neither, the server reads the folder it was started in.
3. Confirm it found your design system
In Claude Code, /mcp lists the server and its six tools. Then ask something only the design
system can answer — "what spacing tokens exist?" — and you should see it call list_tokens.
That's the whole setup. If the answers come back empty, a client only shows you that the tools
exist and not what they loaded, so you can optionally start the server yourself to see the counts
it found — it prints a summary, then waits for protocol traffic, so Ctrl-C out:
npx -y @avcs/weave-design-system-mcp /path/to/your-workspace
# weave-design-system-mcp ready on stdio — 408 tokens, 103 components0 components almost always means the workspace's dependencies aren't installed, or include
doesn't match its layout.
Related MCP server: CDS Components MCP Server
Config reference
tokens and components take either one source object or an array of them.
Field | Purpose |
| Where the design tokens are. Tokens can be spread across several files or formats — list each one and they're concatenated. |
| Where the components are: your workspace's own source files, published packages, or both. |
| Optional. Which styling system |
| Optional. Which utility-class convention |
| Optional, defaults to |
Each source names an implementation, and each implementation defines its own remaining fields
and its own checks. The ones that ship:
Token sources (tokens[].source)
vanilla-extract— readscreateThemeContract/createGlobalThemecalls, flattening nested paths to dot-separated names. AcreateThemeContractleaf declares shape without a value, so it produces a token with areferenceand novalue.object— reads a JSON file, or a.ts/.jsmodule'sexport const X = {...}, and flattens it the same way. Fields:export(which named export, if a file has more than one),rootPath(dot-separated — navigate into a nested key before flattening, e.g. a JSON file wrapped in{ "tokens": {...} }),referenceRoot(override the identifier printed before the dotted path; defaults to the export's name, or the file's basename for JSON).
Component sources (components[].source)
react-tsx— reads React component prop contracts through the real TypeScript type checker (react-docgen-typescript, the same tool Storybook's autodocs use), which is what lets a variant prop typed askeyof typeof someTokenObjectresolve to its actual allowed values rather than only a union written out literally. Also reads each component's colocated.storiesfile and its@invalidAlternativeJSDoc tag (see below). Fields:include(glob pattern(s)),tsconfig(path to a tsconfig, needed to resolve path aliases like a monorepo's@app/*).npm-package— reads an installed package's components from its type declarations, so an icon library or a set of headless primitives becomes part of the queryable inventory rather than something an agent has to guess at. Fields:package(the package name, resolved from your workspace),names("*"or omitted for everything it exports, or an array of specific names).
Styles sources (styles.source): vanilla-extract.
ClassNames sources (classNames.source): tailwind.
A format that isn't listed here needs a new adapter — one file plus one branch, see Architecture.
Tools
Tool | Purpose |
| List tokens, optionally filtered to one group. Call with no group first — group names vary by project ( |
| The component inventory: name, description, category, and which props are variant-like. |
| Ranked token search across names, groups, references and values — for when you know what you want but not what this system calls it. |
| Ranked component search across names, descriptions, categories and prop names — the first call to make before building any UI element from scratch. |
| One component's full contract: every prop with its type and required flag, and the allowed value set for each variant-like prop. An unknown name comes back with the closest real names. |
| Check a JSX/TSX snippet or a style file's content. Unparseable input comes back as a finding. |
What validate checks
Each finding names the exact constraint broken and, where derivable, the exact fix.
Values that should be tokens
Hardcoded literals in style-defining code — for
vanilla-extract, that'sstyle()andstyleVariants()(from@vanilla-extract/css) andrecipe()(from the separate@vanilla-extract/recipespackage), including everything nested insideselectors, media-query and variant objects, quoted pseudo-selector keys included. In a system that splits styling between a CSS-in-TS library and utility classes, this is where most real token violations live.Hardcoded literals in JSX — an inline
style={{...}}prop, and utility-class arbitrary values (text-[#1e1e22],p-[13px]undertailwind), which step outside the design system's scale by construction.
The suggestion on these comes from a value-to-token reverse index built at load time: nearest
color for a hex value, nearest numeric token for spacing, radius and the like — the fix an agent
can apply in one pass.
Wrong implementations
Unknown variant value — a variant-like prop set to a value outside its allowed set, with the allowed set named in the message.
An invalid alternative to a design system component — reaching for a raw
<button>, or for some other library'sMuiButton, when the system has aButtonof its own. A component declares what it supersedes with an@invalidAlternativeJSDoc tag, naming native elements and components alike:/** * Primary interactive control for triggering an action. * @invalidAlternative button, MuiButton */ export function Button(props: ButtonProps) { /* ... */ }Declarations use CSS selector syntax, so a component can name a tag and the classes on it — which is how a layout primitive says "a plain div is fine, a div doing my job is not":
Declaration
Matches
buttonany
<button>MuiButtonanother library's component
div.flexa
<div>carrying the classflexdiv.flex.gap-2a
<div>carrying both classes.flexany element carrying
flexAll the classes named must be present; extras are ignored, so
div.flexmatchesclassName="flex items-center". The classes are plain literals you wrote — this is unrelated toclassNames, and works whether or not you configure a utility-class convention.Any way of writing the value is read, including through a helper —
cn,clsx,classNames,twMergeor your own, since the name of the call is never inspected:<div className="flex items-center" /> <div className={cn('flex', isActive && 'gap-2')} /> <div className={clsx({ flex: isRow })} /> // class as key <div className={`flex ${extra}`} />Where two declarations both match, the more specific one wins, so
div.flexis reported over a barediv.A declared invalid alternative is an error. The same relationship can also be inferred for components that declare nothing, by asking the connected client's model once per component at startup (MCP sampling) — an inferred one is only a warning, since a guess shouldn't carry a declaration's authority.
Inference is off unless
inferInvalidAlternativesturns it on, because those calls run on the client's model and are billed to whoever runs it. Turning it on is a good way to find candidates worth promoting to a real@invalidAlternativetag, which then costs nothing and upgrades the finding to an error. Declared tags never depend on any of this.
Superseded implementations
A deprecated usage pattern — a usage reproducing a prop combination the component's own
.storiesfile marks deprecated, either by an@deprecateddocblock or a story name saying so. The finding names the story, so the reader can go see what replaced it. A warning, not an error.An undocumented usage pattern — a prop combination no story demonstrates. This fires only for components that have stories: with none, the honest answer is "can't tell", and a warning built from no evidence is just noise. A warning, and a soft one.
Worked example: before / after
examples/synthetic-design-system/ is a small, fully public
design system (fictional tokens, one Button with stories) demonstrating the CSS-in-TS + utility
class split this MCP was built around — run it from inside there (cd examples/synthetic-design-system && node ../../dist/mcp/stdio.js) to try this yourself.
An agent about to write Button.css.ts hardcodes a color instead of looking it up:
// before — an agent invented a hex value
import { style } from '@vanilla-extract/css';
export const bad = style({ color: '#3b5bdb' });// validate({ "code": "..." }) response
{
"ok": false,
"findings": [
{
"rule": "hardcoded-literal",
"severity": "error",
"surface": "vanilla-extract",
"line": 2,
"column": 35,
"message": "\"color: #3b5bdb\" hardcodes a color value instead of using a design token. The closest token is \"color.accent\".",
"suggestion": "vars.color.accent",
},
],
}The agent applies the suggestion directly:
// after
import { style } from '@vanilla-extract/css';
import { vars } from '../theme.css';
export const good = style({ color: vars.color.accent });// validate(...) response
{ "ok": true, "findings": [] }Architecture
The design system model has no MCP dependency — everything under src/model/
(config loading, adapters, validate) is plain TypeScript that knows nothing about the protocol;
src/mcp/ is a thin layer that registers tools, calls the model, and formats the
result. That split is deliberate: the model is meant to be reusable by something other than an MCP
server later (a CLI, a lint rule), and a tool handler containing logic beyond formatting would be
a bug rather than a feature.
Exactly three adapters — tsx-adapter, styles-adapter, classname-adapter — one per concern.
All three are dispatchers: they contain no library-specific code of their own, only routing to
whichever implementation config.source names, so no particular styling system or component
format is baked into the thing that's supposed to be generic.
src/
model/
types.ts DesignSystem, ComponentContract, DesignToken, Finding
config.ts Zod schema + loadConfig(cwd) -> ResolvedConfig
design-system.ts createDesignSystem(config) -> DesignSystem: loads every configured
token and component source, builds the reverse index, and holds
the lookup/search API
validate.ts parses once with Babel, calls the two "validate" entry points below
reverse-index.ts value -> nearest-token lookup (color distance / numeric proximity)
flatten-object-literal.ts shared AST helper both token-reading implementations use
check-style-object.ts shared property-group check every styling implementation, and the
TSX adapter's inline style prop, run an object literal through
class-name-strings.ts reads the classes off a JSX className, whatever expression shape
it takes — knows no className convention
adapters/
styles-adapter.ts DISPATCHER — loadTokens(config) and validateStyles(ast, system,
config) each pick a branch on config.source and call into it
styles/
vanilla-extract.ts loadTokens() (createThemeContract/createGlobalTheme) and
validate() (style()/styleVariants()/recipe() calls)
object.ts loadTokens() for a plain JS/JSON file
classname-adapter.ts DISPATCHER — check(config, value) picks a branch on config.source
classnames/
tailwind.ts check() for arbitrary-value brackets
tsx-adapter.ts DISPATCHER for loadComponents(config), plus validateJsx(ast,
system, classNamesConfig): walks JSX elements, checks variant
props, invalid alternatives and story-derived patterns itself, and
delegates style/className checks to its two sibling adapters
components/
react-tsx.ts component contracts from project source files
npm-package.ts component contracts from an installed package's type declarations
stories.ts deprecated and documented prop shapes from a colocated .stories file
mcp/
server.ts tool registration
sampling.ts asks the client's model what a component is an alternative to
stdio.ts entrypoint: loadConfig(workspace) -> createDesignSystem -> serveStdio
index.ts public exportsAdding another styling system (Sass modules, styled-components) is a new file next to
styles/vanilla-extract.ts plus one branch in styles-adapter.ts — no concrete implementation
gets touched to add another, and neither does validate.ts or tsx-adapter.ts, which only ever
call the dispatcher. Same shape for another className convention next to classnames/tailwind.ts,
and for another component format next to components/react-tsx.ts.
Why the TSX adapter delegates instead of checking styles/classes itself. An inline JSX
style={{...}} prop needs the exact same governed-property check as a style-authoring call —
same properties, same token groups, same reverse index — so both run through the shared
check-style-object.ts rather than keeping a second copy of that logic. Utility-class checking is
unrelated (it's regex over strings, not object literals), so it's the className adapter's own
concern, reached through its dispatcher. validate.ts itself calls exactly two things —
validateStyles and validateJsx — because there are only two places in a file token violations
start from: a style-defining call, or a JSX element.
Why styles/classNames are opt-in config. Earlier this ran the vanilla-extract and Tailwind
checks unconditionally, which quietly contradicted the "nothing hardcoded" premise by assuming
every project uses both. Now each check takes its config field and does nothing when it's absent,
the same way a token check does nothing for a group with no tokens: no signal, no finding.
Why property→group is a map, not value-matching. Detecting a violation is structural — is this
governed property set to a literal, or to a reference into the token object? — rather than based
on whether the literal happens to match some token's value. padding: '8px' is wrong even when
8px equals a real token today, because it won't track that token if it changes. Value-matching is
used only to compute suggestion, once a violation is already established.
Why the group list isn't fixed. A real design system's token groups aren't knowable in advance
(some have shadow/motion, plenty don't; some split typography into fontSize/fontWeight/
lineHeight rather than one typography group), so a rule fires only when the configured token
source actually has tokens in that group.
Why width/height aren't governed properties. They're too overloaded — an icon's size and a
card's layout width use the same CSS property — to map onto one token group without a high
false-positive rate, so they're left unchecked.
Why token sources aren't merged by name. If a design system splits token shape (a vanilla-extract contract) from token values (a separate JSON file), those two sources produce separate token entries rather than one merged entry. Whoever writes the config decides which files to list; there's no cross-file name-matching to get wrong.
Known limitations
A
TemplateLiteralvalue is never flagged, even a hardcoded one (e.g.`0 0 0 3px ${theme.shadow}`) — it often mixes a real token reference with literal structure, and resolving that fully isn't worth the false-positive risk.A computed property key (
{ [someVar]: '#fff' }) is checked using the variable's name, not its runtime value — harmless unless a local variable happens to share a name with a governed CSS property.The
react-tsxcomponent source needs your workspace's own dependencies installed (npm install/pnpm installrun there). It resolves types through the real TypeScript checker, so withoutreact/@types/reactand anything a component imports actually present, it detects zero components — which looks like a config problem when it's an install problem.Scanning a large package with
names: "*"is slow. A full icon library (~3,400 components) takes several seconds at startup. Naming the specific components you use keeps it instant.Inferred invalid alternatives are off by default and need a client that supports sampling, and the 2026-07-28 protocol revision removed the push-style sampling this uses. Where it's unavailable the inference is skipped;
@invalidAlternativedeclarations are unaffected and keep working.@modelcontextprotocol/serverv2 (this depends on it per the SDK's own migration guidance away from v1's@modelcontextprotocol/sdk) reached2.0.0a few weeks before this was written. It's maintained by the official MCP org, but has far less real-world mileage than the v1 SDK.
Development
git clone https://github.com/avcs06/weave-design-system-mcp.git
cd weave-design-system-mcp
npm install
npm run buildPoint a client at a local build with node ./dist/mcp/stdio.js /path/to/your-workspace in place
of the npx invocation above.
npm run format:check # prettier
npm run typecheck # tsc --noEmit
npm test # vitest
npm run build # tsc -> dist/CI runs all four on Node 22 and 24, then starts the built server against the example design system to confirm the published entrypoint actually loads one.
Runs against examples/synthetic-design-system/ — the same
fixture the worked example above uses, not a second copy that can drift from it — plus unit tests
per implementation for cases the example doesn't cover (styles/vanilla-extract.test.ts,
styles/object.test.ts, components/stories.test.ts) and dispatcher-level tests
(styles-adapter.test.ts, classname-adapter.test.ts, tsx-adapter.test.ts) covering how an
unrecognized source is reported. createDesignSystem(config) takes an in-memory config object,
so tests need no designsystem.config.json on disk.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Serves your design system and coding standards to coding agents, so they stop guessing.
Live React design-system APIs, patterns, and code validation so AI agents build real UI, not slop.
UI design from prompts, screenshots, and URLs for AI coding agents and theme tokens.
Design intelligence for coding agents: audits, design systems, and a taste profile agents consult.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides AI assistants with access to a production-ready design system including Tailwind CSS component patterns, style guides (colors, typography, spacing), and Web Components specifications for consistent UI development.19MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to search, understand, and generate code for design system components by syncing and indexing a component library.-
- AlicenseNot gradedqualityCmaintenanceEnables Cursor and Claude Code to read design tokens and rules, and validate component code against a design system before generating UI.MIT
- AlicenseAqualityBmaintenanceProvides deterministic, read-only design knowledge for AI coding agents to help them choose visual directions, plan UI states, and compose design tokens, all without network access.6294MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/avcs06/weave-design-system-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server