DESIGN.md
Rama main · réplica de lectura del repositorio original.
---
# GENERATED FROM design-system/src/tokens.css — DO NOT EDIT.
# Run: npm run design:sync
#
# Dark mode is not part of the DESIGN.md schema, so every color token
# below has exactly one `-dark` twin holding its dark-mode value.
version: alpha
name: "MCPJam"
description: "Design system for the MCPJam MCP inspector, docs, and embedded surfaces."
colors:
background: "oklch(0.9818 0.0054 95.0986)"
foreground: "oklch(0.3438 0.0269 95.7226)"
card: "oklch(0.9818 0.0054 95.0986)"
card-foreground: "oklch(0.1908 0.002 106.5859)"
popover: "oklch(1 0 0)"
popover-foreground: "oklch(0.2671 0.0196 98.939)"
primary: "oklch(0.6832 0.1382 38.744)"
primary-foreground: "oklch(1 0 0)"
secondary: "oklch(0.9245 0.0138 92.9892)"
secondary-foreground: "oklch(0.4334 0.0177 98.6048)"
muted: "oklch(0.9341 0.0153 90.239)"
muted-foreground: "oklch(0.6059 0.0075 97.4233)"
accent: "oklch(0.9245 0.0138 92.9892)"
accent-foreground: "oklch(0.2671 0.0196 98.939)"
destructive: "oklch(0.6261 0.2077 25.2698)"
destructive-foreground: "oklch(1 0 0)"
border: "oklch(0.8847 0.0069 97.3627)"
chrome-control: "oklch(0.8995 0.0191 83.0632)"
chrome-control-border: "oklch(0.856 0.0218 85.9495)"
chrome-hover: "oklch(0.8957 0.0187 86.151)"
chrome-control-hover: "oklch(0.83 0.0235 80.6762)"
divider: "oklch(0.8829 0.0171 88.0071)"
input: "oklch(0.7621 0.0156 98.3528)"
ring: "oklch(0.6171 0.1375 39.0427)"
success: "oklch(0.696 0.17 152.5)"
success-foreground: "oklch(1 0 0)"
warning: "oklch(0.769 0.188 85.3)"
warning-foreground: "oklch(0.239 0.06 60)"
info: "oklch(0.623 0.214 259)"
info-foreground: "oklch(1 0 0)"
pending: "oklch(0.769 0.188 85.3)"
pending-foreground: "oklch(0.239 0.06 60)"
diagram-server: "oklch(0.62 0.1 155)"
diagram-server-foreground: "oklch(0.99 0.01 155)"
diagram-sandbox: "oklch(0.65 0.12 75)"
diagram-sandbox-foreground: "oklch(0.99 0.01 75)"
diagram-view: "oklch(0.6 0.1 290)"
diagram-view-foreground: "oklch(0.99 0.01 290)"
run-origin-cli-border: "oklch(0.65 0.11 195 / 0.5)"
run-origin-cli-bg: "oklch(0.65 0.11 195 / 0.1)"
run-origin-agent-border: "oklch(0.62 0.21 330 / 0.5)"
run-origin-agent-bg: "oklch(0.62 0.21 330 / 0.1)"
overlay: "oklch(0 0 0 / 0.5)"
json-key: "oklch(0.55 0.2 255)"
json-string: "oklch(0.5 0.1 55)"
json-number: "oklch(0.5 0.1 55)"
json-boolean: "oklch(0.5 0.15 145)"
json-boolean-false: "oklch(0.55 0.18 25)"
json-null: "oklch(0.55 0.02 270)"
json-punctuation: "oklch(0.55 0.01 270)"
code-bg: "oklch(0.9341 0.0153 90.239)"
code-text: "oklch(0.1908 0.002 106.5859)"
code-keyword: "oklch(0.55 0.16 35)"
code-function: "oklch(0.1908 0.002 106.5859)"
code-class-name: "oklch(0.48 0.13 250)"
code-property: "oklch(0.45 0.14 250)"
code-variable: "oklch(0.1908 0.002 106.5859)"
code-parameter: "oklch(0.3438 0.0269 95.7226)"
code-string: "oklch(0.45 0.09 145)"
code-number: "oklch(0.52 0.08 60)"
code-boolean: "oklch(0.52 0.08 60)"
code-comment: "oklch(0.62 0.01 95)"
code-punctuation: "oklch(0.58 0.005 95)"
code-operator: "oklch(0.55 0.16 35)"
code-link: "oklch(0.6171 0.1375 39.0427)"
background-dark: "oklch(0.2679 0.0036 106.6427)"
foreground-dark: "oklch(0.8074 0.0142 93.0137)"
card-dark: "oklch(0.2679 0.0036 106.6427)"
card-foreground-dark: "oklch(0.9818 0.0054 95.0986)"
popover-dark: "oklch(0.3085 0.0035 106.6039)"
popover-foreground-dark: "oklch(0.9211 0.004 106.4781)"
primary-dark: "oklch(0.6724 0.1308 38.7559)"
primary-foreground-dark: "oklch(1 0 0)"
secondary-dark: "oklch(0.9818 0.0054 95.0986)"
secondary-foreground-dark: "oklch(0.3085 0.0035 106.6039)"
muted-dark: "oklch(0.2213 0.0038 106.707)"
muted-foreground-dark: "oklch(0.7713 0.0169 99.0657)"
accent-dark: "oklch(0.213 0.0078 95.4245)"
accent-foreground-dark: "oklch(0.9663 0.008 98.8792)"
destructive-dark: "oklch(0.6368 0.2078 25.3313)"
destructive-foreground-dark: "oklch(1 0 0)"
border-dark: "oklch(0.3618 0.0101 106.8928)"
chrome-control-dark: "oklch(0.3085 0.0035 106.6039)"
chrome-control-border-dark: "oklch(0.3618 0.0101 106.8928)"
chrome-hover-dark: "oklch(0.213 0.0078 95.4245)"
chrome-control-hover-dark: "oklch(0.3951 0.0106 106.8928)"
divider-dark: "oklch(0.3618 0.0101 106.8928)"
input-dark: "oklch(0.4336 0.0113 100.2195)"
ring-dark: "oklch(0.6724 0.1308 38.7559)"
success-dark: "oklch(0.648 0.15 152)"
success-foreground-dark: "oklch(1 0 0)"
warning-dark: "oklch(0.75 0.183 55)"
warning-foreground-dark: "oklch(0.95 0.06 70)"
info-dark: "oklch(0.623 0.214 259)"
info-foreground-dark: "oklch(1 0 0)"
pending-dark: "oklch(0.75 0.183 55)"
pending-foreground-dark: "oklch(0.95 0.06 70)"
diagram-server-dark: "oklch(0.8 0.1 155)"
diagram-server-foreground-dark: "oklch(0.2 0.04 155)"
diagram-sandbox-dark: "oklch(0.82 0.11 75)"
diagram-sandbox-foreground-dark: "oklch(0.2 0.04 75)"
diagram-view-dark: "oklch(0.8 0.09 290)"
diagram-view-foreground-dark: "oklch(0.2 0.04 290)"
run-origin-cli-border-dark: "oklch(0.78 0.1 195 / 0.5)"
run-origin-cli-bg-dark: "oklch(0.78 0.1 195 / 0.15)"
run-origin-agent-border-dark: "oklch(0.74 0.18 330 / 0.5)"
run-origin-agent-bg-dark: "oklch(0.74 0.18 330 / 0.15)"
overlay-dark: "oklch(0 0 0 / 0.5)"
json-key-dark: "oklch(0.7 0.18 255)"
json-string-dark: "oklch(0.7 0.1 55)"
json-number-dark: "oklch(0.7 0.1 55)"
json-boolean-dark: "oklch(0.65 0.15 145)"
json-boolean-false-dark: "oklch(0.68 0.16 25)"
json-null-dark: "oklch(0.6 0.02 270)"
json-punctuation-dark: "oklch(0.5 0.01 270)"
code-bg-dark: "oklch(0.2213 0.0038 106.707)"
code-text-dark: "oklch(0.92 0.01 95)"
code-keyword-dark: "oklch(0.75 0.13 35)"
code-function-dark: "oklch(0.92 0.01 95)"
code-class-name-dark: "oklch(0.70 0.13 250)"
code-property-dark: "oklch(0.72 0.14 250)"
code-variable-dark: "oklch(0.92 0.01 95)"
code-parameter-dark: "oklch(0.86 0.01 95)"
code-string-dark: "oklch(0.78 0.10 145)"
code-number-dark: "oklch(0.78 0.09 65)"
code-boolean-dark: "oklch(0.78 0.09 65)"
code-comment-dark: "oklch(0.55 0.01 95)"
code-punctuation-dark: "oklch(0.60 0.005 95)"
code-operator-dark: "oklch(0.75 0.13 35)"
code-link-dark: "oklch(0.7400 0.1308 38.7559)"
typography:
sans:
fontFamily: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\""
serif:
fontFamily: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif"
mono:
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace"
code:
fontFamily: "\"JetBrains Mono\", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace"
rounded:
sm: 0.25rem
md: 0.375rem
lg: 0.5rem
xl: 0.75rem
spacing:
base: 0.25rem
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.primary-foreground}"
rounded: "{rounded.lg}"
typography: "{typography.sans}"
button-secondary:
backgroundColor: "{colors.secondary}"
textColor: "{colors.secondary-foreground}"
rounded: "{rounded.lg}"
popover:
backgroundColor: "{colors.popover}"
textColor: "{colors.popover-foreground}"
rounded: "{rounded.lg}"
menu-item:
backgroundColor: "{colors.accent}"
textColor: "{colors.accent-foreground}"
rounded: "{rounded.sm}"
skeleton:
backgroundColor: "{colors.muted}"
rounded: "{rounded.md}"
button-destructive:
backgroundColor: "{colors.destructive}"
textColor: "{colors.destructive-foreground}"
rounded: "{rounded.lg}"
card:
backgroundColor: "{colors.card}"
textColor: "{colors.card-foreground}"
rounded: "{rounded.lg}"
input:
backgroundColor: "{colors.background}"
textColor: "{colors.foreground}"
rounded: "{rounded.md}"
badge-success:
backgroundColor: "{colors.success}"
textColor: "{colors.success-foreground}"
rounded: "{rounded.sm}"
badge-warning:
backgroundColor: "{colors.warning}"
textColor: "{colors.warning-foreground}"
rounded: "{rounded.sm}"
badge-info:
backgroundColor: "{colors.info}"
textColor: "{colors.info-foreground}"
rounded: "{rounded.sm}"
badge-pending:
backgroundColor: "{colors.pending}"
textColor: "{colors.pending-foreground}"
rounded: "{rounded.sm}"
code-block:
backgroundColor: "{colors.code-bg}"
textColor: "{colors.code-text}"
typography: "{typography.code}"
rounded: "{rounded.md}"
---
# MCPJam Design System
## Overview
MCPJam is a testing and inspection workbench for MCP servers: developers connect a
server, exercise its tools, prompts and resources, watch traces, and run evals. The
interface is dense with technical output, so the design system's job is to keep
long-running, high-volume information legible rather than to decorate it.
**Direction: refined minimalism. Cream paper + brown ink + one sharp orange. No
glassy backdrops, no floaty shadows, sharp warm borders, generous space.**
### How to use this file
`design-system/src/tokens.css` is the single source of truth for the palette. The
YAML front matter above is **generated** from it — edit tokens.css and run
`npm run design:sync`, never edit the front matter by hand. The prose below is
hand-written and deliberately names tokens rather than quoting color values, so it
cannot go stale.
Agents working outside this repository can load the file directly:
`https://raw.githubusercontent.com/MCPJam/inspector/main/DESIGN.md`
The surfaces this system covers: the inspector app (`mcpjam-inspector/`), the
Mintlify docs site (`docs/`), the published `@mcpjam/chat-ui` transcript renderer,
the `soundcheck` deployment dashboard, and the `mcp` worker's UI. The docs site and
chat-ui hold generated mirrors of these tokens; both are checked in CI.
## Colors
Colors are **roles, not hues**. Every surface role pairs with a `-foreground`
role intended to sit on it — `card` with `card-foreground`, `primary` with
`primary-foreground`. Reach for the pair, never for a hand-picked text color.
**The pairing is a convention, not a contrast guarantee.** The reading surfaces
(`background`, `card`, `popover`, `code-bg`) clear WCAG AA comfortably. Several
solid brand and status fills do not: they were chosen as fills first, and at
normal text sizes their white labels fall short of 4.5:1. Check the table before
putting body copy on one — and if you need small text on a tinted background,
use a tint (`bg-destructive/10`) with the *reading* foreground rather than a
solid fill.
<!-- BEGIN GENERATED: contrast — run npm run design:sync -->
| Surface / text pair | Light | Dark |
| --- | --- | --- |
| `warning` | 7.96 — AA, any text | 2.00 — **Below 3:1 — no text** |
| `pending` | 7.96 — AA, any text | 2.00 — **Below 3:1 — no text** |
| `success` | 2.53 — **Below 3:1 — no text** | 3.05 — Large text only |
| `primary` | 3.00 — Large text only | 3.12 — Large text only |
| `muted` | 3.17 — Large text only | 8.38 — AA, any text |
| `diagram-sandbox` | 3.19 — Large text only | 10.27 — AA, any text |
| `diagram-server` | 3.39 — Large text only | 9.99 — AA, any text |
| `info` | 3.75 — Large text only | 3.75 — Large text only |
| `destructive` | 3.93 — Large text only | 3.76 — Large text only |
| `diagram-view` | 3.94 — Large text only | 9.55 — AA, any text |
| `secondary` | 6.39 — AA, any text | 12.55 — AA, any text |
| `background` | 10.98 — AA, any text | 8.33 — AA, any text |
| `popover` | 15.18 — AA, any text | 10.48 — AA, any text |
| `accent` | 12.16 — AA, any text | 15.97 — AA, any text |
| `code-bg` | 15.20 — AA, any text | 13.62 — AA, any text |
| `card` | 17.50 — AA, any text | 14.39 — AA, any text |
<!-- END GENERATED: contrast -->
Ratios are computed from the canonical tokens and describe **text on its own
fill** (WCAG 1.4.3): 3:1 is a genuine pass for large text (≥24px, or ≥18.66px
bold) but not for body copy, and a pair marked **Below 3:1** should not carry
text at any size. Treat those as known debt, not as license.
The table says nothing about **1.4.11 non-text contrast** — a component's
boundary against the surface *around* it is a different pair of colors, and it
can be worse: `primary` clears 3:1 against its own label but sits below it
against the page. If a control needs a visible edge, give it a `border` token
rather than relying on the fill to separate itself from the background.
Every token has exactly one `-dark` twin holding its dark-mode value. A `-dark`
token is not a shade variant or a darker version of its sibling; it is the same
role as rendered in dark mode, and the app switches between them via the `.dark`
class. Design both modes, always.
In code you rarely touch the tokens directly. `design-system/src/index.css` maps
them to Tailwind utilities through `@theme inline`, so components use
`bg-background`, `text-muted-foreground`, `border-border` and so on.
**Status colors.** `success`, `warning`, `info` and `pending` carry meaning, not
decoration: use them for state, and nothing else. `pending` shares `warning`'s hue
deliberately — work in flight and work needing attention read as the same family of
"not done".
**Destructive has two modes.** On a solid fill (a destructive button, a badge) use
`destructive-foreground` for the text. On a tinted surface (`bg-destructive/5`
through `/20`) use `text-destructive` for body copy instead — foreground-on-tint is
unreadable.
**Diagram accents are scoped.** `diagram-server`, `diagram-sandbox` and
`diagram-view` label the three layers of the client architecture diagram (servers
hub, sandbox proxy iframe, view iframe). They are role-based names for that one
drawing. Do not reuse them as a general accent palette.
**Two syntax palettes, and they are not interchangeable.** The `json-*` tokens
belong to the JSON viewer alone. The `code-*` tokens serve multi-language code —
docs code blocks (via Mintlify's Shiki `css-variables` theme), terminal and log
views, in-app editors. Both are tuned for warm-paper backgrounds: coral keyword,
indigo class and property names, muted forest strings. Picking the wrong family
gives you colors that were balanced against a different background.
**Theme presets** (`brutalist`, `soft-pop`, `tangerine`) and the `.app-theme-scope`
selector live in `design-system/src/index.css` for surfaces that opt in. They are
advanced; do not fork token values per preset by hand.
## Typography
Four family tokens: `sans` (a system stack — no webfont in the app's body text),
`serif`, `mono`, and `code`, which pins JetBrains Mono for anything showing code or
identifiers.
Fonts differ per surface, by design, and that is worth knowing before you "fix" an
inconsistency:
| Surface | Faces |
| --- | --- |
| Inspector app | System sans; JetBrains Mono for code; Caveat and IBM Plex Mono for accents |
| Docs site | Geist (headings 500, body 400); Geist Mono for code |
| soundcheck | Geist / Geist Mono via `next/font` |
| Score micro-site | Instrument Sans, Montserrat, JetBrains Mono — **a deliberate exception** |
There is no canonical type scale. Sizes come from Tailwind's `text-*` utilities per
surface. Do not invent one in this file; if the product grows a real scale, add it
to tokens.css first and it will appear in the front matter.
## Layout
Spacing is a 4px base unit (`spacing.base`), consumed as Tailwind's numeric scale.
"Generous space" is a real constraint, not a mood: this UI shows dense technical
output, and whitespace is what keeps a wall of tool results scannable. When in
doubt, add space rather than a border or a background tint.
The app chrome is a **linen frame around an off-white working surface** (the
Production Redesign, BB-127): the sidebar and top bar share one linen ground with no
divider between them, and the content panel sits inset within that frame, its
rounded top edge doing the separating. See
`mcpjam-inspector/client/src/components/app-chrome-panel.tsx` and `mcp-sidebar.tsx`.
In the docs, tables use bottom-hairline rows with no header tint and generous
padding; callouts are editorial left-rail marks, never soft-filled boxes.
## Elevation & Depth
Depth comes from **borders and tonal layers first**. The `background` → `muted` →
`accent` progression is the paper stack; a 1px border in `border` is the default
separator. Reach for a tonal step before you reach for a shadow.
A shadow scale does exist (`--shadow-2xs` through `--shadow-2xl` in tokens.css). It
is deliberately faint — 5–10% black across most of the range, and only the largest
step goes further. The DESIGN.md schema has no elevation token group, which is why
you will not find shadows in the front matter above; tokens.css remains their home.
"No floaty shadows" means: no glows, no backdrop blur, no frosted-glass panels, no
heavy drop shadows to fake layering. The docs site goes further and flattens card
shadows entirely. Modal scrims use `overlay`.
## Shapes
`rounded.lg` is the base radius, with `sm`/`md`/`xl` derived from it by the same
±px offsets that `design-system/src/index.css` uses for its Tailwind radii — the two
cannot drift.
Edges are the primary structural device: 1px, in `border`, warm rather than grey.
Preset themes override the radius (brutalist squares everything, soft-pop rounds it
hard); that is the preset's business, not something to hand-apply.
## Components
Forty primitives live in `design-system/src/components/`, shadcn-style, built on
Radix. Import them per-file:
```tsx
import { Button } from "@mcpjam/design-system/button";
import { cn } from "@mcpjam/design-system/cn";
```
Conventions: `cn()` merges class names, `class-variance-authority` defines variants,
`lucide-react` supplies icons, `sonner` supplies toasts. Extend a primitive through
its `className` prop or by adding a cva variant to the primitive itself — not by
restyling it at the call site.
Docs equivalents follow the product: buttons match the app's Add Server button,
tables are bottom-hairline only, callouts are left-rail editorial marks.
## Do's and Don'ts
**Do**
- Use role tokens and their `-foreground` pairs; design light and dark together.
- Add new colors to `design-system/src/tokens.css`, then run `npm run design:sync`.
- Use `success`/`warning`/`info`/`pending` for state, and status only.
- Prefer a border or a tonal step over a shadow; prefer space over either.
- Check the contrast table above before putting text on a solid fill, and
check the mode you are actually shipping — several pairs pass in one mode
and fail in the other.
- Extend primitives via variants; keep one-off styling at the call site rare.
**Don't**
- Don't write a literal hex or `oklch()` value into a component, a stylesheet, or
this file's prose. Role values belong in `design-system/src/tokens.css`, so
there is one place to change. The one exception is a package-local accent
palette deliberately outside the role system — chat-ui's
`--trace-waterfall-*` — and adding another is a decision to make on purpose:
it won't track the theme, and nothing will check it.
- Don't hand-edit generated regions: this file's front matter, the fenced blocks in
`docs/style.css` and `chat-ui/src/styles.css`, or the derived color fields in
`docs/docs.json`. CI fails on drift; run the sync script.
- Don't use `diagram-*` outside the architecture diagram.
- Don't style the JSON viewer with `code-*` tokens, or code blocks with `json-*`.
- Don't add glass, glow, or heavy elevation.
- Don't put small text on `primary`, `destructive`, `success`, `info` or
`pending` fills, and don't read the pairing convention as a promise that
any `-foreground` is legible on its role.
- Don't propagate the score micro-site's fonts to other surfaces.
- Don't restyle a shadcn primitive ad hoc, and don't fork preset token values.