← Neumo Design System docs

Neumo MCP Server — Setup Guide

How to install, register, and use @kofile/neumo-mcp-server — the Model Context Protocol (MCP) server that exposes the Neumo / GDS design system (components, tokens, usage examples, and audit tools) to AI coding assistants like Claude Code, Cursor, and VS Code.

In practice it acts as a guardrail: when an assistant writes UI code, it can ask the server for the real component API, the real token for a color, or whether a stylesheet drifts from the design system — instead of guessing.

No hosting, no API key, no LLM calls. The server runs locally over stdio and only reads bundled design-system data. Any tool use bills to your AI session, never the publisher's.


Prerequisites


0. Authenticate to npm (private package)

@kofile/neumo-mcp-server is private, so both npm install and npx need an npm access token first — otherwise the server fails to launch with a 404/401. Create a Read-only token at npmjs.com (avatar → Access Tokens, with @kofile org membership), then:

npm config set //registry.npmjs.org/:_authToken=PASTE_TOKEN_HERE

Verify with npm view @kofile/neumo-mcp-server version. Full walkthrough and troubleshooting: the npm access token guide.


1. Install

There are two supported ways to run the server. Pick one.

Add it as a dev dependency so the version is pinned in package.json and the setup works offline:

npm install --save-dev @kofile/neumo-mcp-server

This installs the prebuilt server at node_modules/@kofile/neumo-mcp-server/dist/index.js and a neumo-mcp-server bin in node_modules/.bin/.

Option B — Run on demand with npx (no install)

Skip installing and let your MCP client fetch it when launched. Useful for a quick try or for repos that don't want the dependency. See the config in the next section.

Which to choose? Option A is deterministic (pinned version, no network at launch) and is the right default for a team repo. Option B is zero-footprint but re-resolves the package and needs network access.


2. Register with your AI client

MCP clients read a JSON config that tells them how to launch each server. The file location depends on the client:

Client Config location
Claude Code .mcp.json in your project root
Cursor .cursor/mcp.json (or Settings → MCP)
Claude Desktop claude_desktop_config.json

If you installed it (Option A)

Point the client at the local build. Create .mcp.json in your project root:

{
  "mcpServers": {
    "neumo-ds": {
      "command": "node",
      "args": ["./node_modules/@kofile/neumo-mcp-server/dist/index.js"]
    }
  }
}

If you prefer npx (Option B)

{
  "mcpServers": {
    "neumo-ds": {
      "command": "npx",
      "args": ["-y", "@kofile/neumo-mcp-server@latest"]
    }
  }
}

Pin a version for reproducibility (e.g. @kofile/neumo-mcp-server@0.1.0). The bundled design-system data is a snapshot from publish time, so the server's answers track the version you install.

.mcp.json is safe to commit — teammates get the server automatically (after a one-time trust prompt in Claude Code, since project-scoped servers require approval).


3. Verify the connection

  1. Reload / restart your AI client so it picks up the new config.
  2. In Claude Code, run /mcp — you should see neumo-ds listed and connected.
  3. Ask it something like "list the Neumo components" — you should get a list back.

Quick command-line smoke test (confirms the server boots over stdio):

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
  | node ./node_modules/@kofile/neumo-mcp-server/dist/index.js

Expected: a line @kofile/neumo-mcp-server running on stdio followed by a JSON result advertising resources and tools capabilities.


Testing the tools

Three ways to exercise the tools, from everyday use to CI.

A. Interactively, through your AI client

The way you'll use it day to day. Confirm neumo-ds is connected (/mcp in Claude Code), then prompt something that triggers each tool — see the Prompt line under every tool in the Tool catalog. For example:

"List the Neumo components." "Show the API for neumo-button." "Check contrast of #562c9e on #f7f7f7."

B. Raw JSON-RPC by hand (debugging one tool)

To poke a single tool at the protocol level, pipe line-delimited JSON-RPC into the binary. Send initialize, the notifications/initialized notification, then a tools/call (or tools/list to inspect schemas):

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"check-contrast","arguments":{"foreground":"#000","background":"#fff"}}}' \
| node ./node_modules/@kofile/neumo-mcp-server/dist/index.js

The response with "id":2 contains the tool result under result.content[0].text.

C. Automated smoke test (optional, for CI)

A small script that performs the handshake and calls every tool with sample arguments makes a good CI gate — run it whenever you bump the @kofile/neumo-mcp-server version to catch a tool that changed shape or broke. The repo doesn't ship one yet; the JSON-RPC pattern in B is the building block.


Resources

Read-only blobs the client can pull on demand:

URI Description
neumo://components/manifest Full Custom Elements Manifest (every component's props, events, slots)
neumo://tokens/global Global / primitive design tokens
neumo://tokens/light Light-mode tokens
neumo://tokens/dark Dark-mode tokens

Tool catalog

Fourteen tools, grouped by purpose. Each entry lists its inputs, what it returns, a natural-language prompt that triggers it in an AI client, and an example response.

Quick reference

Tool Purpose Required inputs
list-components List all public components (none)
search-components Fuzzy-search components query
get-component Full API for one component tagName
get-usage-example HTML snippet for a component tagName
get-tokens Search/filter design tokens (none — all optional)
suggest-token Nearest token for a hex color hexValue
remap-variables Map CSS custom props → tokens css
audit-spacing Audit CSS spacing vs scale css
audit-typography Audit CSS typography vs scale css
audit-tokens Audit CSS colors vs token catalog css
check-contrast WCAG ratio for two hex colors foreground, background
audit-a11y Scan HTML/JSX for WCAG 2.2 violations markup
score-alignment 0–100 alignment score from audit counts (none — all optional)
find-component-replacements Find off-system elements to swap for Neumo files or code

Components

list-components

List every public Neumo web component with its tag name and description.

[
  { "tagName": "neumo-accordion-content", "description": "Expandable content panel for an accordion item. Controlled by neumo-accordion-item — do not use standalone." },
  { "tagName": "neumo-button", "description": "`<neumo-button>` — design system button. …" }
]

search-components

Fuzzy-search components by any term — matches tag name, description, slot names, event names, and attribute names. Returns a ranked list with match details.

[
  {
    "tagName": "neumo-input",
    "description": "Composed Input component that combines Input field with Addons and Icons. …",
    "score": 10,
    "matchedOn": ["property description: type"]
  }
]

get-component

Full details for a single component: props, events, slots, and CSS parts.

{
  "kind": "class",
  "description": "`<neumo-button>` — GovOS design system button. … Events: neumo-button-click …"
}

get-usage-example

A ready-to-paste HTML snippet with realistic attribute values.

{
  "tagName": "neumo-button",
  "variant": "default",
  "description": "Primary button",
  "html": "<neumo-button variant=\"primary\">Save changes</neumo-button>"
}

Tokens

get-tokens

Search and filter Neumo design tokens (sourced from Style Dictionary output).

[
  { "name": "color.primary.700", "cssVar": "--color-primary-700", "value": "{primitive.purple.700}", "type": "color", "source": "global" }
]

suggest-token

Find the closest token for any hex color, with the RGB delta and a recommendation.

{
  "input": "#6640c2",
  "closest": { "token": "--primitive-purple-600", "value": "#7c4ccd", "delta": 27.37 },
  "nearMatches": [{ "token": "--primitive-purple-700", "value": "#562c9e", "delta": 44.18 }],
  "recommendation": "create-token"
}

remap-variables

Map every custom-property value in a CSS block to the nearest token, and emit a ready-to-paste :root migration snippet.

{
  "variables": [
    { "name": "--brand", "value": "#562c9e", "neumoToken": "--primitive-purple-700", "delta": 0, "recommendation": "direct-map" },
    { "name": "--gap", "value": "16px", "neumoToken": "--primitive-16", "delta": 0, "recommendation": "direct-map" }
  ],
  "migrationSnippet": ":root {\n  --brand: var(--primitive-purple-700); /* was #562c9e, direct-map */\n  --gap: var(--primitive-16); /* was 16px, direct-map */\n}"
}

Audits

audit-spacing

Audit CSS spacing/sizing declarations against the Neumo spacing scale (and border-radius against the radius scale). Splits shorthands, handles rem/em and var() tokens, and skips calc()/percentages/keywords.

{
  "findings": [
    { "property": "padding", "value": "16px", "bucket": "on-scale", "nearestToken": "--spacing-md", "delta": 0 },
    { "property": "margin", "value": "13px", "bucket": "off-scale-near", "nearestToken": "--spacing-sm", "delta": 1 },
    { "property": "gap", "value": "var(--spacing-md)", "bucket": "uses-token" }
  ]
}

audit-typography

Audit CSS typography (font-family, font-size, font-weight, line-height, letter-spacing, and the font shorthand) against the Neumo type scale.

{
  "findings": [
    { "property": "font-size", "value": "16px", "bucket": "on-scale", "nearestToken": "--fontSize-md", "delta": 0 },
    { "property": "font-weight", "value": "600", "bucket": "on-scale", "nearestToken": "--fontWeights-semibold", "delta": 0 },
    { "property": "line-height", "value": "1.5", "bucket": "off-scale-far", "nearestToken": null }
  ]
}

audit-tokens

Audit CSS color declarations against the Neumo color-token catalog. Scans color, background, border, outline, fill, stroke, box-shadow, etc.; extracts colors from shorthands; understands hex (3/6/8-digit), rgb()/rgba(), hsl()/hsla(), and var() token usage; skips keywords like transparent/currentColor.

{
  "findings": [
    { "property": "color", "value": "#6741c3", "bucket": "near-match", "nearestToken": "--color-primary-600", "delta": 0.6 },
    { "property": "background", "value": "#fff", "bucket": "no-match", "nearestToken": null, "delta": null }
  ],
  "counts": { "total": 2, "matched": 0, "nearMatched": 1 }
}

check-contrast

WCAG 2.1 contrast ratio between two hex colors, with pass/fail at AA and AAA for normal and large text.

{
  "foreground": "#562c9e",
  "background": "#f7f7f7",
  "ratio": 8.71,
  "AA": { "normal": "pass", "large": "pass" },
  "AAA": { "normal": "pass", "large": "pass" }
}

audit-a11y

Scan HTML or JSX markup for WCAG 2.2 violations. Runs axe-core over a jsdom parse for structural rules (alt text, accessible names, roles, labels), plus static checks for color-contrast (1.4.3, cross-referenced with check-contrast on inline styles) and target-size (2.5.8).

{
  "violations": [
    { "sc": "4.1.2", "rule": "button-name", "severity": "critical", "count": 1,
      "occurrences": [{ "target": "button", "summary": "Element has no accessible name" }],
      "remediation": "Buttons must have discernible text (…helpUrl)" }
  ],
  "summary": "1 occurrence across 1 rule: button-name (4.1.2)×1.",
  "totalViolations": 1
}

score-alignment

Combine audit results into a single 0–100 alignment score. Accepts partial input — pass whichever audits you have run.

{
  "overall": 90,
  "breakdown": {
    "spacing": { "score": 80, "total": 10, "matched": 8, "weight": 25 },
    "typography": { "score": 100, "total": 5, "matched": 5, "weight": 25 }
  },
  "summary": "Your codebase is strongly aligned with the Neumo design system at 90%. …",
  "recommendations": ["Alignment is healthy. Consider auditing near-matches in spacing tokens …"]
}

Migration

find-component-replacements

Scan React (JSX/TSX) source for off-system elements that could be swapped for Neumo @kofile/gds-react components. AST-parses the source, matches intrinsic HTML elements (and ARIA-role patterns like role="tablist") to their Neumo equivalents, and estimates swap effort. Validates/enriches targets against the component manifest.

{
  "findings": [
    { "file": "X.tsx", "line": 1, "candidate": "<button>", "suggestedComponent": "Button",
      "package": "@kofile/gds-react", "effort": "medium",
      "rationale": "<button> → <Button> from @kofile/gds-react — props-only swap; wires an event handler; Button accepts: variant, size, disabled, loading" }
  ],
  "counts": { "total": 1, "byEffort": { "simple": 0, "medium": 1, "complex": 0 } }
}

Common workflows

Generate a component correctly the first time

"Add a Neumo select with a label and an error message."

The assistant calls search-componentsget-component (neumo-select) → get-usage-example, then writes markup using the real props, slots, and events instead of guessing.

Replace a hardcoded color with a token

"Is #6640c2 a Neumo token?"

suggest-token returns the nearest token and the RGB delta, and recommends whether to reuse it or create a new one.

Migrate a stylesheet's custom properties to tokens

"Map the :root variables in this file to Neumo tokens."

remap-variables analyzes the CSS and returns per-variable mappings plus a ready-to-paste :root migration snippet.

Score a component's design-system alignment

Run audit-spacing and audit-typography on the component's CSS, then feed the counts into score-alignment for a 0–100 score and prioritized fixes.

Audit color contrast across a real page

check-contrast compares two hex values — it has no stylesheet mode, and a stylesheet rarely pairs color with the background it actually renders on. The reliable workflow is to read computed styles from the running app:

  1. Load the page in a browser / headless driver (e.g. Playwright).
  2. For each visible text node, read its computed color and walk up the ancestor chain for the first non-transparent background-color.
  3. De-duplicate to unique foreground|background pairs.
  4. Run each pair through check-contrast.

This catches inherited backgrounds and resolves nested var()s to their rendered hex automatically. Caveats: it only covers what's rendered on screen (states like hover/focus and other breakpoints need their own pass), and gradients / semi-transparent text aren't flat colors — pick the worst-case effective color for those manually.

A quick single-pair check is just:

"Check contrast of #562c9e text on #f7f7f7." → ratio 8.71, AA & AAA pass.


How it loads data

On startup the server loads the component manifest + token JSON from two places, in order:

  1. Bundled data at data/, resolved relative to the server module — this is what makes installed / npx usage work from any directory.
  2. Live monorepo files at process.cwd() (packages/wc/custom-elements.json, packages/foundations/tokens/{Global,Light,Dark}.json) — used when running from a clone of the design-system monorepo, so you always get current files.

Bundled data wins when present. The bundle is a snapshot produced at build/publish time, so the installed package's answers reflect the design system at the version you installed. Bump the package to get newer data.


Troubleshooting

Symptom Fix
Server doesn't appear in /mcp Reload the client after editing config. Confirm .mcp.json is valid JSON and in the project root.
Claude Code shows it but it won't connect Run the smoke test. If node can't find the file, check the path in args.
"Command not found" / wrong Node Ensure Node ≥ 18 is on PATH for the client's environment.
Project server never prompts for trust Project-scoped MCP servers require a one-time approval — re-open the project or check client trust settings.
Answers look stale / missing a new component The bundled data is a publish-time snapshot — npm update @kofile/neumo-mcp-server (or bump the pinned version).
Component tools say data is missing (clone usage) In a monorepo clone, regenerate custom-elements.json (nx run wc:cem) and rebuild.

Server version documented: @kofile/neumo-mcp-server@0.1.0.