Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1801f55
* Refactored menu on spec pages
cliffhall Mar 10, 2026
285d5c0
Apply suggestion from @olaservo
olaservo Mar 11, 2026
b18b81b
spec: Add Storybook component plan with 43 components across 4 tiers
cliffhall Mar 17, 2026
fcd0d15
* Added base project to clients/web
cliffhall Mar 17, 2026
39ef184
feat: Add Mantine theme, 12 atom components, and 25 molecule componen…
cliffhall Mar 17, 2026
75f6f64
feat: Add 7 organism screen components with Storybook stories
cliffhall Mar 17, 2026
019e104
* Added AGENTS.md
cliffhall Mar 17, 2026
d53c3d3
feat: Add HomeLayout and ConnectedLayout components with Storybook st…
cliffhall Mar 17, 2026
0471443
fix: Use ServerListScreen with real ServerCards in HomeLayout stories
cliffhall Mar 17, 2026
4b28a69
formatting
cliffhall Mar 17, 2026
707cb19
update main.yml
cliffhall Mar 17, 2026
c065db0
update main.yml
cliffhall Mar 17, 2026
9102d20
update main.yml
cliffhall Mar 17, 2026
f7217fa
update main.yml
cliffhall Mar 17, 2026
a82444b
update package.json, package-lock.json
cliffhall Mar 17, 2026
bd4606a
add guidance on theme files vs atom components to AGENTS.md
cliffhall Mar 21, 2026
5d35a5f
add chromatic.config.json
cliffhall Mar 22, 2026
8d434e2
fix HistoryEntry children prop conflict and overlay CopyButton in Con…
cliffhall Mar 22, 2026
abb2d57
add tsconfig for storybook files and fix ImagePreview story
cliffhall Mar 22, 2026
4a4818e
fix InlineError blank space and ElicitationUrlPanel URL overflow
cliffhall Mar 22, 2026
1da76c5
storybook component fixes and improvements
cliffhall Mar 23, 2026
3c25bdf
refine InlineError, ServerCard, HistoryEntry, and LoggingScreen
cliffhall Mar 23, 2026
46dd1b0
fix dark mode, storybook config, and minor UI issues
cliffhall Mar 23, 2026
0cb7883
fix server cards stretching to fill row height
cliffhall Mar 23, 2026
08c42a2
fix dark/light mode surface colors and Remove button color
cliffhall Mar 23, 2026
f674ce0
refine UI layout, theming, and component consistency across screens
cliffhall Mar 23, 2026
bca31d1
refine UI layout, stories, and component consistency across screens
cliffhall Mar 23, 2026
fb4ca78
fix lint error, build error, and apply formatting across components
cliffhall Mar 24, 2026
04c2309
refactor atom components to use .withProps() subcomponents and extrac…
cliffhall Mar 24, 2026
27186c2
refactor molecule components to use .withProps() subcomponents and ex…
cliffhall Mar 24, 2026
69b17ad
Update AGENTS.md
cliffhall Mar 24, 2026
ca6de91
refactor organism components to use .withProps() subcomponents and ex…
cliffhall Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Run install, format, lint, build, and test on every push or pull request

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20.x'
cache: 'npm'

- name: Install dependencies
working-directory: ./clients/web
run: npm install

- name: Check formatting
working-directory: ./clients/web
run: npm run format:check

- name: Run linter
working-directory: ./clients/web
run: npm run lint

- name: Run Build
working-directory: ./clients/web
run: npm run build

# - name: Run tests
# run: npm run test
108 changes: 108 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Inspector V2

This is an application for inspecting MCP servers. Has three incarnations, Web, TUI, and CLI.

## Project Structure

```
inspector/
├── clients/
│ ├── web/ # Web client code
│ ├── cli/ # CLI client code
│ ├── tui/ # TUI client code
│ ├── launcher/ # Shared launcher
├── core/ # Shared core code
├── soecification/ # Build specification
...
```

## Maintenance Rules

### Keep documentation files up to date
- 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
- When the structure of the project, the tech stack, or the developer setup changes, update appropriate README.md files with the details.
- When adding new commands, dependencies, or architectural patterns, update the relevant sections of appropriate README.md files as well.
- When rules for implementation and testing change, update this file AGENTS.md

### Always test new or modified coderea
- Ensure all code has corresponding tests
- Ensure test coverage for each file is at least 90%
- In unit tests that expect error output, suppress it from the console

### Responding to Code Reviews
- When asked to respond to a code review of a PR,
- it is not necessary to implement all suggestions
- you are free to implement suggestions in a different way or to ignore if there is a good reason
- after making the changes, respond to each review comment with what was done (or why it was ignored)

### Lint-fixed, Formatted code
- Ensure linting and formatting are applied after every change
- ALWAYS do `npm run format`, `npm run lint`, `npm run typecheck`, `npm run test` and `npm run build` before pushing any changes

### Typescript instructions
- Use TypeScript for all new code
- Follow TypeScript best practices and coding standards
- NEVER use 'any' as a type
- 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.
- Utilize type annotations and interfaces to improve code clarity and maintainability
- Leverage TypeScript's type inference and static analysis features for better code quality and refactoring
- Use type guards and type assertions to handle potential type mismatches and ensure type safety
- Take advantage of TypeScript's advanced features like generics, type aliases, and conditional types to write more expressive and reusable code
- Regularly review and refactor TypeScript code to ensure it remains well-structured and adheres to evolving best practices

## React instructions
- UI Components
- We are using the Mantine component library for UI.
- Instructions are at https://mantine.dev/llms.txt
- Avoid using div and other basic HTML elements for layout purposes.
- Prefer Mantine's Box, Group, and Stack components for layout.
- Use Mantine's theme and styling utilities to ensure a consistent and responsive design.
- NEVER use inline styles on a component.
- 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.
- 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.
- PREFER component props (via `.withProps()`) to CSS for behavioral and visual styles.
- 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.
- 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.
- NEVER use inline code; instead extract to functions in the same file, exported or located in a shared location if immediately reusable.
- In a component's file, for sub-components:
- ALWAYS use Mantine components for layout and content, configured with props for styling and behavior.
- ALWAYS declare a meaningfully named subcomponent as a constant using `.withProps()` if a component has two or more props.
- 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.
- 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()`.
- 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.
- 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.
- Example — subcomponent constant with `withProps`:
```tsx
const CardContent = Group.withProps({
flex: 1,
align: 'flex-start',
justify: 'space-between',
wrap: 'nowrap',
});
return <CardContent> ... </CardContent>
```
- Example — theme variant with auto-assigned className for nested selectors:
```tsx
// src/theme/Paper.ts
export const ThemePaper = Paper.extend({
classNames: (_theme, props) => {
if (props.variant === 'message') return { root: 'message' };
return {};
},
styles: (_theme, props) => {
if (props.variant === 'message') {
return { root: { padding: '1.5rem', borderRadius: 12 } };
}
return { root: {} };
},
}),

// Component.tsx
const MessageContainer = Paper.withProps({ variant: 'message' });
```
- Theme files vs. Storybook atom components
- **Theme files** (`src/theme/<Component>.ts`) and **atom components** (`src/components/atoms/`) serve different purposes and both are needed.
- Theme files customize every instance of a Mantine component app-wide — defaults (size, radius), custom variants, and global style overrides. They are applied automatically by `MantineProvider`.
- Atom 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.
- Atom components MUST import from `@mantine/core`, NOT from `src/theme/`. The theme layer is applied transparently by the provider — atoms do not need to know about `Theme<Name>` constants.
- NEVER push domain-specific variant logic (e.g., annotation types, transport types) into theme files. Domain variants belong in the atom component that owns those semantics. Theme files are for styling that applies to the Mantine primitive globally.
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@./AGENTS.md
@./README.md
25 changes: 25 additions & 0 deletions clients/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
storybook-static
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
17 changes: 17 additions & 0 deletions clients/web/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
"stories": [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
],
"addons": [
"@chromatic-com/storybook",
"@storybook/addon-vitest",
"@storybook/addon-a11y",
"@storybook/addon-docs",
"@storybook/addon-onboarding"
],
"framework": "@storybook/react-vite"
};
export default config;
69 changes: 69 additions & 0 deletions clients/web/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Preview } from '@storybook/react-vite'
import { MantineProvider, useMantineColorScheme } from '@mantine/core'
import { Notifications } from '@mantine/notifications'
import '@mantine/core/styles.css'
import '@mantine/notifications/styles.css'
import '../src/App.css'
import { theme } from '../src/theme/theme'
import { useEffect } from 'react'

// eslint-disable-next-line react-refresh/only-export-components
function ColorSchemeWrapper({
colorScheme,
children,
}: {
colorScheme: 'light' | 'dark'
children: React.ReactNode
}) {
const { setColorScheme } = useMantineColorScheme()

useEffect(() => {
setColorScheme(colorScheme)
}, [colorScheme, setColorScheme])

return <>{children}</>
}

const preview: Preview = {
globalTypes: {
colorScheme: {
description: 'Mantine color scheme',
toolbar: {
title: 'Color Scheme',
icon: 'mirror',
items: [
{ value: 'light', title: 'Light', icon: 'sun' },
{ value: 'dark', title: 'Dark', icon: 'moon' },
],
dynamicTitle: true,
},
},
},
initialGlobals: {
colorScheme: 'light',
},
decorators: [
(Story, context) => (
<MantineProvider theme={theme} defaultColorScheme="light">
<ColorSchemeWrapper colorScheme={context.globals.colorScheme ?? 'light'}>
<Notifications position="top-right" />
<Story />
</ColorSchemeWrapper>
</MantineProvider>
),
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
test: 'todo',
},
layout: 'centered',
},
}

export default preview
7 changes: 7 additions & 0 deletions clients/web/.storybook/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from '@storybook/react-vite';
import * as projectAnnotations from './preview';

// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
73 changes: 73 additions & 0 deletions clients/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)

## React Compiler

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

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

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:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
6 changes: 6 additions & 0 deletions clients/web/chromatic.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"onlyChanged": true,
"projectId": "Project:69bf2823055478c6c6f4d991",
"storybookBaseDir": "clients/web",
"zip": true
}
23 changes: 23 additions & 0 deletions clients/web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook";

import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([globalIgnores(['dist', 'storybook-static']), {
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
}, ...storybook.configs["flat/recommended"]])
13 changes: 13 additions & 0 deletions clients/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MCP Inspector</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading