Merge pull request #1647 from modelcontextprotocol/1640-v2-publish-job
Add release-gated npm publish job for the single v2 package
This commit is contained in:
commit
483981064a
4 changed files with 197 additions and 76 deletions
79
.github/workflows/main.yml
vendored
79
.github/workflows/main.yml
vendored
|
|
@ -1,6 +1,12 @@
|
||||||
name: Run install, format, lint, build, and test on every push
|
name: Run install, format, lint, build, and test on every push
|
||||||
|
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
# A published GitHub release triggers the `publish` job below. The release's
|
||||||
|
# target commit determines which workflow runs — so this only publishes when a
|
||||||
|
# release is cut from a commit that carries this (v2) workflow.
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -69,3 +75,74 @@ jobs:
|
||||||
- name: Run Storybook play-function tests
|
- name: Run Storybook play-function tests
|
||||||
working-directory: ./clients/web
|
working-directory: ./clients/web
|
||||||
run: npm run test:storybook
|
run: npm run test:storybook
|
||||||
|
|
||||||
|
# Publish the single `@modelcontextprotocol/inspector` package to npm on a
|
||||||
|
# published GitHub release. v2 is not an npm workspace, so there is no
|
||||||
|
# `publish-all` / `--workspaces` (v1) — just one `npm publish`, whose `prepack`
|
||||||
|
# (`npm run build`) builds every client bundle into the `files` allowlist.
|
||||||
|
# `pack:verify` runs first as the pre-publish gate: it builds, packs the real
|
||||||
|
# tarball, installs it into a clean throwaway consumer, and drives the
|
||||||
|
# installed `mcp-inspector` bin (web/cli/tui) end to end — so a broken package
|
||||||
|
# is caught before it reaches npm rather than after.
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
environment: release
|
||||||
|
needs: build
|
||||||
|
# Serialize publishes so two releases cut in quick succession can't run
|
||||||
|
# overlapping `npm publish`es. Never cancel an in-flight publish.
|
||||||
|
concurrency:
|
||||||
|
group: publish-npm
|
||||||
|
cancel-in-progress: false
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
# Required for npm provenance (`--provenance` mints a signed attestation
|
||||||
|
# via GitHub's OIDC token). The repo is public, so provenance is available.
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '22.x'
|
||||||
|
cache: 'npm'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Assert release tag matches package version
|
||||||
|
# `npm publish` ships whatever `version` is in the root package.json,
|
||||||
|
# regardless of the release's tag. Fail fast (before the heavy install /
|
||||||
|
# pack:verify) if they disagree — e.g. a release drafted without running
|
||||||
|
# `npm version`, or cut from the wrong commit — since publishing is
|
||||||
|
# irreversible. Tolerates the conventional leading `v` (npm version tags
|
||||||
|
# as `vX.Y.Z`). The tag arrives via `env:` (not spliced into the script)
|
||||||
|
# to avoid the script-injection surface of interpolating `${{ }}` into a
|
||||||
|
# `run:` block.
|
||||||
|
env:
|
||||||
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
|
run: |
|
||||||
|
PKG="$(node -p "require('./package.json').version")"
|
||||||
|
if [ "${TAG#v}" != "$PKG" ]; then
|
||||||
|
echo "Release tag '$TAG' does not match package.json version '$PKG'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies (root + all clients)
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Verify the publishable tarball end to end
|
||||||
|
# Builds, `npm pack`s, installs the tarball into a clean consumer, and
|
||||||
|
# drives the installed bin. Needs registry access to pull the tarball's
|
||||||
|
# runtime deps — available here. `smoke:tui` inside it self-skips on CI.
|
||||||
|
run: npm run pack:verify
|
||||||
|
|
||||||
|
- name: Publish to npm (single package, with provenance)
|
||||||
|
# `prepack` (`npm run build`) rebuilds the client bundles into the tarball.
|
||||||
|
# The build runs three times on this path (build job → pack:verify →
|
||||||
|
# prepack); the redundancy is intentional — each is a clean-tree rebuild
|
||||||
|
# and the `prepack` one is what actually populates the published tarball,
|
||||||
|
# so don't "optimize" it away.
|
||||||
|
run: npm publish --access public --provenance
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
|
||||||
40
AGENTS.md
40
AGENTS.md
|
|
@ -123,49 +123,61 @@ All work should be driven by items on the project board.
|
||||||
- Run format, lint, typecheck, build, and test — ensure all checks pass
|
- Run format, lint, typecheck, build, and test — ensure all checks pass
|
||||||
- Open a PR against the matching base branch (`main` for v1, `v2/main` for v2) and set the item's Status to **In review**
|
- Open a PR against the matching base branch (`main` for v1, `v2/main` for v2) and set the item's Status to **In review**
|
||||||
- **Link the PR to its issue.** The PR body's **first line must be `Closes #<ISSUE_NUMBER>`**. ⚠️ Note: closing keywords only auto-link/auto-close for PRs targeting the repo's **default branch** (`main`). Because v2 PRs target `v2/main` (a non-default branch), `Closes #N` there is only a cross-reference — it will **not** create a hard link or close the issue on merge. (There is no `gh` flag for manual linking — `gh pr edit` has no `--add-issue`; closing keywords are the only mechanism GitHub exposes, and they're gated to the default branch.)
|
- **Link the PR to its issue.** The PR body's **first line must be `Closes #<ISSUE_NUMBER>`**. ⚠️ Note: closing keywords only auto-link/auto-close for PRs targeting the repo's **default branch** (`main`). Because v2 PRs target `v2/main` (a non-default branch), `Closes #N` there is only a cross-reference — it will **not** create a hard link or close the issue on merge. (There is no `gh` flag for manual linking — `gh pr edit` has no `--add-issue`; closing keywords are the only mechanism GitHub exposes, and they're gated to the default branch.)
|
||||||
- **On merge of a v2 PR, manually close its issue and move the board item to Done** (option id `1bbc5632`), since auto-close won't fire on `v2/main`. Keep the `Closes #N` line anyway so the issues close automatically if/when `v2/main` is eventually merged to `main`.
|
- **On merge of a v2 PR, manually close its issue and move the board item to Done** (option id `248a3910`), since auto-close won't fire on `v2/main`. Keep the `Closes #N` line anyway so the issues close automatically if/when `v2/main` is eventually merged to `main`.
|
||||||
- If new tasks are discovered or requested during development, create issues and add them to the board.
|
- If new tasks are discovered or requested during development, create issues and add them to the board.
|
||||||
|
|
||||||
#### V2 board (#28) `gh` recipes
|
#### V2 board (#28) `gh` recipes
|
||||||
|
|
||||||
The board is an **org project**, so all commands use `--owner modelcontextprotocol` and the numeric project `28`. The IDs below are stable; if a command rejects one, re-fetch with `gh project field-list 28 --owner modelcontextprotocol --format json`.
|
The board is an **org project**, so all commands use `--owner modelcontextprotocol` and the numeric project `28`. The project node id and Status field id are stable. **The Status *option* ids are NOT stable — they are regenerated whenever the Status field's option list is edited** (see the ⚠️ hazard below). If any option id here is rejected, re-fetch the current set with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh project field-list 28 --owner modelcontextprotocol --format json \
|
||||||
|
| jq '.fields[] | select(.name=="Status") | .options'
|
||||||
|
```
|
||||||
|
|
||||||
| Thing | ID |
|
| Thing | ID |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Project node ID | `PVT_kwDOCt2Azc4BJVxt` |
|
| Project node ID | `PVT_kwDOCt2Azc4BJVxt` |
|
||||||
| Status field ID | `PVTSSF_lADOCt2Azc4BJVxtzg5iI8c` |
|
| Status field ID | `PVTSSF_lADOCt2Azc4BJVxtzg5iI8c` |
|
||||||
|
|
||||||
Status option IDs (`--single-select-option-id`):
|
Status option IDs (`--single-select-option-id`) — **last verified 2026-07-09**:
|
||||||
|
|
||||||
| Status | Option ID |
|
| Status | Option ID |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Backlog | `6080ca99` |
|
| Todo | `fbdaf21e` |
|
||||||
| Building CLI / TUI / CORE | `fe170c62` |
|
| Building CLI / TUI / CORE | `4ac261ee` |
|
||||||
| Building Web | `4faeae7a` |
|
| Building Web | `c28da89f` |
|
||||||
| MCP Apps Extension | `588c6a63` |
|
| MCP Apps Extension | `73d0b807` |
|
||||||
| In progress | `d43284fe` |
|
| SDK V2 + New Spec | `1bbb6f57` |
|
||||||
| In review | `fb2103f2` |
|
| In Progress | `195df262` |
|
||||||
| Done | `1bbc5632` |
|
| In Review | `159c8a02` |
|
||||||
|
| Done | `248a3910` |
|
||||||
|
|
||||||
Use **In progress** for general work, one of the **Building** statuses (or **MCP Apps Extension**) while actively coding that surface, **In review** once a PR is open, and **Done** on merge.
|
Use **Todo** for approved-but-not-started work, **In Progress** for general active work, one of the **Building** statuses (or **MCP Apps Extension** / **SDK V2 + New Spec**) while actively coding that surface, **In Review** once a PR is open, and **Done** on merge.
|
||||||
|
|
||||||
|
> ⚠️ **Never add, rename, or remove a board column (Status option) with the `updateProjectV2Field` GraphQL mutation unless you pass every existing option's `id`.** That mutation does a **full replace** of the option list: if you resend options by name/color/description but omit their `id`s, GitHub **deletes all existing options and mints new ones**, which **orphans the Status of every card on the board** (all items go blank) *and* invalidates every option id in the table above. This has happened once (required reconstructing ~197 items' statuses by inference). Safe alternatives, in order of preference:
|
||||||
|
> 1. **Add/rename/remove a column in the GitHub web UI** (Project #28 → Status field settings). This preserves ids of untouched options and never orphans cards.
|
||||||
|
> 2. If you must script it, first `gh api graphql` the current options **with their `id`s**, then call `updateProjectV2Field` echoing back every existing option **including its `id`**, appending only the new one. Verify afterward that no card lost its Status.
|
||||||
|
>
|
||||||
|
> `gh project item-add` and `gh project item-edit` are always safe — they set a card's value and never touch the field schema. When option ids change for any reason, **re-verify and update the table above** (and the `248a3910` / `195df262` references in the recipes below and the merge step above).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Add an issue to the board — prints the item id (PVTI_…); capture it.
|
# 1. Add an issue to the board — prints the item id (PVTI_…); capture it.
|
||||||
gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json
|
gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json
|
||||||
|
|
||||||
# 2. Set its Status (here: In progress). Use the option id from the table above.
|
# 2. Set its Status (here: In Progress). Use the option id from the table above.
|
||||||
gh project item-edit \
|
gh project item-edit \
|
||||||
--project-id PVT_kwDOCt2Azc4BJVxt \
|
--project-id PVT_kwDOCt2Azc4BJVxt \
|
||||||
--id <item-id-from-step-1> \
|
--id <item-id-from-step-1> \
|
||||||
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c \
|
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c \
|
||||||
--single-select-option-id d43284fe
|
--single-select-option-id 195df262
|
||||||
```
|
```
|
||||||
|
|
||||||
The one-liner that does both, capturing the item id (use the option id for the status you want):
|
The one-liner that does both, capturing the item id (use the option id for the status you want):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ITEM_ID=$(gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json --jq '.id')
|
ITEM_ID=$(gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json --jq '.id')
|
||||||
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id d43284fe
|
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id 195df262
|
||||||
```
|
```
|
||||||
|
|
||||||
### Always test new or modified code
|
### Always test new or modified code
|
||||||
|
|
|
||||||
16
README.md
16
README.md
|
|
@ -148,6 +148,22 @@ The root `package.json` `"files"` allowlist is the source of truth for the tarba
|
||||||
|
|
||||||
The `smoke:*` scripts run against the in-repo build tree, which is **not** the published package. `npm run pack:verify` (`scripts/pack-and-verify.mjs`) closes that gap: it builds, `npm pack`s the publishable tarball (asserting no source maps ship and that the runtime-required files are present), installs the tarball into a **clean throwaway consumer** — a fresh temp directory where it runs a real `npm install <tgz>` (pulls runtime deps, runs `postinstall`), exactly as `npx @modelcontextprotocol/inspector` would — and drives the installed `mcp-inspector` bin end to end: `--help` dispatch, a real `--cli tools/list` over stdio, and a prod `--web` boot that must serve `/` from the shipped `dist`. It catches "works in `--dev`, breaks under `npx …`" path/packaging failures. It requires network access (the install pulls deps), so it is a local / release check, **not** part of the fast `validate`/`ci` loop.
|
The `smoke:*` scripts run against the in-repo build tree, which is **not** the published package. `npm run pack:verify` (`scripts/pack-and-verify.mjs`) closes that gap: it builds, `npm pack`s the publishable tarball (asserting no source maps ship and that the runtime-required files are present), installs the tarball into a **clean throwaway consumer** — a fresh temp directory where it runs a real `npm install <tgz>` (pulls runtime deps, runs `postinstall`), exactly as `npx @modelcontextprotocol/inspector` would — and drives the installed `mcp-inspector` bin end to end: `--help` dispatch, a real `--cli tools/list` over stdio, and a prod `--web` boot that must serve `/` from the shipped `dist`. It catches "works in `--dev`, breaks under `npx …`" path/packaging failures. It requires network access (the install pulls deps), so it is a local / release check, **not** part of the fast `validate`/`ci` loop.
|
||||||
|
|
||||||
|
### Cutting a release
|
||||||
|
|
||||||
|
Publishing is automated by the `publish` job in [`.github/workflows/main.yml`](.github/workflows/main.yml), gated on a **published GitHub release** (`github.event_name == 'release'`). On release it runs `npm run pack:verify` as the pre-publish gate, then `npm publish --access public --provenance` — a single `npm publish` (v2 is not an npm workspace, so there is no v1-style `publish-all`/`--workspaces`), with a signed provenance attestation via GitHub OIDC (`id-token: write`, `environment: release`, `NPM_TOKEN`).
|
||||||
|
|
||||||
|
Because there is **one version number** (only the root `package.json` has one — the clients carry none, so there is nothing to keep in sync and no `check-version` step), the release flow is just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm version <major|minor|patch> # bumps the root package.json + tags
|
||||||
|
git push --follow-tags
|
||||||
|
# then draft & publish a GitHub Release for that tag → triggers `publish`
|
||||||
|
```
|
||||||
|
|
||||||
|
The release's target commit selects which workflow runs, so this only publishes when a release is cut from a commit carrying this (v2) workflow.
|
||||||
|
|
||||||
|
> **Docker / GHCR is not wired up yet.** v1 published a container image to GHCR, but v2 has no Dockerfile (its `client/server/cli` layout is gone). A v2 image + GHCR job is tracked separately in [#1646](https://github.com/modelcontextprotocol/inspector/issues/1646).
|
||||||
|
|
||||||
## Contributing — `AGENTS.md` and `CLAUDE.md`
|
## Contributing — `AGENTS.md` and `CLAUDE.md`
|
||||||
|
|
||||||
**[`AGENTS.md`](./AGENTS.md) is the contract for changing this codebase, and it applies to humans and AI agents alike.** It is not agent-only boilerplate — it holds the project's real conventions: the issue-and-board workflow, branch/label rules, the TypeScript and Mantine/React standards, the testing and coverage requirements, and the mandatory pre-push gate. Read it before making changes, and keep it up to date when you change structure, tooling, or rules.
|
**[`AGENTS.md`](./AGENTS.md) is the contract for changing this codebase, and it applies to humans and AI agents alike.** It is not agent-only boilerplate — it holds the project's real conventions: the issue-and-board workflow, branch/label rules, the TypeScript and Mantine/React standards, the testing and coverage requirements, and the mandatory pre-push gate. Read it before making changes, and keep it up to date when you change structure, tooling, or rules.
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,93 @@
|
||||||
# React + TypeScript + Vite
|
# MCP Inspector Web Client
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
The browser incarnation of the Inspector: a **Vite + React + [Mantine](https://mantine.dev)** single-page app backed by a small **Node (Hono)** server. The SPA is presentational — it renders data and fires callbacks; all MCP state comes from the shared `@inspector/core` hooks. The backend proxies MCP connections, serves the built SPA, and exposes `/api/*`.
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
This README covers what's specific to the web client. For the repo-wide picture (the `@inspector/core` shared package, the "dumb components" philosophy, the top-level `validate`/`coverage`/`ci` scripts, and publishing), see the [root README](../../README.md).
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
## Two halves: `src/` (browser) and `server/` (Node)
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
||||||
|
|
||||||
## React Compiler
|
| Path | Runs in | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `src/` | browser | The React SPA — components, hooks, theme, entry (`main.tsx`). |
|
||||||
|
| `server/` | Node | The dev/prod backend wiring (never imported by the browser). |
|
||||||
|
|
||||||
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).
|
The `server/` directory holds the Node-only backend:
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
- **`vite-hono-plugin.ts`** — mounts the Hono `/api/*` middleware onto the Vite dev server (so `npm run dev` has a live backend).
|
||||||
|
- **`server.ts`** — the standalone Hono production server (serves `dist/` + `/api/*`).
|
||||||
|
- **`run-web.ts`** / **`start-vite-dev-server.ts`** — entry points the launcher calls for prod `--web` and `--web --dev`.
|
||||||
|
- **`web-server-config.ts`** — env parsing, the `GET /api/config` payload, the startup banner.
|
||||||
|
- **`inject-auth-token.ts`** — embeds the API token into the served `index.html` (see [Auth token](#auth-token)).
|
||||||
|
- **`sandbox-controller.ts`** — the MCP Apps sandbox HTTP server; **`ensure-web-build.ts`** — builds `dist/` on demand for prod `--web`; **`vite-base-config.ts`** — shared `optimizeDeps` exclusions.
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
## Development
|
||||||
|
|
||||||
```js
|
```bash
|
||||||
export default defineConfig([
|
npm run dev # Vite dev server + Hono /api middleware, HMR
|
||||||
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:
|
For the launcher-driven prod/dev flows (`npm run web` / `web:dev`), see the root README — those run the built launcher.
|
||||||
|
|
||||||
```js
|
## Build
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
```bash
|
||||||
globalIgnores(['dist']),
|
npm run build # tsc -b → vite build → build:runner
|
||||||
{
|
|
||||||
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...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Two artifacts come out, both of which ship in the published package:
|
||||||
|
|
||||||
|
- **`dist/`** — the browser SPA (`vite build`). Served statically by the prod backend.
|
||||||
|
- **`build/`** — the Node prod-server runner (`build:runner` = `tsup --config tsup.runner.config.ts`), which bundles `server/` + `@inspector/core` into one ESM file and externalizes npm deps.
|
||||||
|
|
||||||
|
`build:client` runs only the `vite build` half when you just need fresh `dist/`.
|
||||||
|
|
||||||
|
## Component layers
|
||||||
|
|
||||||
|
Components live under `src/components/` in four layers, smallest to largest:
|
||||||
|
|
||||||
|
| Layer | Count | What it is |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `elements/` | ~31 | Leaf presentational pieces (badges, buttons, toggles) over Mantine primitives. |
|
||||||
|
| `groups/` | ~63 | Composite pieces (cards, panels, modals, control bars). |
|
||||||
|
| `screens/` | ~11 | Full tab screens (Tools, Resources, Servers, monitoring screens…). |
|
||||||
|
| `views/` | 1 | `InspectorView` — the top-level layout that composes the screens. |
|
||||||
|
|
||||||
|
Every screen and element has a `*.stories.tsx` (see [Storybook](#storybook)). Styling follows the Mantine-first rules in [`AGENTS.md`](../../AGENTS.md) — theme variants and component props over CSS, `--inspector-*` tokens over raw colors.
|
||||||
|
|
||||||
|
## Theme (`src/theme/`)
|
||||||
|
|
||||||
|
Each customized Mantine component has a `Theme<Name>.ts` file (`Button.ts`, `Text.ts`, …, ~21 total) exporting a `Theme<Name>` constant; the barrel `index.ts` re-exports them and `theme.ts` assembles the `MantineProvider` theme. Theme files hold app-wide defaults and **variants** (flat CSS-in-JS); only pseudo-selectors, nested child selectors, keyframes, and native-HTML styling belong in `App.css`. Element components import from `@mantine/core` (never from `theme/`) — the theme layer is applied transparently by the provider.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests run under three Vitest **projects** (configured in `vite.config.ts`), each in the right environment:
|
||||||
|
|
||||||
|
| Project | Env | Scope | Script |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `unit` | happy-dom | Components, hooks, utils (`*.test.tsx` beside the source) | `npm test` |
|
||||||
|
| `integration` | node | `@inspector/core` + transports + auth, spawning the real stdio test server (`src/test/integration/**`) | `npm run test:integration` |
|
||||||
|
| `storybook` | real Chromium | Story **play functions** as interaction tests | `npm run test:storybook` |
|
||||||
|
|
||||||
|
- `npm test` runs the fast **unit** project (happy-dom). `test:watch` for the loop.
|
||||||
|
- **Integration** tests run in a real Node env (no happy-dom, 30s timeouts) and spawn `test-servers/build/test-server-stdio.js` as a subprocess, so `pretest`/the coverage script build the test servers first (`test-servers:build`).
|
||||||
|
- **`npm run test:coverage`** runs unit **and** integration under v8 instrumentation and enforces the **per-file ≥90%** gate (lines/statements/functions/branches) — the same gate CI runs. Genuinely-unreachable branches are annotated with a justified `/* v8 ignore … */`, not waved through.
|
||||||
|
|
||||||
|
Integration tests live under `src/test/integration/` mirroring the `core/` layout; anything placed there is picked up by the `integration` project automatically. Render components with `renderWithMantine` (`src/test/renderWithMantine.tsx`) so they get the project theme.
|
||||||
|
|
||||||
|
## Storybook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run storybook # dev server on :6006
|
||||||
|
npm run build:storybook # static build
|
||||||
|
npm run test:storybook # run every story's play function in headless Chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
Storybook is first-class here because the components are presentational — each renders against fixture props. **Play functions double as interaction tests** and run headless in real Chromium via `@vitest/browser-playwright` + `@storybook/addon-vitest` (the `storybook` Vitest project). They're part of `npm run ci` (which installs the Chromium binary first) but kept out of the fast `validate` loop since they need the browser.
|
||||||
|
|
||||||
|
## Auth token
|
||||||
|
|
||||||
|
The dev/prod backend guards every `/api/*` route with `x-mcp-remote-auth: Bearer <MCP_INSPECTOR_API_TOKEN>`. The browser recovers the token, in priority order (see `App.tsx` `getAuthToken()`): the `window.__INSPECTOR_API_TOKEN__` global injected into `index.html` on every page load (`server/inject-auth-token.ts`), then a `?MCP_INSPECTOR_API_TOKEN=…` query param, then `sessionStorage`. Injection is a no-op when auth is disabled (`DANGEROUSLY_OMIT_AUTH`). See the root [AGENTS.md](../../AGENTS.md) for the full rationale.
|
||||||
|
|
||||||
## HTTP proxy support
|
## HTTP proxy support
|
||||||
|
|
||||||
The web backend connects to remote MCP servers through the shared Node transport (`core/mcp/node/transport.ts`), which honors the conventional proxy environment variables: `HTTPS_PROXY` / `HTTP_PROXY` (and their lowercase forms) select the proxy, and `NO_PROXY` exempts hosts. Routing is powered by [`undici`](https://www.npmjs.com/package/undici)'s `EnvHttpProxyAgent`, imported lazily only when a proxy variable is set, so runs without a proxy configured pay no cost. See the CLI README for more detail.
|
The web backend connects to remote MCP servers through the shared Node transport (`core/mcp/node/transport.ts`), which honors the conventional proxy environment variables: `HTTPS_PROXY` / `HTTP_PROXY` (and their lowercase forms) select the proxy, and `NO_PROXY` exempts hosts. Routing is powered by [`undici`](https://www.npmjs.com/package/undici)'s `EnvHttpProxyAgent`, imported lazily only when a proxy variable is set, so runs without a proxy configured pay no cost. See the CLI README for more detail.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue