Sekura Design MCP
# Sekura Design MCP
A dockerized [Model Context Protocol](https://modelcontextprotocol.io) server that
serves the complete **Sekura Design System** — tokens, components, layouts, UX
patterns, accessibility contract and paste-ready code — to any MCP-capable tool.
Agents and developers can inspect the same tokens, native markup and interaction contracts.
Application behavior still needs implementation and accessibility verification.
```
67 components · 15 foundations · 15 UX patterns · 9 layout recipes
120 semantic tokens · 4 themes · 3 densities · 8 target frameworks
Build-derived counts and support: sample/assets/component-manifest.json
```
The human-readable specification is [`DESIGN.md`](./DESIGN.md), and there is an
101-page [documentation site](./sample) — generated from the same data — that
explains it with live demos, a full colour guide and worked examples.
---
## Integration and migration
The [support matrix](./sample/support.html) distinguishes CSS, native behavior,
controllers, and application-owned actions. The [workbench](./sample/workbench.html)
compares every control size and density and demonstrates pending, failure, retry,
cancellation and real undo. Example data stays local; simulated remote actions are labelled.
Generated framework code is an **editable reference recipe**, preserving the native
markup, unique IDs and controller cleanup. Except for the dedicated React Button,
these are not general-purpose prop-driven components. Read the emitted API before
passing specification props. Keep one owner for controller state and wire your own
permissions, network requests, persistence and navigation.
Version 2 corrects the accordion Tab sequence and replaces the old generated wrappers.
See [migration notes and the second design review](./DESIGN-REVIEW.md) and the
[original audit](./DESIGN-AUDIT.md). No package or site is published by a local build.
## Quickest start
```bash
./run.sh start-build
```
Builds everything — TypeScript, contrast audit, CSS lint, stylesheets, the
documentation site and the Docker image — then serves MCP and the documentation
on `:8080`, with the docs at `/docs/`.
```
./run.sh build Compile, run gates, emit CSS, build the docs and the image
./run.sh start Start the MCP server and the documentation site
./run.sh start-build Build, then start
./run.sh restart Stop, then start
./run.sh stop Stop everything
./run.sh logs [target] Follow server logs
./run.sh status What is running, plus a live contrast-audit check
./run.sh verify Run every gate without starting anything
./run.sh clean [--all] Remove build output, container and image
./run.sh help
```
The MCP server runs in Docker when Docker is available and falls back to a local
Node process when it is not, so the script behaves the same either way. The
published port and the image names are overridable: `SEKURA_PORT`,
`SEKURA_IMAGE`, `SEKURA_CONTAINER`.
`./run.sh status` is a real check rather than a liveness ping — `/health`
re-runs the full contrast audit, so a server quietly using a broken palette
reports it.
---
## The documentation site
```bash
./run.sh start-build # then open http://localhost:4173
```
A 101-page documentation site — explanations, a full colour guide, a type
specimen, live demos, a complete component reference and nine worked examples.
**It is generated from the design system's own data**, so the colour guide shows
genuinely audited contrast values and the component pages show the same
specification the MCP server serves. Browser regression tests check that the documented interactions work.
| | |
|---|---|
| `color.html` | Every ramp step with its contrast against white *and* black, all 120 semantic tokens in four themes, and the full 86-pairing contrast contract with measured ratios |
| `dark-mode.html` | The elevation inversion, demonstrated with the same markup under both themes side by side — plus the nine things that break silently |
| `layout.html` | Flex-first, with **resizable** demos that reflow on container width |
| `tokens.html` | Filterable reference for every token |
| `component-*.html` | One page per component: anatomy, states, dark-mode note, full keyboard and ARIA contract |
See [`sample/README.md`](./sample/README.md) for what to try.
---
## Quick start
### Docker (recommended)
```bash
docker compose up -d
curl http://localhost:8080/health
```
Or without compose:
```bash
docker build -t sekura-design-mcp .
docker run -d -p 8080:8080 --name sekura sekura-design-mcp
```
The build runs the contrast audit and the full smoke test. **An image whose palette
breaks a declared WCAG pairing does not get built.**
### Local
```bash
npm install
npm run build
npm start # stdio
npm run start:http # HTTP on :8080
```
---
## Connecting a client
### Claude Code / Claude Desktop — HTTP
```json
{
"mcpServers": {
"sekura-design": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}
```
### Claude Code — one-liner
```bash
claude mcp add --transport http sekura-design http://localhost:8080/mcp
```
### OpenAI Codex
Codex reads MCP servers from `~/.codex/config.toml`. Add a `[mcp_servers.<name>]`
table:
```toml
# ~/.codex/config.toml
[mcp_servers.sekura-design]
command = "docker"
args = ["run", "-i", "--rm", "-e", "SEKURA_MCP_TRANSPORT=stdio", "sekura-design-mcp:2.0.0"]
# The first call builds a 5,900-line overview, so allow a little headroom.
startup_timeout_sec = 30
tool_timeout_sec = 60
```
Without Docker, point it at the built server directly:
```toml
[mcp_servers.sekura-design]
command = "node"
args = ["/absolute/path/to/SekuraDesignMCP/dist/index.js"]
```
Recent Codex versions can add it for you:
```bash
codex mcp add sekura-design -- docker run -i --rm \
-e SEKURA_MCP_TRANSPORT=stdio sekura-design-mcp:2.0.0
codex mcp list # confirm it registered
```
Then just ask for work in design-system terms — Codex will call the tools:
```
> Build a settings page using the Sekura design system. Check the dark mode
> foundation before you write any CSS, and validate the markup when you're done.
```
**Note on transport.** stdio is the broadly supported path and is what the
examples above use. Codex's support for remote `url`-based MCP servers is newer
and has moved between releases — check `codex mcp --help` for your version before
relying on the HTTP endpoint. Everything the server exposes is available over
stdio, so nothing is lost.
**Getting good results.** The server's `instructions` already tell a client where
to start, but these help:
- Ask it to call `get_overview` first on a new task.
- For anything visual, `get_foundation({ id: "dark-mode" })` before writing CSS
prevents the nine most common dark-mode defects.
- Ask it to finish with `validate_markup` — the linter catches missing accessible
names and hard-coded colours that a model will otherwise leave behind.
### stdio (client spawns the container)
```json
{
"mcpServers": {
"sekura-design": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "SEKURA_MCP_TRANSPORT=stdio",
"sekura-design-mcp:2.0.0"]
}
}
}
```
### stdio (local install)
```json
{
"mcpServers": {
"sekura-design": {
"command": "node",
"args": ["/absolute/path/to/SekuraDesignMCP/dist/index.js"]
}
}
}
```
---
## Tools
| Tool | Returns |
|---|---|
| `get_overview` | **Start here.** The map of everything, with the call needed to fetch each part. |
| `search` | Full-text search across components, foundations, patterns, layouts and tokens. |
| `get_foundation` | The reasoning: colour, dark mode, responsive layout, accessibility, typography, motion, i18n, theming… |
| `list_components` | The catalogue, filterable by category and maturity. |
| `get_component` | Full spec: anatomy, variants, sizes, states, props, tokens, dark-mode behaviour, complete accessibility contract, do/don't. |
| `get_component_code` | Paste-ready code in `html`, `css`, `react`, `vue`, `svelte`, `angular`, `blazor` or `web-component`. |
| `get_layout` | A complete page blueprint with markup and CSS. |
| `get_pattern` | A recurring UX problem, its solution, and the anti-patterns. |
| `get_tokens` | Resolved token values, showing all four themes side by side. |
| `export_tokens` | CSS, SCSS, W3C DTCG, Tailwind v3/v4, JS, TS, Swift, Android XML, Figma. |
| `get_primitives` | The raw colour ramps behind the semantic layer. |
| `suggest_token` | Describe an intent in words, get the right token with values per theme and *why*. |
| `check_contrast` | WCAG verdict for any pair — accepts hex or token names, resolves per theme, composites translucency. |
| `audit_theme` | Every declared pairing across every theme. The build gate. |
| `validate_markup` | Lints HTML/CSS for the failures that actually ship. |
| `get_setup` | HTML scaffold, pre-paint theme script, reset, utilities, prose, theme control. |
| `get_stylesheet` | The entire stylesheet as one file. |
### Resources
`sekura://tokens/css` · `sekura://tokens/dtcg` · `sekura://foundations/principles` ·
`sekura://foundations/dark-mode`
### Prompts
`build-page` · `review-ui` · `implement-dark-mode`
---
## HTTP endpoints
Beyond MCP, the container serves plain HTTP so a build step can consume tokens
without speaking the protocol:
| Endpoint | Purpose |
|---|---|
| `POST /mcp` | MCP streamable HTTP endpoint |
| `GET /health` | Health check — **re-runs the contrast audit**, so a container serving a broken palette reports unhealthy |
| `GET /tokens.css` | CSS custom properties, all themes and densities |
| `GET /tokens.json` | W3C Design Tokens JSON |
---
## What makes this specification unusual
**Dark mode is specified, not derived.** Every component documents what changes in
dark mode and why. The system encodes the rules most implementations get wrong:
floating surfaces get *lighter* as they rise while recessed surfaces get *darker*;
saturated fills step *up* the ramp so their labels flip to dark; borders go *darker*
on dark, not lighter; and elevation is two tokens because a drop shadow is nearly
invisible against a dark page.
`get_foundation({ id: "dark-mode" })` lists the nine failures that pass a design
review and break in production — the unstyleable Chrome autofill background, SVG
chevrons baked into data URIs, WebKit's search clear button, scrims that are too
weak on dark, and so on.
**The contrast contract is machine-verified.** 86 declared pairings × 4 themes = 344
checks, run on every build and by the container's health check. Two neutral steps
are pinned by contrast rather than by eye: `neutral-400` is the lightest grey
clearing 3:1 on white, and `neutral-500` the lightest clearing 4.5:1 on the subtle
surface. Moving either lighter breaks a promise, and the audit catches it.
Sekura exceeds WCAG in three places where products commonly fail: placeholder text
and tertiary text are both held to full body contrast, and switch and progress
tracks are treated as meaningful graphics rather than decoration.
**Layout is flex-first.** Composition primitives wrap rather than overflow, children
declare `flex` explicitly, text-bearing flex children set `min-inline-size: 0`, and
widths are `flex-basis` (an ideal) rather than `width` (a demand). Most layouts
therefore respond to their **container** and need no media query — including the
two-column `sidebar-layout`, which stacks purely through flex wrapping.
---
## Example session
```
> get_overview
→ the full map
> get_foundation({ id: "dark-mode" })
→ the nine silent failures, the elevation inversion, theme-switching rules
> get_layout({ id: "list-page" })
→ regions, responsive strategy, a11y obligations, markup + CSS
> get_component({ id: "table" })
→ 6 variants, 6 states, full keyboard model, and why row separators
go DARKER in dark mode
> get_component_code({ id: "table", framework: "react" })
→ typed component forwarding the required ARIA attributes
> suggest_token({ intent: "border around a card in dark mode" })
→ --sk-color-border-default, values per theme, and why
> check_contrast({ foreground: "#8590a3", background: "#ffffff", use: "ui-component" })
→ 3.22:1 — AA pass for control boundaries
> validate_markup({ markup: "<button><svg/></button>" })
→ ❌ button-accessible-name, with the fix and the WCAG criterion
> audit_theme
→ 344/344 pairings satisfied across all four themes
```
---
## CI and releases
Two workflows in `.github/workflows/`.
**`ci.yml`** runs on every push and pull request. Each step is a gate that exits
non-zero, so a change that breaks a promise cannot merge green:
| Gate | Checks |
|---|---|
| `check:version` | No version literal has drifted from `package.json` |
| `check:deps` | No pre-release dependencies; every Node reference an LTS line |
| `check:env` | `.env.example` documents every setting, and only real ones |
| `audit:contrast` | 344 checks — 86 declared pairings across four themes |
| `lint:css` | Structure, tokens only, no physical properties |
| `smoke` | Every MCP tool, component, framework and export format |
| `test:color` | Colour maths against WCAG reference values |
| `test:urls` | Path prefixes and proxy headers resolve to reachable URLs |
| `test:codegen` | React, Angular and Web Component typechecks; Vue/Svelte compilation; Angular templates |
| `test:blazor` | All generated Razor components compile with .NET 10 |
| `test:design` | Example workflows, control alignment, generated React mounts, lifecycle and mobile geometry |
| `test:behaviours` | Real key presses in a browser: focus, ARIA, Escape, inert |
| `site:publish` | Static bundle is portable — nothing root-absolute |
| `test:errors` | Every failing tool call is marked, coded and actionable |
| `test:announce` | Accessible names exist and are distinct within a region |
| `test:rtl` | Nothing clipped in either direction, at four widths, including 320px, across 14 pages |
| `verify:sample` | Dangling references, broken links, markup lint |
| `test:a11y` | axe-core, WCAG 2.2 AA, both themes |
| Docker | Image builds, `/health` re-runs the contrast audit inside it |
**`release.yml`** runs on a `v*` tag and publishes artifacts.
```bash
npm version minor # bumps package.json; everything else derives from it
git push --follow-tags
```
The workflow **refuses to release if the tag disagrees with `package.json`** —
otherwise you ship artifacts labelled one version and containing another. It then
runs the full gate chain again (a release cannot skip checks) and publishes:
| Artifact | Use |
|---|---|
| `sekura-<v>.css` | The whole stylesheet, one file |
| `tokens-<v>.css` | Custom properties only, all themes and densities |
| `tokens-<v>.dtcg.json` | W3C Design Tokens format |
| `sekura-behaviours-<v>.iife.min.js` | Drop-in `<script>`, global `Sekura` |
| `sekura-behaviours-<v>.esm.min.js` | ES module for bundlers |
| `sekura-css-<v>.zip` | Per-component CSS, Tailwind, Swift, Android |
| `sekura-docs-<v>.zip` | The documentation site, hostable anywhere |
| `SHA256SUMS.txt` | Checksums |
Plus a multi-arch image to GHCR, tagged `1.2.3`, `1.2`, `1` and `latest`:
```bash
docker run -d -p 8080:8080 ghcr.io/mictsi/sekuradesignmcp:2.0.0
```
and the documentation site to GitHub Pages.
### Versioning
`package.json` is the single source of truth. The server, docs site, behaviours
bundle and container tag all derive from it — `check:version` fails the build if
a literal creeps back in.
A **major** bump is required for anything that breaks consumers silently:
renaming a semantic token or component class, changing a keyboard contract,
removing an MCP tool, or changing the focus ring or spacing scale. Changing a
*primitive* value is a minor bump, because the semantic layer absorbs it and the
contrast audit proves nothing regressed. See [`CHANGELOG.md`](./CHANGELOG.md).
---
## Development
```bash
npm run verify # full checks; Node/npm, Chromium, Firefox, WebKit and .NET 10 SDK required
npm run build # compile
npm run check:version # no version literal has drifted
npm run check:deps # dependency policy: stable releases, Node LTS only
npm run check:env # .env.example matches what the code actually reads
npm run test:color # colour maths vs WCAG reference values
npm run test:urls # URL generation under prefixes and reverse proxies
npm run audit:contrast # 344 contrast checks — build gate
npm run lint:css # structural CSS lint over all 67 stylesheets
npm run smoke # checks every tool, component and export
npm run emit:css # write dist-css/ and the dependency/event manifest
npm run site:build # regenerate the 101-page documentation site
npm run verify:sample # lint every page against the design system itself
```
`lint:css` exists because component CSS is a string as far as the TypeScript
compiler is concerned. It checks for unbalanced braces, selectors running into
at-rules, hard-coded colours, unknown tokens and physical properties — it was
added after building the sample surfaced an invalid selector in the dialog
stylesheet that had shipped unnoticed.
`npm run emit:css` produces standalone artefacts for consuming Sekura as plain
files: `sekura.css` (everything), `tokens.css`, `tokens.dtcg.json`,
`tailwind.config.js`, `SekuraColor.swift`, `android-resources.xml`,
`tokens.figma.json`, per-component CSS, and `component-manifest.json` with dependency and behavior metadata.
### Layout
```
src/
├── index.ts entry, transport selection
├── server.ts MCP server: 17 tools, 4 resources, 3 prompts
├── http.ts streamable HTTP transport, health, plain-HTTP token endpoints
├── site/ documentation site generator
│ ├── shell.ts page shell, navigation, reusable doc blocks
│ └── pages.ts every page, built from the data below
├── data/
│ ├── primitives.ts ramps, scales, type scale, elevation, motion, breakpoints
│ ├── semantic.ts 120 tokens × 4 themes + the contrast contract
│ ├── tokens.ts resolution and audit
│ ├── base-css.ts reset, utilities, prose, theme runtime
│ ├── foundations.ts 15 foundation documents
│ ├── layouts.ts 9 page recipes
│ ├── patterns.ts 15 UX patterns
│ └── components/ 67 component specifications
└── lib/
├── color.ts WCAG luminance, contrast, compositing
├── exporters.ts 11 output formats
├── codegen.ts 8 target frameworks
├── validate.ts markup linting
├── markdown.ts minimal renderer for the foundation documents
├── suggest.ts intent → token
└── search.ts weighted full-text search
```
### Settings
Every setting, with what it does and when you would change it, is documented in
[`.env.example`](./.env.example) — including the path variables this section
used to omit. See [Configuration](#configuration) for how to use the file.
A second list here would drift from that one, which is the failure
`npm run check:env` exists to prevent.
---
## Notes
The HTTP server is **stateless** — a fresh server per request, no sessions to lose.
The design system is read-only at runtime, so the container runs unprivileged with a
read-only filesystem and all capabilities dropped.
## Supported runtimes
Node **22 (Jod)** and **24 (Krypton)** — the two Node LTS lines currently in
support. The container builds on 24; CI runs every gate on both, so
`engines: >=22` is a verified claim rather than an aspiration.
Odd-numbered Node majors are never promoted to LTS, so a dependency bot
offering `node:25-alpine` is offering a runtime that reaches end-of-life in
months. `npm run check:deps` fails the build if one lands.
### What the build actually needs
The **container build needs Docker and nothing else.** The Dockerfile installs
its own dependencies, compiles inside the image, and runs its own gates there,
so the host toolchain is never involved:
```bash
./run.sh build --image # or plain: docker build -t sekura-design-mcp .
```
Verified on a clean `git archive` with no `node_modules`, no `dist`, no `tsc`
and no browser on the host.
Plain `./run.sh build` does more than that: it also compiles and verifies on the
host *before* building the image, which is why it wants TypeScript and
Playwright. Only two steps need a browser — the behaviour contracts and the RTL
regression — and they are skippable:
| Command | Needs |
|---|---|
| `./run.sh build --image` | Docker |
| `./run.sh build --no-browser` | Node and npm |
| `./run.sh build` | Node, npm, and a Playwright browser |
`--no-browser` genuinely avoids Playwright rather than tolerating its absence:
with `PLAYWRIGHT_BROWSERS_PATH` pointed at nothing, it exits 0 while the full
build exits 1 at the behaviour step.
Install the browser once with `node node_modules/playwright-core/cli.js install chromium firefox webkit` if you want the
full local build.
## Configuration
Every container setting lives in one file.
```bash
cp .env.example .env # then edit .env, which is gitignored
```
`docker compose up`, `./run.sh start` and `docker run --env-file .env` all read
it, so there is a single place to look when a deployment misbehaves. Anything
already exported wins, so `SEKURA_PORT=9000 ./run.sh start` still works for a
one-off.
`npm run check:env` fails the build if the code reads a setting `.env.example`
does not document, or documents one nothing reads — a setting someone will set,
restart for, and watch do nothing is worse than an undocumented one. It also
rejects quoted values, because `docker run --env-file` keeps the quotes as part
of the value while Compose strips them.
## Publishing under a path
**The MCP endpoint, the health check and the documentation site are served from
one port under one path prefix.** There is no second port and no second server:
```
http://host:8080/<app_path>/mcp MCP, POST
http://host:8080/<app_path>/health health
http://host:8080/<app_path>/docs/ documentation site
```
The image serves at the root by default. To publish it under a path on an
existing web server — `https://example.com/design-system/` — you need to know
which of two things your proxy does, because they need different settings.
| Variable | Answers |
|---|---|
| `SEKURA_BASE_PATH` | *Where does this process listen?* |
| `SEKURA_EXTERNAL_URL` | *What does the outside world see?* |
They are equal in the simple case and different in the common one, which is why
they are two variables rather than one.
**If the proxy passes the prefix through**, the app has to answer on
`/design-system/health`:
```nginx
location /design-system/ {
proxy_pass http://app:8080/design-system/; # prefix kept
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
```bash
docker run -d -p 8080:8080 -e SEKURA_BASE_PATH=/design-system sekura-design-mcp:2.0.0
```
**If the proxy strips the prefix**, the app still listens at the root but has no
way to discover what was removed, so it must be told:
```nginx
location /design-system/ {
proxy_pass http://app:8080/; # prefix stripped
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
```bash
docker run -d -p 8080:8080 \
-e SEKURA_EXTERNAL_URL=https://example.com/design-system \
sekura-design-mcp:2.0.0
```
**With Traefik or ingress-nginx, neither is needed.** `X-Forwarded-Prefix` is
honoured automatically. Set `SEKURA_TRUST_PROXY=false` if the container is
exposed directly to the internet, so a forged `X-Forwarded-Host` cannot rewrite
the links it hands out.
### If it starts but never becomes healthy
Almost always `HOST`. A published port forwards to the container's *external*
interface; `HOST=127.0.0.1` binds the container's own loopback instead, so
nothing on the host can connect — while the container's health check, probing
from inside, passes and Docker reports it **healthy**.
`./run.sh start` now names this when it happens, and the server warns at
startup. To restrict access, leave `HOST=0.0.0.0` and narrow the mapping
instead: `-p 127.0.0.1:8080:8080`.
### Ports
Two, and only two:
| | |
|---|---|
| `SEKURA_PORT` | **Published port** — what the outside world connects to |
| `PORT` | **Internal port** — what the server listens on in the container |
They meet at the port mapping (`-p ${SEKURA_PORT}:8080`). Change the published
one; leave the internal one alone, or the healthcheck, the `EXPOSE` and the
mapping have to be kept in step by hand.
### What gets published
Every path below is relative to the mount point. **There is no route outside it
— not even a redirect from `/`.** A deployment reachable at two addresses is one
where a link, a bookmark or a proxy rule eventually points at the wrong one.
| Path | What |
|---|---|
| `/mcp` | MCP endpoint (POST). Rename with `SEKURA_MCP_PATH` |
| `/health` | Liveness, and the contrast audit re-run inside the container |
| `/manifest.json` | **Every URL above and below, as JSON** |
| `/tokens.css` | Custom properties, all themes and densities |
| `/tokens.json` | W3C DTCG format |
| `/css/sekura.css` | The complete stylesheet; `/css/` also has per-component files |
| `/js/sekura.iife.min.js` | Behaviours, drop-in `<script>`; `.esm.min.js` alongside |
| `/docs/` | The 101-page documentation site |
Do not assemble those paths by hand from a base you assume. Fetch
`/manifest.json`, or call the `get_endpoints` MCP tool — only the server knows
what prefix it is actually reachable on, and both report the real URLs including
anything a proxy rewrote.
```bash
curl -s https://example.com/design-system/manifest.json | jq .artefacts.stylesheet.url
# "https://example.com/design-system/css/sekura.css"
```
The documentation site uses relative links throughout, so it is portable to any
prefix with no rebuild.
## Errors, for a model rather than a developer
The caller of an MCP server is a language model. That changes what a good error
is: a model that receives "Unknown component" in a **success** envelope has no
signal anything went wrong, and will carry on and invent the component.
Every failure from this server is marked `isError`, and carries:
| Field | Why |
|---|---|
| `code` | `UNKNOWN_COMPONENT` — branchable without parsing English |
| `hint` | One of `RETRY_LATER`, `CHECK_INPUT`, `TRY_ALTERNATIVE`, `REPORT_TO_USER` |
| Closest matches | Ranked by edit distance, never the whole namespace |
| `retryable` | So a model does not retry something that cannot succeed |
| `traceId` | So a bug report can name one specific response |
```
ERROR UNKNOWN_COMPONENT
No component with that id.
Received: "datepicker"
Closest matches:
- date-picker
- date-range-picker
Next:
- Call `list_components` for the full list.
Recovery hint: TRY_ALTERNATIVE
Retryable: no — the same call will fail again. Change the arguments first.
This is an error, not content. Do not include it in generated output.
```
An empty result is **not** an error, and says so explicitly with `count: 0`. A
model cannot otherwise tell "there genuinely are none" from "something broke and
returned a default", and the second is a false claim of safety.
`npm run test:errors` enforces all of this — 94 checks.
## Publishing the documentation as a static site
```bash
npm run site:publish # -> dist-site/
```
`dist-site/` is self-contained: 101 pages, the assets, and a 404 page. Upload it
anywhere. Every reference in it is relative, so the same bundle serves from a
domain root or any subdirectory with no rebuild:
```
dist-site/ -> https://example.com/
-> https://example.com/design-system/
```
The command is a gate, not just a copy. It refuses to produce a bundle
containing a root-absolute `href` or `src`, because that is the failure that
works locally, works at a domain root, and 404s under a subdirectory — which is
where most bundles end up.
The running container serves the same site at `<app_path>/docs/`, so publishing
statically is an alternative to it rather than a prerequisite.
## Licence
MIT — see [`LICENSE`](./LICENSE).
Contributing: [`CONTRIBUTING.md`](./CONTRIBUTING.md) · Security: [`SECURITY.md`](./SECURITY.md) · Changes: [`CHANGELOG.md`](./CHANGELOG.md)
## Review follow-ups
- `npm run build:react`: builds the versioned local `@sekura/react` package with Button, TextField, Textarea, Select, Checkbox and Switch. See [the API contract](src/react/README.md).
- `npm run build:design-kit`: derives six Figma component sets (216 theme/density/state variants) from emitted CSS. See [import instructions and coverage](design-tools/figma/README.md). It is a starter library requiring visual review in Figma before publication.
- `npm run demo:server`: serves the form-composition lab at `http://127.0.0.1:4173/form-lab.html`, with same-origin HTTP validation, save and version-conflict endpoints. Data lives in memory. These endpoints are not mounted by the MCP server.
- `npm run test:browsers`: exercises native React controls, HTTP workflows, cancellation, permission changes, keyboard overlays, RTL and enlarged text in Chromium, Firefox and WebKit.
- `get_component_code` retains readable text and adds an output schema and `structuredContent`. Recipes include stylesheet dependencies, initialization instructions, native React export availability and public event payloads.
- `validate_integration` checks selected components, markup, loaded CSS, SVG symbols, target IDs, initialization and declared application handlers together. Warnings about undeclared handlers do not execute or prove callback behavior.
Example MCP integration validation:
```json
{
"componentIds": ["button"],
"markup": "<button type=\"button\" class=\"sk-button\">Save</button>",
"stylesheets": ["sekura.css"],
"initialization": "auto",
"handledEvents": ["button:click"]
}
```
Run `npm run test:integration` for structured MCP contracts and `npm run test:request` for HTTP error/abort/timeout handling. Actual screen-reader, OS contrast, physical touch and Figma acceptance procedures are recorded in [MANUAL-VALIDATION.md](MANUAL-VALIDATION.md); they have not been performed by a human tester.
TDQS
Scored across 19 tools
Most tools cleanly map to distinct resources or actions, and the consistent prefixes make routing straightforward. The only real ambiguities are validate_integration vs validate_markup and get_tokens vs export_tokens, but their descriptions clarify the different intents.
The set overwhelmingly follows a snake_case verb_noun pattern such as get_component, export_tokens, and audit_theme. The single bare verb 'search' is a minor deviation from an otherwise uniform convention.
At 19 tools, the surface sits in the 16–25 range that feels heavy for agent navigation, even though the design-system domain is broad. The tools are largely non-redundant, so the count is borderline rather than excessive.
The server covers discovery, retrieval, validation, export, and integration setup with no obvious dead ends for a read-only design-system knowledge and validation server. get_overview ties the surface together and points to every relevant tool.