Skip to main content
Glama
DarCas
by DarCas
README.md
# refero-design-mcp

[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?style=for-the-badge)](https://www.typescriptlang.org)
![Node.js](https://img.shields.io/badge/node.js-%3E%3D20-5FA04E?logo=nodedotjs&logoColor=white&style=for-the-badge)
[![Version](https://img.shields.io/github/v/tag/DarCas/refero-design-mcp?label=version&style=for-the-badge)](https://github.com/DarCas/refero-design-mcp/releases)
![npm](https://img.shields.io/npm/v/@darcas/refero-design-mcp?style=for-the-badge)

[![Tests](https://img.shields.io/badge/tests-67%20passing-brightgreen?style=for-the-badge)](#testing)

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)

[![Buy me a coffee](https://img.shields.io/badge/buy_me_a_coffee-%E2%9D%A4%EF%B8%8F-FEEBE7?style=for-the-badge&labelColor=FF0000)](https://www.paypal.com/donate/?hosted_button_id=YZQDE3TEYDBWA)

> The underlying data belongs to Refero Design. This is an unofficial client; no
affiliation or endorsement is implied.

An [MCP](https://modelcontextprotocol.io) server for discovering, searching and
extracting design systems from the public pages of
[styles.refero.design](https://styles.refero.design).

Give an assistant a real design system — palette, type scale, spacing, surfaces,
do/don't guidance — instead of one it invented.

---

## Why it is built this way

Two decisions shape everything else.

**The JSON API is not used.** `styles.refero.design/robots.txt` disallows
`/api/`, and the endpoint is gated in practice: it answers `403` to every
non-browser client and `200` to a real browser, which points at TLS fingerprinting
rather than authentication. Spoofing headers would not fix that, and would not be
the right thing to do regardless.

So this server reads only what `Allow: /` permits — the published sitemap and the
public style pages — reachable with a plain `curl` and no pretending to be
anything else. Requests are conditional, concurrency-capped, and sent with a
`User-Agent` that identifies the project.

**Coverage is reported, never implied.** A style page is a few hundred KB and
there are over a thousand of them, so the local index grows on demand. That
makes "I found no matches" ambiguous — it usually means *"I looked at 30 of
1,340"*, not *"nothing exists"*. Every search states its own coverage, and
`refero_index_status` exists so the model can check before concluding a style is
absent.

## Tools

| Tool | Purpose |
| --- | --- |
| `refero_index_status` | Published styles vs. locally indexed styles |
| `refero_search_styles` | Free-text search over names, north stars, colours, fonts, URLs |
| `refero_match_style` | Ranks styles against a prose design brief |
| `refero_get_design_md` | Renders a style as `design.md`, by section |
| `refero_get_style` | Parsed design system plus measured tokens, as JSON |
| `refero_list_style_ids` | Style UUIDs from the sitemap, without reading any page |

A resource, `refero://style/{id}/design.md`, is exposed for clients that prefer
resource reads over tool calls.

### Sections

`refero_get_design_md` takes `sections`, and **honours it**:

```
overview · colors · typography · type_scale · spacing · surfaces
imagery · principles · components · similar · custom
```

Sections with no data are dropped rather than rendered as empty headings.

## Installation

```bash
npm install -g @darcas/refero-design-mcp
```

Installs the `refero-design-mcp` command — the binary name stays unscoped, so
client configs stay short.

### Configuration

Point a client at the binary, either installed globally or run on demand:

```jsonc
{
  "mcp": {
    "servers": {
      // installed with `npm install -g @darcas/refero-design-mcp`
      "refero": {
        "command": ["refero-design-mcp"]
      },
      // or run on demand — no install step, first run downloads the package
      "refero-on-demand": {
        "command": ["npx", "-y", "@darcas/refero-design-mcp"]
      }
    }
  }
}
```

`-y` suppresses the install prompt, which would otherwise block startup. The
`npx` form starts in about 2s on a cold cache and under 1s once cached; the
global form starts immediately and needs no network. Either works, and the
server behaves identically.

The package is scoped but the binary is not, so the command is
`refero-design-mcp` with no `@darcas/` prefix.

Transport is stdio. Diagnostics go to stderr only — stdout carries JSON-RPC
frames and nothing else. A server started by hand (`npm start`) will appear to
hang: it is waiting for a client on stdin, which is correct behaviour.

### Environment variables

Every setting is optional.

| Variable | Default | Purpose |
| --- | --- | --- |
| `REFERO_SITE_URL` | `https://styles.refero.design` | Origin base URL |
| `REFERO_STYLES_SITEMAP` | `{site}/sitemaps/styles.xml` | Style index |
| `REFERO_MAX_FETCHES` | `25` | Max page fetches per tool call |
| `REFERO_CONCURRENCY` | `4` | Max simultaneous requests |
| `REFERO_TIMEOUT_MS` | `30000` | Per-request timeout |
| `REFERO_CACHE_TTL_MS` | `604800000` | Cache freshness window (7 days) |
| `REFERO_CACHE_DIR` | `~/.cache/refero-design-mcp` | On-disk cache location |
| `REFERO_USER_AGENT` | see `src/config.ts` | Request identification |
| `REFERO_MAX_RESPONSE_CHARS` | `40000` | Response budget |
| `REFERO_VERBOSE` | `false` | Diagnostics to stderr |

## Example session

```jsonc
// The model calls these in sequence
{ "name": "refero_index_status", "arguments": {} }
// → "Published styles: 1342 / Indexed locally: 1 / Coverage: <1% (1 styles)"

{ "name": "refero_match_style", "arguments": { "brief": "dark, dense dashboard for engineers" } }
// → ranked styles, each with matched terms and the north star that drove the match

{ "name": "refero_get_design_md", "arguments": {
    "style_id": "a73148b9-449b-42cd-9f38-86ef694f500e",
    "sections": ["overview", "colors", "typography"]
} }
// → # Apple, with the palette table and type scale
```

## How styles are indexed

```
sitemaps/styles.xml  →  list of ids + lastmod        (small, fetched once)
        ↓
/style/{id}          →  Next.js RSC payload in HTML  (~300 KB, fetched on demand)
        ↓
result.meta / result.raw / result.designSystem         (parsed, validated, cached)
```

The style record is located by `styleId`. A page embeds a dozen or more related
styles that share the same key names, so anchoring on the id is what prevents
returning a neighbour's palette.

The RSC format is internal to Next.js and may change. Every failure surfaces as
a typed error rather than a partially-filled object that looks like a valid
answer.

## Development

```bash
npm install
npm run dev        # watch mode
npm run verify     # typecheck + lint + test + build
```

| Script | Action |
| --- | --- |
| `npm run build` | Compile to `dist/` |
| `npm start` | Run the compiled server |
| `npm run typecheck` | `tsc --noEmit` |
| `npm run lint` | ESLint, type-aware rules |
| `npm test` | Vitest |
| `npm run verify` | All of the above, in order |
| `npm run clean` | Remove `dist/` |
| `npm run deploy` | Test, build, then publish to npm |

## Testing

67 tests across 7 files.

Unit tests run **offline**, against a byte-for-byte fixture of a real RSC
payload — the fragile parts (id anchoring, brace matching, lazy reference
detection) are exactly the parts worth pinning.

`test/e2e.test.ts` drives the server against the live site and skips itself when
the origin is unreachable. The reachability probe runs at module load rather
than in a hook, because `describe.skipIf` is evaluated during collection: a
probe in `beforeAll` leaves every test silently skipped, which is
indistinguishable from a passing suite.

## Design notes

- **Schemas derived from real payloads, not from documentation.** `spacing.radius`
  is a per-element map (`{cards: "28px", buttons: "9999px"}`), not a string.
  `similar` is `[{business, why}]`, not `string[]`. `surfaces` is
  `{hex, name, level, purpose}`. `typeScale` sizes arrive as *numbers*. Every
  schema is permissive about type and strict about presence, so a partial
  document degrades instead of throwing away a whole design system.
- **Measured and curated data are both kept.** `result.raw` holds tokens with
  usage frequency and context; `result.designSystem` holds the curated reading.
  Frequency is what separates a signature colour from an incidental one.
- **Truncation respects structure.** Cutting markdown with `slice(0, n)` slices
  code fences in half, which makes the model read broken CSS as if it were the
  design. This server trims on a structural boundary instead, and closes any
  fence it opens, so a shortened document is still valid.
- **Conditional requests everywhere,** so a repeat call usually costs a 304.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

---

Made with ❤️ by [Dario Casertano (DarCas)](https://casertano.name).

TDQS

A3.8/5.0

Scored across 6 tools

Disambiguation4/5

Each tool targets a distinct action, and descriptions actively clarify the two closest pairs: search_styles (word-boundary keyword match) vs match_style (qualitative prose brief), and get_design_md (prose) vs get_style (raw JSON tokens). The boundaries are mostly clear, though a naive agent could still reach for search_styles when match_style is intended.

Naming Consistency4/5

All names share the refero_ prefix and snake_case with a verb_noun shape (search_styles, match_style, get_style, get_design_md, list_style_ids). refero_index_status is the one deviation (noun_noun rather than an action verb), but it is still readable and in the same convention.

Tool Count5/5

Six tools is well-scoped for a design-style discovery and rendering server, with each tool earning its place across the discovery, search, match, retrieve, and diagnostic phases.

Completeness4/5

The surface covers the full read lifecycle: discover ids (list_style_ids), search by text (search_styles), match by brief (match_style), retrieve as JSON (get_style) or prose (get_design_md), plus index diagnostics (index_status). Only minor gaps, such as filtered/paginated listing, remain and are workable given the sitemap-backed ids.

Maintenance

ActivityMaintained
ResponsivenessNo issues