Skip to content

Commit e9d4e92

Browse files
authored
Merge pull request #1156 from modelcontextprotocol/add-storybook
feat: Add Storybook component library with presentational components
2 parents 78f7a1b + 8483b98 commit e9d4e92

File tree

187 files changed

+20989
-96
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

187 files changed

+20989
-96
lines changed

.claude/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"enabledPlugins": {
3+
"playwright@claude-plugins-official": true
4+
}
5+
}

.github/workflows/main.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Run install, format, lint, build, and test on every push or pull request
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v6
12+
13+
- name: Setup Node.js
14+
uses: actions/setup-node@v6
15+
with:
16+
node-version: '20.x'
17+
cache: 'npm'
18+
19+
- name: Install dependencies
20+
working-directory: ./clients/web
21+
run: npm install
22+
23+
- name: Check formatting
24+
working-directory: ./clients/web
25+
run: npm run format:check
26+
27+
- name: Run linter
28+
working-directory: ./clients/web
29+
run: npm run lint
30+
31+
- name: Run Build
32+
working-directory: ./clients/web
33+
run: npm run build
34+
35+
# - name: Run tests
36+
# run: npm run test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
node_modules
55
dist
66
.env
7+
/.playwright-mcp/

AGENTS.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Inspector V2
2+
3+
This is an application for inspecting MCP servers. Has three incarnations, Web, TUI, and CLI.
4+
5+
## Project Structure
6+
7+
```
8+
inspector/
9+
├── clients/
10+
│ ├── web/ # Web client code
11+
│ ├── cli/ # CLI client code
12+
│ ├── tui/ # TUI client code
13+
│ ├── launcher/ # Shared launcher
14+
├── core/ # Shared core code
15+
├── soecification/ # Build specification
16+
...
17+
```
18+
19+
## Maintenance Rules
20+
21+
### Keep documentation files up to date
22+
- When adding, removing, renaming, or changing the purpose of any file or folder, update the corresponding entry in the main README.md and/or the related clients/*/README.md
23+
- When the structure of the project, the tech stack, or the developer setup changes, update appropriate README.md files with the details.
24+
- When adding new commands, dependencies, or architectural patterns, update the relevant sections of appropriate README.md files as well.
25+
- When rules for implementation and testing change, update this file AGENTS.md
26+
27+
### Always test new or modified coderea
28+
- Ensure all code has corresponding tests
29+
- Ensure test coverage for each file is at least 90%
30+
- In unit tests that expect error output, suppress it from the console
31+
32+
### Responding to Code Reviews
33+
- When asked to respond to a code review of a PR,
34+
- it is not necessary to implement all suggestions
35+
- you are free to implement suggestions in a different way or to ignore if there is a good reason
36+
- after making the changes, respond to each review comment with what was done (or why it was ignored)
37+
38+
### Lint-fixed, Formatted code
39+
- Ensure linting and formatting are applied after every change
40+
- ALWAYS do `npm run format`, `npm run lint`, `npm run typecheck`, `npm run test` and `npm run build` before pushing any changes
41+
42+
### Typescript instructions
43+
- Use TypeScript for all new code
44+
- Follow TypeScript best practices and coding standards
45+
- NEVER use 'any' as a type
46+
- NEVER suppress error types (e.g., no-unused-vars, no-explicit-any) in the typescript or eslint configuration as a way of satisfying the linter or compiler.
47+
- Utilize type annotations and interfaces to improve code clarity and maintainability
48+
- Leverage TypeScript's type inference and static analysis features for better code quality and refactoring
49+
- Use type guards and type assertions to handle potential type mismatches and ensure type safety
50+
- Take advantage of TypeScript's advanced features like generics, type aliases, and conditional types to write more expressive and reusable code
51+
- Regularly review and refactor TypeScript code to ensure it remains well-structured and adheres to evolving best practices
52+
53+
## React instructions
54+
- UI Components
55+
- We are using the Mantine component library for UI.
56+
- Instructions are at https://mantine.dev/llms.txt
57+
- Avoid using div and other basic HTML elements for layout purposes.
58+
- Prefer Mantine's Box, Group, and Stack components for layout.
59+
- Use Mantine's theme and styling utilities to ensure a consistent and responsive design.
60+
- NEVER use inline styles on a component.
61+
- NEVER use raw hex values (`#ddd`, `#94a3b8`, etc.) or `rgba()` literals for colors in component props or theme files. Use `--inspector-*` CSS custom properties defined in `App.css :root` (e.g., `c: 'var(--inspector-text-primary)'`). If no existing token fits, add one to `:root` first.
62+
- NEVER add a CSS class to a Mantine component when the styles can instead be expressed as component props or a theme variant. CSS classes are a last resort.
63+
- PREFER component props (via `.withProps()`) to CSS for behavioral and visual styles.
64+
- PREFER defining styles as theme variants (via `Component.extend()` in `src/theme/<Component>.ts`) over CSS classes. Each Mantine component with custom variants has its own file in `src/theme/`, exporting a `Theme<Name>` constant. The barrel `src/theme/index.ts` re-exports them all and `theme.ts` imports from the barrel. Flat CSS properties (margin, padding, background, border, color, font-size, etc.) belong in the theme. Only pseudo-selectors, nested child selectors, keyframes, and native HTML element styles belong in App.css.
65+
- App.css must contain ONLY styles that cannot be expressed in the Mantine theme: `@keyframes`, pseudo-selectors (`:hover`, `:focus`), cross-component hover relationships, nested child-element selectors for third-party HTML output (e.g. ReactMarkdown), and styles for native HTML elements (`img`, `iframe`). When refactoring a component, actively move any flat CSS properties out of App.css and into theme variants or `.withProps()` constants.
66+
- NEVER use inline code; instead extract to functions in the same file, exported or located in a shared location if immediately reusable.
67+
- In a component's file, for sub-components:
68+
- ALWAYS use Mantine components for layout and content, configured with props for styling and behavior.
69+
- ALWAYS declare a meaningfully named subcomponent as a constant using `.withProps()` if a component has two or more props.
70+
- NEVER use `Box` for subcomponent constants — `Box` does not support `.withProps()`. Use `Group`, `Stack`, `Flex`, `Text`, `Paper`, `UnstyledButton`, or `Image` instead. Pick the component that best matches the purpose: `Paper` for bordered/surfaced containers, `Text` for any text or content wrapper, `Stack`/`Group`/`Flex` for layout.
71+
- NEVER use a CSS class on a subcomponent constant when the styles can be expressed as a Mantine theme variant instead. Define variants in `src/theme/<Component>.ts` using `Component.extend({ styles: (_theme, props) => { ... } })` and reference them with `variant="variantName"` on the component or in `.withProps()`.
72+
- CSS classes are ONLY acceptable on subcomponents for styles that cannot be expressed as flat CSS-in-JS properties in the theme — specifically: pseudo-selectors (`:hover`, `:focus`), cross-component hover relationships (`.parent:hover .child`), nested child-element selectors (`.wrapper p`, `.wrapper code`), `@keyframes` definitions, and native HTML elements (`img`, `iframe`) that are not Mantine components.
73+
- When a theme variant needs a CSS class for nested/pseudo selectors, use `classNames` in the theme extension to auto-assign it — never add `className` manually in JSX for theme-styled components.
74+
- Example — subcomponent constant with `withProps`:
75+
```tsx
76+
const CardContent = Group.withProps({
77+
flex: 1,
78+
align: 'flex-start',
79+
justify: 'space-between',
80+
wrap: 'nowrap',
81+
});
82+
return <CardContent> ... </CardContent>
83+
```
84+
- Exampletheme variant with auto-assigned className for nested selectors:
85+
```tsx
86+
// src/theme/Paper.ts
87+
export const ThemePaper = Paper.extend({
88+
classNames: (_theme, props) => {
89+
if (props.variant === 'message') return { root: 'message' };
90+
return {};
91+
},
92+
styles: (_theme, props) => {
93+
if (props.variant === 'message') {
94+
return { root: { padding: '1.5rem', borderRadius: 12 } };
95+
}
96+
return { root: {} };
97+
},
98+
}),
99+
100+
// Component.tsx
101+
const MessageContainer = Paper.withProps({ variant: 'message' });
102+
```
103+
- Theme files vs. Storybook element components
104+
- **Theme files** (`src/theme/<Component>.ts`) and **element components** (`src/components/elements/`) serve different purposes and both are needed.
105+
- Theme files customize every instance of a Mantine component app-widedefaults (size, radius), custom variants, and global style overrides. They are applied automatically by `MantineProvider`.
106+
- Element components add domain-specific semantics on top of Mantine primitives. For example, `AnnotationBadge` maps domain concepts (audience, destructive, longRun) to Mantine's styling primitives (color, variant). Storybook documents these domain components for designers and developers.
107+
- Element components MUST import from `@mantine/core`, NOT from `src/theme/`. The theme layer is applied transparently by the providerelements do not need to know about `Theme<Name>` constants.
108+
- NEVER push domain-specific variant logic (e.g., annotation types, transport types) into theme files. Domain variants belong in the element component that owns those semantics. Theme files are for styling that applies to the Mantine primitive globally.

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@./AGENTS.md
2+
@./README.md

clients/web/.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
storybook-static
14+
*.local
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?

clients/web/.storybook/main.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { StorybookConfig } from '@storybook/react-vite';
2+
3+
const config: StorybookConfig = {
4+
"stories": [
5+
"../src/**/*.mdx",
6+
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
7+
],
8+
"addons": [
9+
"@chromatic-com/storybook",
10+
"@storybook/addon-vitest",
11+
"@storybook/addon-a11y",
12+
"@storybook/addon-docs",
13+
"@storybook/addon-onboarding"
14+
],
15+
"framework": "@storybook/react-vite",
16+
previewHead: (head) => `
17+
${head}
18+
<link rel="preconnect" href="https://fonts.googleapis.com" />
19+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
20+
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet" />
21+
`,
22+
};
23+
export default config;

clients/web/.storybook/preview.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { Preview } from '@storybook/react-vite'
2+
import { MantineProvider, useMantineColorScheme } from '@mantine/core'
3+
import { Notifications } from '@mantine/notifications'
4+
import '@mantine/core/styles.css'
5+
import '@mantine/notifications/styles.css'
6+
import '../src/App.css'
7+
import { theme } from '../src/theme/theme'
8+
import { useEffect } from 'react'
9+
10+
// eslint-disable-next-line react-refresh/only-export-components
11+
function ColorSchemeWrapper({
12+
colorScheme,
13+
children,
14+
}: {
15+
colorScheme: 'light' | 'dark'
16+
children: React.ReactNode
17+
}) {
18+
const { setColorScheme } = useMantineColorScheme()
19+
20+
useEffect(() => {
21+
setColorScheme(colorScheme)
22+
}, [colorScheme, setColorScheme])
23+
24+
return <>{children}</>
25+
}
26+
27+
const preview: Preview = {
28+
globalTypes: {
29+
colorScheme: {
30+
description: 'Mantine color scheme',
31+
toolbar: {
32+
title: 'Color Scheme',
33+
icon: 'mirror',
34+
items: [
35+
{ value: 'light', title: 'Light', icon: 'sun' },
36+
{ value: 'dark', title: 'Dark', icon: 'moon' },
37+
],
38+
dynamicTitle: true,
39+
},
40+
},
41+
},
42+
initialGlobals: {
43+
colorScheme: 'light',
44+
},
45+
decorators: [
46+
(Story, context) => {
47+
const isFullscreen = context.parameters?.layout === 'fullscreen'
48+
return (
49+
<MantineProvider theme={theme} defaultColorScheme="light">
50+
<ColorSchemeWrapper colorScheme={context.globals.colorScheme ?? 'light'}>
51+
<Notifications position="top-right" />
52+
{isFullscreen ? (
53+
<div style={{ height: '100vh', overflow: 'hidden' }}>
54+
<Story />
55+
</div>
56+
) : (
57+
<Story />
58+
)}
59+
</ColorSchemeWrapper>
60+
</MantineProvider>
61+
)
62+
},
63+
],
64+
parameters: {
65+
controls: {
66+
matchers: {
67+
color: /(background|color)$/i,
68+
date: /Date$/i,
69+
},
70+
},
71+
a11y: {
72+
test: 'todo',
73+
},
74+
layout: 'centered',
75+
},
76+
}
77+
78+
export default preview
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
2+
import { setProjectAnnotations } from '@storybook/react-vite';
3+
import * as projectAnnotations from './preview';
4+
5+
// This is an important step to apply the right configuration when testing your stories.
6+
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
7+
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);

clients/web/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# React + TypeScript + Vite
2+
3+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4+
5+
Currently, two official plugins are available:
6+
7+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9+
10+
## React Compiler
11+
12+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13+
14+
## Expanding the ESLint configuration
15+
16+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17+
18+
```js
19+
export default defineConfig([
20+
globalIgnores(['dist']),
21+
{
22+
files: ['**/*.{ts,tsx}'],
23+
extends: [
24+
// Other configs...
25+
26+
// Remove tseslint.configs.recommended and replace with this
27+
tseslint.configs.recommendedTypeChecked,
28+
// Alternatively, use this for stricter rules
29+
tseslint.configs.strictTypeChecked,
30+
// Optionally, add this for stylistic rules
31+
tseslint.configs.stylisticTypeChecked,
32+
33+
// Other configs...
34+
],
35+
languageOptions: {
36+
parserOptions: {
37+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
38+
tsconfigRootDir: import.meta.dirname,
39+
},
40+
// other options...
41+
},
42+
},
43+
])
44+
```
45+
46+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47+
48+
```js
49+
// eslint.config.js
50+
import reactX from 'eslint-plugin-react-x'
51+
import reactDom from 'eslint-plugin-react-dom'
52+
53+
export default defineConfig([
54+
globalIgnores(['dist']),
55+
{
56+
files: ['**/*.{ts,tsx}'],
57+
extends: [
58+
// Other configs...
59+
// Enable lint rules for React
60+
reactX.configs['recommended-typescript'],
61+
// Enable lint rules for React DOM
62+
reactDom.configs.recommended,
63+
],
64+
languageOptions: {
65+
parserOptions: {
66+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
67+
tsconfigRootDir: import.meta.dirname,
68+
},
69+
// other options...
70+
},
71+
},
72+
])
73+
```

0 commit comments

Comments
 (0)