IGDS Storybook MCP Server
# IGDS MCP Server
An MCP server that gives coding agents the **component contract** for the Israeli Government Design System (IGDS) — props, legal values, defaults, usage snippets and design rules — and nothing else.
No rendered HTML. No CSS. No Storybook chrome. A `get-component` call costs about **3–6 KB**.
> **Why not the official Storybook MCP?** It cannot serve IGDS consumers today: IGDS runs Storybook 10.3.5 / Angular, while the official MCP requires 10.5+, is React-only in preview, and attaches to a *local* dev server. See [docs/EVALUATION.md](docs/EVALUATION.md) for the full assessment and the conditions under which we should switch to it.
---
## What an agent gets
```
get-component("Button")
```
# Button `<igds-button>`
The component allows the user to perform actions with one click
**Frameworks:** angular, react, core-web · **Category:** Buttons
**Import:** `import '@igds/core-web';`
## Props (14)
| prop | type | default | required | description |
|---|---|---|---|---|
| disabled | boolean | false | | Disables interaction. |
| loading | boolean | false | | Shows loading state and disables interaction while active. |
| size | 'small' \| 'medium' \| 'large' | medium | | Button size. Defaults to 'medium'. |
| type | 'button' \| 'submit' \| 'reset' | button | | Native button type. Defaults to 'button'. |
| variant | 'primary' \| 'secondary' \| 'alternative' \| 'link' \| 'link-inline' | primary | | Visual style of the button. Defaults to 'primary'. |
## Events
| event | description |
|---|---|
| igds-click | payload: CustomEvent<{ value: { nativeEvent: PointerEvent } }> from ig |
## Usage — Default (core-web)
```html
<igds-button class="button" variant="primary" size="medium">
כפתור ראשי
</igds-button>
```
```
The legal values, descriptions and events are real — from IGDS's own hand-written per-framework API tables where they exist, falling back to the component's manifest and finally to values mined from the design system's own stories — so an agent cannot invent a `variant` that does not exist.
```
## Quick Start
### Connect over stdio
Build once locally — this doesn't change per client, every one of them below just needs to point `node` at the resulting file:
```bash
git clone https://github.com/michab23/igds-storybook-MCP.git
cd igds-storybook-mcp
npm install
npm run build # produces dist/index.js
```
Then use the generic config shape most MCP clients read directly:
```json
{
"mcpServers": {
"igds": {
"command": "node",
"args": ["<path-to-project>/dist/index.js"]
}
}
}
```
### Client-specific setup
A few tools use a different entry point or config shape instead of the generic block above:
**Claude Code**
```bash
claude mcp add igds --scope project -- node <path-to-project>/dist/index.js
```
`--scope project` writes the entry to `.mcp.json` in the current repo so teammates get it via version control; drop the flag for a personal-only entry, or use `--scope user` to make it available across every project.
**Gemini CLI** — add the same `mcpServers` block above to `.gemini/settings.json` (project) or `~/.gemini/settings.json` (user).
**OpenCode** — different shape: `mcp` instead of `mcpServers`, and `command` is an array. Add to `opencode.json` in the workspace root:
```json
{
"mcp": {
"igds": {
"type": "local",
"command": ["node", "<path-to-project>/dist/index.js"],
"enabled": true
}
}
}
```
**Kiro** — same `mcpServers` shape as the generic block, plus two Kiro-specific fields. Add to `.kiro/settings/mcp.json` (workspace) or `~/.kiro/settings/mcp.json` (user):
```json
{
"mcpServers": {
"igds": {
"command": "node",
"args": ["<path-to-project>/dist/index.js"],
"disabled": false,
"autoApprove": []
}
}
}
```
`autoApprove` lists tool names Kiro should run without a confirmation prompt — leave it empty to confirm every call.
Use an absolute path for `<path-to-project>` in all of the above — a relative one resolves against whatever working directory each tool happens to launch from.
### Connect over HTTP
```bash
npm run http # http://localhost:3000/mcp (PORT to override)
```
Each session gets its own server instance, so concurrent clients are safe. `GET /health` reports version and live session count.
`/mcp` implements the MCP Streamable HTTP protocol, not a webpage — opening it directly in a browser shows `{"error":"Invalid or missing session ID"}`, which is expected since every request needs an `mcp-session-id` header obtained via an `initialize` POST. To exercise it:
- **MCP Inspector**: `npx @modelcontextprotocol/inspector --transport streamable-http http://localhost:3000/mcp`
- **A real MCP client** — point its config at the URL, e.g. `{ "mcpServers": { "igds": { "url": "http://localhost:3000/mcp" } } }`
- **`npm run verify:http`** — spins up its own instance and drives two full MCP sessions through it automatically
### Suggested agent workflow
1. `search-components` to find the right component.
2. `get-component` before writing any markup — use only props and values it lists.
3. `get-component-examples` if you need more than the canonical snippet.
4. `get-design-guidance` when accessibility or RTL matters.
---
## Available Tools
Five tools, ~3 KB of tool definitions total.
| Tool | Parameters | Returns |
|------|-----------|---------|
| `search-components` | `query`, `framework?` | Matching component names + why they matched. References only — never bodies. |
| `list-components` | `framework?`, `category?` | Index of components: name, tag, category, summary. |
| `get-component` | `component`, `framework?`, `detail?` | The API contract: props with types, legal values, defaults, plus one usage snippet. |
| `get-component-examples` | `component`, `framework?`, `variant?`, `limit?` | Usage code snippets from the design system's stories. Code only. |
| `get-design-guidance` | `component` | When to use, when not to, accessibility requirements, RTL notes. |
Freshness metadata (`scrapedAt`, `builtAt`, known gaps) is exposed as the MCP **resource** `igds://meta`, so it costs no tool-list context.
---
## Architecture
The component data behind these tools ships pre-built in the npm package (`data/igds-agent-docs.json`) — there's nothing for a consumer to fetch or generate. It's produced once, offline, by the maintainers from a scrape of IGDS's own Storybook and Zeroheight sites, then distilled down to just the API contract:
```
igds-storybook.globalbit.dev ──► data/igds-agent-docs.json ──► the 5 tools above
+ zeroheight docs (~500 KB, committed & published)
```
Rendered HTML is captured in that process only to mine the real attribute vocabulary and distil usage snippets — it is never served to an agent. `npm run verify` enforces that.
Import statement shapes (`import { Button } from '@igds/react/button'` vs. `import '@igds/core-web/button'` vs. Angular's per-component deep imports) are configured in [data/packages.json](data/packages.json) from the IGDS monorepo's publish targets, and confirmed against the published packages' `exports` maps (IGDS 0.1.37) rather than assumed. Child elements import from their parent's subpath (`TableRow` from `@igds/react/table`). Components without a tag of their own (Filter, Textarea, …) use verified imports from `componentImports`; anything unverified gets no import line rather than a guess.
Inline icon markup in story snippets (`icon: '<path d="M20 3…"/>'`) is replaced with the `@igds/icons` export name (`icon: realEstate`) using [data/icon-index.json](data/icon-index.json). The index stores only hashes of each icon's markup, never the artwork, because the IGDS packages declare no license. Regenerate it from an installed package when IGDS updates, then rebuild:
```bash
npm run build:icons -- /path/to/node_modules/@igds/icons
npm run build:docs
```
Public methods (`Toast.show()`, `Drawer.hide()`, `Input.reportValidity()`, …) come from two files. [data/component-methods.json](data/component-methods.json) is a curated list of the methods consumers call, because IGDS doesn't mark internals private: its manifest lists ~600 "public" methods and documents 3. [data/igds-manifest.json](data/igds-manifest.json) holds every class's method signatures, read from the custom-elements manifest embedded in the live core-web Storybook bundle. The build takes signatures from the manifest and skips, with a warning, any curated method that no longer exists.
Slots (`Modal`'s `footer`, `Header`'s `search`, …) are in [data/igds-slots.json](data/igds-slots.json), read from the compiled component templates because the manifest declares none. Each component chunk of the core-web Storybook build holds its Lit template and its registration decorator, and every `<slot>` is attributed to the element registered after it.
Refresh both without a full scrape:
```bash
npm run scrape:manifest
npm run build:docs
```
Design guidance (`get-design-guidance`) comes from the zeroheight design docs at igds.gov.il. Each component page has Design, Code, Usage and Accessibility tabs; the scraper reads each tab of *that* component, waits for it to render, and keeps only its content. To refresh that text without re-downloading the ~1,000 committed images:
```bash
npm run scrape:zeroheight-text
npm run build:docs
```
Where IGDS publishes no accessibility guidance for a component, `get-design-guidance` says so and points to the matching WAI-ARIA Authoring Practices pattern.
Behaviour an agent can't infer from props, events or snippets (Pagination items need a unique `href`, Dropdown emits `igds-change` when set from code, the default Modal footer always closes, …) is curated in [data/component-notes.json](data/component-notes.json). Each note was reproduced against the IGDS version in its `verifiedIn` field. `get-component` shows the notes before the usage snippet, so a response cut to fit its budget loses the snippet rather than the notes.
## Project layout
| Path | Purpose |
|------|---------|
| `src/agent-docs.ts` | The agent-facing model and the store tools read from. |
| `src/tools.ts` | The five MCP tools. |
| `src/format.ts` | Markdown rendering and context budgets. |
| `src/index.ts`, `src/http-server.ts` | stdio and HTTP entry points. |
| `src/build-agent-docs.ts` | Raw scrape → agent docs transform (maintainer use only). |
| `src/light-dom.ts` | Mines attribute vocabulary and usage snippets from captured markup. |
| `src/zeroheight-clean.ts` | Strips site chrome from Zeroheight text, extracts guidance. |
| `src/scrape-all.ts`, `src/scrapers/` | Scraping pipeline (maintainer use only). |
| `docs/EVALUATION.md` | Official-MCP and Graphify assessment. |
---
## Development
```bash
npm run verify
```
Checks that the served artifact contains no `renderedHtml`, `cssStyles`, `<storybook-root`, `_ngcontent` or site navigation; that components have props, usage and legal values; that the tool surface stays small; and that every `get-component` response stays within budget.
Current measurements:
| Metric | Value |
|--------|-------|
| Components | 50 |
| With props / usage | 49 / 50 |
| With documented legal values | 40 |
| With documented events | 38 |
| `get-component` p50 / p95 / max | 2,720 / 4,818 / 5,678 chars |
| Worst case vs. previous version | 5,678 vs. 153,245 chars (**27× smaller**) |
| Child elements attached to parents | 12 |
| Tool definitions | 5 tools, 3,125 chars |
Numbers are from a full live scrape of all three frameworks, not a partial or synthetic run.
| Command | Purpose |
|---------|---------|
| `npm run verify` | Content and context-budget gates (see above). |
| `npm run verify:http` | Starts the HTTP server and drives two concurrent sessions. |
| `npm run inspector` | MCP Inspector over stdio. |
Refreshing `data/igds-agent-docs.json` from a new IGDS release is a maintainer task, not part of normal setup — see `src/scrape-all.ts` and `src/build-agent-docs.ts` if you're doing that.
## License
ISC
TDQS
Scored across 17 tools
Several tools overlap in purpose: list-components vs search-components vs zeroheight-list-components all deal with component discovery, and get-component vs zeroheight-get-component both retrieve component docs. Story-related tools (get-story, get-component-stories, get-story-examples) have distinct but somewhat ambiguous boundaries. Descriptions help, but an agent may struggle to pick the right tool without careful reading.
Naming is inconsistent: most tools use kebab-case with verb-noun (get-stats, list-components), but zeroheight tools use a 'zeroheight-' prefix with camelCase (zeroheight-search, zeroheight-list-categories). There is no uniform verb convention (get, list, search, load, compare), and the mix of styles reduces predictability.
With 17 tools, the server is on the heavy end but each tool addresses a specific need (storybook data, Zeroheight docs, comparisons, CSS, examples). The scope is broad enough to justify the count, though it could be streamlined by merging some overlapping component/story tools.
The domain is well-covered: data loading, listing, searching, retrieving details, stories, CSS, examples, and Zeroheight integration. The only minor gap is lack of explicit framework management (e.g., unloading data) but this is a read-only documentation server, so core workflows are complete.