Merge pull request #1156 from modelcontextprotocol/add-storybook
feat: Add Storybook component library with presentational components
This commit is contained in:
commit
e9d4e928a9
187 changed files with 20989 additions and 96 deletions
5
.claude/settings.json
Normal file
5
.claude/settings.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"enabledPlugins": {
|
||||
"playwright@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
36
.github/workflows/main.yml
vendored
Normal file
36
.github/workflows/main.yml
vendored
Normal file
|
|
@ -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
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@
|
|||
node_modules
|
||||
dist
|
||||
.env
|
||||
/.playwright-mcp/
|
||||
|
|
|
|||
108
AGENTS.md
Normal file
108
AGENTS.md
Normal file
|
|
@ -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 element components
|
||||
- **Theme files** (`src/theme/<Component>.ts`) and **element components** (`src/components/elements/`) 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`.
|
||||
- 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.
|
||||
- Element components MUST import from `@mantine/core`, NOT from `src/theme/`. The theme layer is applied transparently by the provider — elements 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 element component that owns those semantics. Theme files are for styling that applies to the Mantine primitive globally.
|
||||
2
CLAUDE.md
Normal file
2
CLAUDE.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@./AGENTS.md
|
||||
@./README.md
|
||||
25
clients/web/.gitignore
vendored
Normal file
25
clients/web/.gitignore
vendored
Normal file
|
|
@ -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?
|
||||
23
clients/web/.storybook/main.ts
Normal file
23
clients/web/.storybook/main.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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",
|
||||
previewHead: (head) => `
|
||||
${head}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<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" />
|
||||
`,
|
||||
};
|
||||
export default config;
|
||||
78
clients/web/.storybook/preview.tsx
Normal file
78
clients/web/.storybook/preview.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
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) => {
|
||||
const isFullscreen = context.parameters?.layout === 'fullscreen'
|
||||
return (
|
||||
<MantineProvider theme={theme} defaultColorScheme="light">
|
||||
<ColorSchemeWrapper colorScheme={context.globals.colorScheme ?? 'light'}>
|
||||
<Notifications position="top-right" />
|
||||
{isFullscreen ? (
|
||||
<div style={{ height: '100vh', overflow: 'hidden' }}>
|
||||
<Story />
|
||||
</div>
|
||||
) : (
|
||||
<Story />
|
||||
)}
|
||||
</ColorSchemeWrapper>
|
||||
</MantineProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
a11y: {
|
||||
test: 'todo',
|
||||
},
|
||||
layout: 'centered',
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
7
clients/web/.storybook/vitest.setup.ts
Normal file
7
clients/web/.storybook/vitest.setup.ts
Normal file
|
|
@ -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
clients/web/README.md
Normal file
73
clients/web/README.md
Normal file
|
|
@ -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
clients/web/chromatic.config.json
Normal file
6
clients/web/chromatic.config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"onlyChanged": true,
|
||||
"projectId": "Project:69bf2823055478c6c6f4d991",
|
||||
"storybookBaseDir": "clients/web",
|
||||
"zip": true
|
||||
}
|
||||
23
clients/web/eslint.config.js
Normal file
23
clients/web/eslint.config.js
Normal file
|
|
@ -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"]])
|
||||
16
clients/web/index.html
Normal file
16
clients/web/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!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" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<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" />
|
||||
<title>MCP Inspector</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
6335
clients/web/package-lock.json
generated
Normal file
6335
clients/web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
53
clients/web/package.json
Normal file
53
clients/web/package.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/inspector-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write src",
|
||||
"format:check": "prettier --check src",
|
||||
"preview": "vite preview",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@mantine/core": "^8.3.17",
|
||||
"@mantine/form": "^8.3.17",
|
||||
"@mantine/hooks": "^8.3.17",
|
||||
"@mantine/notifications": "^8.3.17",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-icons": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@storybook/addon-a11y": "^10.2.19",
|
||||
"@storybook/addon-docs": "^10.2.19",
|
||||
"@storybook/addon-onboarding": "^10.2.19",
|
||||
"@storybook/addon-vitest": "^10.2.19",
|
||||
"@storybook/react-vite": "^10.2.19",
|
||||
"@types/node": "^24.12.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.0",
|
||||
"@vitest/browser-playwright": "^4.1.0",
|
||||
"@vitest/coverage-v8": "^4.1.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"eslint-plugin-storybook": "^10.2.19",
|
||||
"globals": "^17.4.0",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "^3.8.1",
|
||||
"storybook": "^10.2.19",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"vite": "^8.0.0",
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
1
clients/web/public/favicon.svg
Normal file
1
clients/web/public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
24
clients/web/public/icons.svg
Normal file
24
clients/web/public/icons.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
122
clients/web/src/App.css
Normal file
122
clients/web/src/App.css
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* App.css — Global styles that CANNOT be expressed in the Mantine theme.
|
||||
*
|
||||
* Only the following belong here:
|
||||
* - :root CSS custom properties (--inspector-* design tokens)
|
||||
* - @keyframes animations
|
||||
* - Pseudo-selectors (:hover, :focus) that cross component boundaries
|
||||
* - Nested child-element selectors for third-party HTML output
|
||||
* - Styles for native HTML elements (img, iframe, etc.)
|
||||
*
|
||||
* Everything else (margin, padding, background, border, color, font-size)
|
||||
* belongs in the Mantine theme via Component.extend() or .withProps().
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ── Brand ─────────────────────────────────────────────── */
|
||||
--inspector-brand-primary: var(--mantine-primary-color-filled);
|
||||
|
||||
/* ── Text ──────────────────────────────────────────────── */
|
||||
--inspector-text-primary: var(--mantine-color-text);
|
||||
--inspector-text-secondary: var(--mantine-color-dimmed);
|
||||
--inspector-text-inverse: var(--mantine-color-white);
|
||||
|
||||
/* ── Surfaces ──────────────────────────────────────────── */
|
||||
--inspector-surface-body: var(--mantine-color-dark-9);
|
||||
--inspector-surface-card: var(--mantine-color-gray-0);
|
||||
--inspector-surface-code: var(--mantine-color-white);
|
||||
|
||||
/* ── Borders ───────────────────────────────────────────── */
|
||||
--inspector-border-default: var(--mantine-color-default-border);
|
||||
--inspector-border-subtle: var(--mantine-color-gray-3);
|
||||
|
||||
/* ── Connection status ─────────────────────────────────── */
|
||||
--inspector-status-connected: var(--mantine-color-green-6);
|
||||
--inspector-status-connecting: var(--mantine-color-yellow-6);
|
||||
--inspector-status-disconnected: var(--mantine-color-gray-5);
|
||||
--inspector-status-failed: var(--mantine-color-red-6);
|
||||
|
||||
/* ── Log levels (RFC 5424) ─────────────────────────────── */
|
||||
--inspector-log-debug: var(--mantine-color-gray-6);
|
||||
--inspector-log-info: var(--mantine-color-blue-6);
|
||||
--inspector-log-notice: var(--mantine-color-teal-6);
|
||||
--inspector-log-warning: var(--mantine-color-yellow-6);
|
||||
--inspector-log-error: var(--mantine-color-red-6);
|
||||
--inspector-log-critical: var(--mantine-color-red-8);
|
||||
--inspector-log-alert: var(--mantine-color-red-9);
|
||||
--inspector-log-emergency: var(--mantine-color-red-9);
|
||||
|
||||
/* ── Annotations ───────────────────────────────────────── */
|
||||
--inspector-annotation-destructive: var(--mantine-color-red-6);
|
||||
--inspector-annotation-long-run: var(--mantine-color-yellow-6);
|
||||
--inspector-annotation-read-only: var(--mantine-color-dark-6);
|
||||
--inspector-annotation-audience: var(--mantine-color-gray-6);
|
||||
--inspector-annotation-priority-high: var(--mantine-color-orange-6);
|
||||
--inspector-annotation-priority-medium: var(--mantine-color-yellow-6);
|
||||
--inspector-annotation-priority-low: var(--mantine-color-green-6);
|
||||
|
||||
/* ── Task status ───────────────────────────────────────── */
|
||||
--inspector-task-waiting: var(--mantine-color-gray-5);
|
||||
--inspector-task-running: var(--mantine-color-blue-6);
|
||||
--inspector-task-completed: var(--mantine-color-green-6);
|
||||
--inspector-task-failed: var(--mantine-color-red-6);
|
||||
--inspector-task-cancelled: var(--mantine-color-yellow-6);
|
||||
|
||||
/* ── History ───────────────────────────────────────────── */
|
||||
--inspector-history-success: var(--mantine-color-green-6);
|
||||
--inspector-history-error: var(--mantine-color-red-6);
|
||||
}
|
||||
|
||||
/* Dark mode overrides — tokens that need adjustment in dark scheme */
|
||||
[data-mantine-color-scheme="dark"] {
|
||||
--inspector-surface-card: var(--mantine-color-gray-9);
|
||||
--inspector-surface-code: var(--mantine-color-dark-9);
|
||||
--inspector-border-subtle: var(--mantine-color-dark-4);
|
||||
}
|
||||
|
||||
/* ── Animations ──────────────────────────────────────────── */
|
||||
|
||||
@keyframes inspector-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes inspector-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Status dot animations ───────────────────────────────── */
|
||||
|
||||
.inspector-pulse {
|
||||
animation: inspector-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.inspector-spin {
|
||||
animation: inspector-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* ── List item hover ────────────────────────────────────── */
|
||||
|
||||
.list-item:hover {
|
||||
background-color: var(--mantine-color-gray-1);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme="dark"] .list-item:hover {
|
||||
background-color: var(--mantine-color-dark-5);
|
||||
}
|
||||
|
||||
/* ── Grid alignment ─────────────────────────────────────── */
|
||||
|
||||
.grid-align-start {
|
||||
align-items: start;
|
||||
}
|
||||
42
clients/web/src/App.tsx
Normal file
42
clients/web/src/App.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import {
|
||||
Title,
|
||||
Text,
|
||||
Container,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
useComputedColorScheme,
|
||||
ActionIcon,
|
||||
Group,
|
||||
} from "@mantine/core";
|
||||
|
||||
function App() {
|
||||
const { setColorScheme } = useMantineColorScheme();
|
||||
const computedColorScheme = useComputedColorScheme("light");
|
||||
|
||||
const toggleColorScheme = () => {
|
||||
setColorScheme(computedColorScheme === "dark" ? "light" : "dark");
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size="sm" py="xl">
|
||||
<Stack align="center" gap="md">
|
||||
<Group>
|
||||
<Title order={1}>MCP Inspector</Title>
|
||||
<ActionIcon
|
||||
onClick={toggleColorScheme}
|
||||
variant="default"
|
||||
size="lg"
|
||||
aria-label="Toggle color scheme"
|
||||
>
|
||||
{computedColorScheme === "dark" ? "\u2600" : "\u263E"}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Text c="var(--inspector-text-secondary)">
|
||||
Web client for the Model Context Protocol Inspector
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { AnnotationBadge } from "./AnnotationBadge";
|
||||
|
||||
const meta: Meta<typeof AnnotationBadge> = {
|
||||
title: "Elements/AnnotationBadge",
|
||||
component: AnnotationBadge,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AnnotationBadge>;
|
||||
|
||||
export const Audience: Story = {
|
||||
args: {
|
||||
label: "user",
|
||||
variant: "audience",
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnly: Story = {
|
||||
args: {
|
||||
label: "read-only",
|
||||
variant: "readOnly",
|
||||
},
|
||||
};
|
||||
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
label: "destructive",
|
||||
variant: "destructive",
|
||||
},
|
||||
};
|
||||
|
||||
export const LongRun: Story = {
|
||||
args: {
|
||||
label: "long-run",
|
||||
variant: "longRun",
|
||||
},
|
||||
};
|
||||
|
||||
export const PriorityHigh: Story = {
|
||||
args: {
|
||||
label: "priority: high",
|
||||
variant: "priority",
|
||||
},
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
label: "custom",
|
||||
variant: "default",
|
||||
},
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue