Skip to the content

Small, exact answers for your coding agent

The Perron MCP server. It answers an agent's questions about Perron's tokens, its docs, its components and patterns, and the API of the Base UI and MUI versions a project installs, so the agent reads exact, small answers instead of the sources.

Set it up

  1. Run the server

    npx @mylder/perron-mcp
  2. Or add it to the project's .mcp.json

    It runs over stdio. In a project's .mcp.json:

    {
      "mcpServers": {
        "perron": {
          "command": "npx",
          "args": ["-y", "@mylder/perron-mcp"]
        }
      }
    }

Tools 5

Each tool takes dir, the project folder to read from as an absolute path, such as a git worktree an agent works in while the server runs from another checkout; the server's own project is the default. The data is read again when it changes, so a token rebuilt mid-session shows.

list_tokens

List Perron tokens

Perron's design tokens: name, type, CSS custom property, value in each theme (light, dark), the token it aliases, reduced-motion overrides and what it is for. Filter by words in the name, CSS property or description, such as surface warning, and by type, such as color; with no filter it lists the names only.

Inputs

querystringoptional
Words that must all appear in the name or description
typestringoptional
A DTCG type: color, dimension, duration, typography…
dirstringoptional
The project folder to read from, as an absolute path, such as the git worktree you work in; the server's own project by default
list_tokens({ query: "surface warning" })
TokenLightDark
color.surface.warningorange.100orange.900
Show the raw answer
{"name":"color.surface.warning","type":"color","cssVar":"--perron-color-surface-warning","description":"Tinted background for a warning message or count","values":{"light":{"css":"#FEF1E4","alias":"palette.orange.100"},"dark":{"css":"#3F280A","alias":"palette.orange.900"}}}

search_docs

Search Perron docs

Search Perron's foundation pages (colour, type, space, icons, motion, data states, the map…), content design (the words Mylder shows), the glossary, and the component contract and spec template. Returns the best matching sections in full.

Inputs

querystringrequired
What to look for, such as reduced motion or delay wording
limitinteger, 1 to 10optional
Sections to return, 3 by default
dirstringoptional
The project folder to read from, as an absolute path, such as the git worktree you work in; the server's own project by default
search_docs({ query: "reduced motion", limit: 1 })

From Motion (docs/foundations/motion.md):

Reduced motion

When the system asks for less motion, the transition tokens take 0.01 ms: their values under prefers-reduced-motion: reduce are in the table's Reduced motion column. The value is 0.01 ms rather than 0, so transitionend and animation events still fire. CSS that reads the tokens needs nothing more.

  • Loops keep their durations in the tokens, because a loop at 0.01 ms would flicker. Each component that runs a loop stops it under reduced motion.
  • Vehicles move to each new report without gliding.

The values come from the resolver's motion modifier. Its default context, full, is empty, because base already holds the full-motion values; its reduced context points at packages/perron/tokens/motion-reduced.tokens.json. The build writes the overrides into each output:

OutputReduced motion
tokens.cssThe overrides inside @media (prefers-reduced-motion: reduce), on :root
tokens.tsmodifiers.motion.reduced, as CSS text and as numbers
penpot.tokens.jsonA motion-reduced set, and a Motion group with Full and Reduced themes
Show the raw answer
From Motion (docs/foundations/motion.md):

## Reduced motion

When the system asks for less motion, the transition tokens take 0.01 ms: their values under `prefers-reduced-motion: reduce` are in the table's Reduced motion column. The value is 0.01 ms rather than 0, so `transitionend` and animation events still fire. CSS that reads the tokens needs nothing more.

- **Loops** keep their durations in the tokens, because a loop at 0.01 ms would flicker. Each component that runs a loop stops it under reduced motion.
- Vehicles move to each new report without gliding.

The values come from the resolver's `motion` modifier. Its default context, `full`, is empty, because base already holds the full-motion values; its `reduced` context points at `packages/perron/tokens/motion-reduced.tokens.json`. The build writes the overrides into each output:

| Output               | Reduced motion                                                             |
| -------------------- | -------------------------------------------------------------------------- |
| `tokens.css`         | The overrides inside `@media (prefers-reduced-motion: reduce)`, on `:root` |
| `tokens.ts`          | `modifiers.motion.reduced`, as CSS text and as numbers                     |
| `penpot.tokens.json` | A `motion-reduced` set, and a Motion group with Full and Reduced themes    |

list_components

List Perron components

Perron's components and patterns, one JSON line each: name, layer, category, status, its successor when deprecated, and what it is in one sentence. Filter by words, such as turn a setting on or off, which keeps the entries that hold any of them, ranked by how well they match, and by layer and category, such as Inputs. Then ask get_component for the one to use.

Inputs

querystringoptional
Words to look for in the name, the category, the summary and when to use it
layer"component" | "pattern"optional
categorystringoptional
A category, such as Actions, Inputs or Overlays for a component
dirstringoptional
The project folder to read from, as an absolute path, such as the git worktree you work in; the server's own project by default
list_components({ query: "setting", category: "Inputs" })
{"name":"Checkbox","layer":"component","category":"Inputs","status":"stable","summary":"A box with its words beside it that turns one setting or one answer on and off, such as \"Show planned works on the map\", ticked when it is on and dashed when it stands for a set that is partly on, and submitted with a form."}
{"name":"Switch","layer":"component","category":"Inputs","status":"stable","summary":"A two-state control that turns one setting on or off at once, such as showing vehicle trails on the map."}

get_component

Get a Perron component

Everything a caller needs to use a Perron component or pattern, as JSON: what it is, its import line, when to use it and which entries to use instead, its props with their types, defaults, descriptions and deprecations, the rules of its Do and don't, each with an id such as DD-2, a level (must, or should, which a reason may break) and what checks it (types, the compiler, or review, nothing but you), the mistakes agents have made with it, and its docs page.

Inputs

namestringrequired
The component or pattern, such as Button or DepartureRow
dirstringoptional
The project folder to read from, as an absolute path, such as the git worktree you work in; the server's own project by default
get_component({ name: "Button" })
{
  "name": "Button",
  "layer": "component",
  "category": "Actions",
  "status": "stable",
  "summary": "A button that runs one action on the screen it sits on, such as \"Show departures\", in ink (`neutral`) or, for the one main action of a screen, in the brand yellow (`brand`).",
  "import": "import { Button } from '@mylder/perron'",
  "whenToUse": "Use Button for an action that runs on the screen it sits on and is named in words, such as \"Show departures\", or, given `selected`, for a toggle whose words are an action, such as \"Follow\".",
  "alternatives": [
    {
      "name": "LinkButton",
      "when": "The action takes the reader to another page or place, such as \"Back to the map\""
    },
    {
      "name": "Link",
      "when": "The words that go to another page or place sit in a sentence, or stand as plain text"

Cut for this page: 214 more lines, 8,538 characters. The agent reads the whole answer.

get_reference

Get a Base UI or MUI API reference

The API reference of a Base UI or MUI component, from the docs bundled in the version installed in this project: props, data attributes, state and types. Name a part, such as Positioner for Base UI or NativeSelect for MUI, to get only that part.

Inputs

library"base-ui" | "mui"required
componentstringrequired
The component, such as Select or TextField
partstringoptional
One part, such as Positioner
dirstringoptional
The project folder to read from, as an absolute path, such as the git worktree you work in; the server's own project by default
get_reference({ library: "base-ui", component: "Select", part: "Positioner" })

@base-ui/react 1.9.0, docs/react/components/select.md

Positioner

Positions the select popup. Renders a <div> element.

Positioner Props:

PropTypeDefaultDescription
alignItemWithTriggerbooleantrueWhether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space.
disableAnchorTrackingbooleanfalseWhether to disable the popup from tracking any layout shift of its positioning anchor.
alignAlign'center'How to align the popup relative to the specified side.
alignOffsetnumber | OffsetFunction0Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a data object parameter with the following properties: data.anchor: the dimensions of the anchor element with properties width and height.data.positioner: the dimensions of the positioner element with properties width and height.data.side: which side of the anchor element the positioner is aligned against.data.align: how the positioner is aligned relative to the specified side.
sideSide'bottom'Which side of the anchor element to align the popup against. May automatically change to avoid collisions.
Show the raw answer
@base-ui/react 1.9.0, docs/react/components/select.md

### Positioner

Positions the select popup.
Renders a `<div>` element.

**Positioner Props:**

| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| alignItemWithTrigger | `boolean` | `true` | Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space. |
| disableAnchorTracking | `boolean` | `false` | Whether to disable the popup from tracking any layout shift of its positioning anchor. |
| align | `Align` | `'center'` | How to align the popup relative to the specified side. |
| alignOffset | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels.&#xA;Also accepts a function that returns the offset to read the dimensions of the anchor&#xA;and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.`data.positioner`: the dimensions of the positioner element with properties `width` and `height`.`data.side`: which side of the anchor element the positioner is aligned against.`data.align`: how the positioner is aligned relative to the specified side. |
| side | `Side` | `'bottom'` | Which side of the anchor element to align the popup against.&#xA;May automatically change to avoid collisions. |

Cut for this page: 82 more lines, 5,860 characters. The agent reads the whole answer.

Where the answers come from

The tokens, docs and components come from the data that @mylder/perron ships in data/, so they match the installed Perron. The references come from the docs bundled inside @base-ui/react and @mui/material in the project's node_modules.

node_modules/@mylder/perron/data/

components.json, docs.json and tokens.json, built with each release of @mylder/perron (0.10.1 here), so they never drift from it.

node_modules/@base-ui/react/docs/react/components/

The docs bundled in @base-ui/react 1.9.0: the version the project's lockfile holds, not the latest release.

node_modules/@mui/material/docs/material-ui/

The docs bundled in @mui/material 9.5.0: the version the project's lockfile holds, not the latest release.