--- # 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. | 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 | 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.