Commit graph inspector/clients/web/vite.config.ts
Author SHA1 Message Date
cliffhall
77e57e0d25 feat(web): add sandbox CSP builder library (closes #1558)
Add a pure, DOM-free CSP-builder library at
clients/web/src/lib/sandbox-csp.ts that validates app-supplied
`_meta.ui.csp` requests, filters them to safe sources, builds the
locked-down Content-Security-Policy string, and wraps untrusted widget
HTML in a fixed host shell whose first <head> child is the CSP meta.

- SAFE_CSP_SOURCE: strict regex that rejects directive/attribute
  breakouts (`;`, `"`, `<`, `>`, whitespace).
- approveCspSources(): drops unsafe entries (with a warning) and omits
  empty keys; echoes back only what the host will enforce.
- buildSandboxCspPolicy(): `default-src 'none'` catch-all, `form-action
  'none'`, source-allowlist filtering, `'unsafe-inline'` for the app's
  own inline script/style.
- escapeHtmlAttr() / wrapSandboxedHtml(): defense-in-depth so untrusted
  bytes never precede the applied policy.

Re-implements the sandbox-csp portion of PR #1510 (Wave 1 of #1579) as a
standalone library; wiring into the app bridge is a follow-up issue.
Adds full unit tests (100% lines/statements/functions/branches) and
lists the file in the web coverage `include` so it is gated (the
untested legacy src/lib siblings stay out until they get tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3fTN8H3R8YV4yUGvZjYnX
2026-07-01 18:23:13 -04:00
Bob Dickinson
13dafe457b
Guided-auth removal, OAuth smoke testing, and TUI/CLI support (#1531)
* refactor(web): update OAuth E2E tests to use new authentication method

- Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration.
- Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs.
- Ensured that the tests continue to validate the expected authorization URL after the change.

Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process.

* Guided-auth removal, OAuth smoke testing, and TUI/CLI support

* Added CLI OAuth runner flags tests

* Added clarifying comment on port 0 binding

* README notes on TUI oauth callback port, prettier fixes for CLI tests

* CIMD validation uses inline like EMA IdP

* Fixed TUI test failures from merge

* test: bring 5 OAuth files over the 90 per-file coverage gate

The guided-auth-removal work added/touched several files that fell below
the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run
the gate (it's local-only), so validate + test:integration passed while
these gaps slipped through. Bring them all back over the line:

- core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme
  throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the
  provably-dead guards (empty host, out-of-range port, String(err)
  fallback, pathname "/" fallback) -- all rejected by new URL() upstream --
  with justified v8 ignore comments instead of lowering the gate.
- core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return.
- core/react/useClientSettingsDraft.ts: test the functional-updater path,
  the stale-onChange-after-close prev===null guard, and the
  timer-fires-after-close value===null guard.
- ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear.
- clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration
  kinds, the expired IdP session, and the configured-scope/empty-scope
  branches of formatScopes.

npm run coverage now passes (exit 0) across web, cli, tui, launcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: cliffhall <cliff@futurescale.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:35:51 -04:00
Cliff Hall
c96db0402e
test: raise per-file coverage gate to 90 across all clients (closes #1271) (#1532)
* test(web): audit branch-coverage outliers, raise per-file gate to 70 (#1271)

The web per-file coverage gate had drifted red: the recently-landed EMA
auth feature (core/auth/ema/*) and ClientSettings components shipped
undertested, and the prior 50% branch floor let business-logic branches
go untested. CI does not run the coverage gate, so this accumulated.

Add tests to bring every failing file above the gate and push branch
coverage well past 70 across the board, then raise the per-file branches
threshold 50 -> 70 in clients/web/vite.config.ts.

- EMA core (emaFlow, resourceContext, jwt, wire): 50-88% -> 86-100% branch
- EMA OIDC/transport (idpOidc, tokenEndpoint, transportProvider): -> 97-100%
- oauthManager / discovery: 73-83% -> 88-92%
- ClientSettingsModal/Form, ConnectionInfoContent: 0-88% -> 96-100%
- useClientSettingsDraft / useEmaIdpLoginState hooks: 20% -> 100%
- TaskStatusBadge / SortableServerCard outliers: 50% -> 100%

Coverable branches reached by mocking useComputedColorScheme (dark theme),
dnd-kit useSortable (isDragging), and fetch/OIDC paths. Remaining sub-100
branches are genuinely-unreachable defensive guards or happy-dom-inherent
(portal / useMediaQuery / typeof window) and documented in test comments.

No source files changed. Global web branch coverage 88.73% -> 90.63%.
AGENTS.md updated; cli/tui/launcher stay at 50 (out of scope).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: raise per-file coverage gate to 90/90/90/90 across all clients

Follows the #1271 web branch-coverage audit (50 -> 70) by raising the
whole per-file gate to >=90 on all four dimensions — lines, statements,
functions, branches — for web, cli, tui, and launcher (core/ is measured
under the web suite). A codebase-wide audit added real tests for every
outlier and brought global coverage up across the board.

Config: all four vitest/vite coverage configs set to 90/90/90/90.
Two pure-type modules (core/mcp/import/types.ts, clients/web/server/
types.ts) added to the web coverage exclude list, matching the existing
type-only exclusions.

Tests: ~50 test files extended/created across core (auth, ema, mcp,
remote, state, storage, react), web components/screens/server, and the
cli/tui clients. Highlights: full EMA OAuth flow + transport coverage,
inspectorClient/remote server backfill, every Mantine control's handler
branches, and the cli unsupported-method / argv-fallback paths.

Genuinely-unreachable branches are annotated at the source with justified
`/* v8 ignore -- <reason> */` comments rather than lowering the gate:
happy-dom-inherent paths (Mantine portal, useMediaQuery, typeof window
SSR guards), React StrictMode effect replay, and provably-dead defensive
guards (?? fallbacks the types make impossible, Select.onChange values
outside the allowed list). No runtime logic changed; the only structural
edits are two behavior-preserving refactors (ResourceTemplatePanel hoists
the field value to a local so the ignore applies in JSX; ToolsScreen
swaps a ternary for an if) and prettier line-wrapping.

Also fixed a flaky real-timer debounce assertion in PromptArgumentsForm
(asserted an exact sibling-typing interleaving that raced the 300ms
debounce; now asserts the load-bearing fact — a non-empty fire-time
sibling capture).

AGENTS.md updated to describe the uniform 90 gate and the v8-ignore
policy. All four client coverage gates, root validate, storybook (370),
and smoke pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(web): make connect-crash SSE reader stop on the terminal event

`readSseEvents` looped until the server closed the stream (`done`). The
server closes the events stream immediately after writing the terminal
`transport_error`, but under the heavy parallel scheduler pressure of a
full `coverage` run (v8 instrumentation across all workers) that close
can lag far enough behind the event to trip the 30s test timeout — a
hang, not a wrong result. This made the test intermittently fail under
`npm run coverage` (it passed in isolation and under CI's lighter
`test:integration`).

Stop reading as soon as the terminal `transport_error` event arrives
(cancelling the reader) instead of waiting only for `done`. That event is
always the last one the session queue emits, so nothing is lost, and the
test no longer depends on stream-close timing. The `done` break is kept
for any stream that closes first.

Verified green across two consecutive full `npm run test:coverage` runs
(3094 tests) where it previously hung, plus isolation (~0.5s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:26:03 -04:00
Cliff Hall
5efe9ddc7b
refactor: self-validating clients, fast validate + separate coverage/smoke (#1504) (#1505)
* refactor(launcher): self-contained validate; split smokes into top-level step (#1504)

Previously `validate:launcher` built cli/tui/web and ran the cross-client
smokes, so the launcher's "validation" depended on every other client
being built — and the launcher had no lint/format/coverage of its own.

- clients/launcher: add a web-style `validate` (format:check && lint &&
  build && test:coverage) with a Node-only eslint flat config, prettier,
  and a per-file coverage gate (parse-launcher-argv.ts covered;
  src/index.ts excluded as binary bootstrap). 100% on the gated file.
- Root `validate:launcher` now just delegates: `cd clients/launcher &&
  npm run validate` — no dependency on the other clients.
- New top-level `smoke` step (smoke:launcher + smoke:cli + smoke:tui +
  smoke:web) with NO build commands; `validate` runs it last. Each
  client's own validation builds its bundle first, so nothing builds
  twice (cli/tui via test:coverage, launcher via its validate;
  smoke:web builds dist on demand).
- clients/tui test:coverage now builds the TUI bundle (mirroring cli) so
  the build-free smoke has it available.
- CI: replace the standalone cli/tui/launcher build step with a
  "Validate launcher" step. Docs updated (AGENTS.md, launcher README).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(cli,tui,root): every client self-validates; smoke separate from validate

Extends the launcher self-validate work to the CLI and TUI, and makes the
top-level `validate` nothing but the per-client validations — smokes are
now a separate top-level target.

- clients/cli, clients/tui: add a web-style `validate` with a Node flat
  eslint config + prettier. CLI lint/format pass as-is. TUI registers
  eslint-plugin-react-hooks for the classic rules only (rules-of-hooks +
  exhaustive-deps, which the source already references); the stricter
  react-hooks@7 rules are not enforced on the interim component surface
  (#1501).
- Avoid double builds:
  - CLI's e2e spawns the built binary, so its `test:coverage` builds first;
    `validate` folds that in (`format:check && lint && test:coverage`, no
    separate `build`).
  - TUI tests don't spawn a bundle, so `validate` keeps an explicit
    `build` (for the smoke) and `test:coverage` is pure.
- Root: `validate` = validate:web && validate:cli && validate:tui &&
  validate:launcher (each delegates to the client's own validate). `smoke`
  is no longer part of `validate` — it's a separate top-level target run
  after a build/validate, with no build commands of its own.
- CI: per-client "Validate X" steps + a single "Run cross-client smokes"
  (npm run smoke). Docs updated (AGENTS.md, cli/tui READMEs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(root): drop redundant test aggregator scripts

Now that every client self-validates and the top-level `validate` chains
the per-client validations, the root `test` / `test:web` / `test:cli` /
`test:tui` / `test:coverage` aggregators are redundant — run
`npm run validate` (all) or `cd clients/<name> && npm run validate` (one).
`test:storybook` stays (it's the one check not part of validate). CI was
already using each client's own scripts, so it's unaffected. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(web): silence happy-dom infra noise in unit runs

Passing unit runs printed dozens of alarming-but-expected lines that made
green runs look broken — all from happy-dom performing real I/O with no
server behind it:
- `<iframe src="/sandbox.html">` (MCP Apps sandbox) made happy-dom fetch
  and later unload that URL, emitting ~40 `DOMException [NetworkError/
  AbortError]` + `AsyncTaskManager destroyed` lines. Disable child-frame
  navigation in the unit project (the tests only assert on the iframe
  element/attributes, not its document), removing the noise with no
  coverage loss.
- App-level renders read `GET /api/config` via useSandboxUrl /
  useServerListWritable, 404-ing ~76x. Add a benign default `fetch` (empty
  200) in the unit setup; tests that exercise fetch still override it.

Negative tests that assert on errors already suppress their own console
output (e.g. jsonUtils/schemaUtils); CLI, TUI, and the web integration
suite were already clean. Unit suite is now noise-free; 2424 web tests
pass and the coverage gate is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(scripts): make validate fast (test), split coverage into its own target

Each client's `validate` now runs `test` instead of `test:coverage`, so
the everyday pre-push check skips v8 instrumentation (and, for web, the
slower integration project). The per-file coverage gate moves to a
dedicated top-level `coverage` script:

  coverage = coverage:web && coverage:cli && coverage:tui && coverage:launcher
  coverage:<name> = cd clients/<name> && npm run test:coverage

`validate` still builds each client's bundle (cli via its test `pretest`,
the others via an explicit `build`), so `npm run validate && npm run
smoke` works unchanged. CI now runs `validate` (fast checks + builds),
then `coverage` (the gate), then `smoke` — collapsing the old per-client
steps. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: drop the coverage gate from CI; keep web integration as its own step

CI no longer runs `npm run coverage` — the per-file coverage gate is now a
local/manual check (`npm run coverage` before pushing). To avoid silently
dropping the web integration suite (it only ran via test:coverage, and
`validate` runs the unit project only), CI gains a standalone
`clients/web` `test:integration` step that exercises the real
stdio/HTTP/OAuth/storage paths without coverage instrumentation.

CI is now: validate (fast) → web integration → smoke → storybook. Docs
updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(smoke): give smoke:launcher its own script like the others

smoke:launcher was the odd one out — an inline chain of three
`node clients/launcher/build/index.js ... --help` commands while smoke:cli
/ smoke:tui / smoke:web each run a `scripts/smoke-*.mjs`. Extract it to
`scripts/smoke-launcher.mjs`, which runs the same `--help` / `--cli --help`
/ `--tui --help` dispatch checks but now also asserts each prints that
mode's usage banner (proving the launcher resolved the right client build)
and verifies the required builds exist up front with a clear error. Prints
a `smoke:launcher OK — ...` summary in the same style as the rest. Docs
updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: reorder package.json scripts for readability

Move frequently-run scripts toward the top and rarely-run-by-hand ones
(pretest hooks, lint/format internals, etc.) toward the end, consistently
across the root and client manifests. Pure reordering — no script names or
values changed, and no other manifest content is touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: drop references to removed root inspector / test:storybook scripts

The reordering dropped the root `inspector` and `test:storybook`
aggregators (intentional — `inspector` is redundant with `web`, and
storybook is run from clients/web in CI). Update AGENTS.md so it no longer
points at `npm run inspector`, and run `test:storybook` from `clients/web/`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: address PR #1505 review (format glob gap, fetch-baseline comment)

- TUI format/format:check now also cover the client-root source files
  (index.ts, tui.tsx, dev.ts) that live outside src/ — `eslint .` already
  lints them, so they were linted but not format-checked. (cli/launcher
  have no source outside src/; their root .ts are single-quoted configs
  intentionally left out of prettier, matching web.)
- Reword the setup.ts default-fetch comment: it overstated auto-restore
  (the unit project sets no restoreMocks/unstubGlobals), and now also
  flags that the permissive 200 default means a test asserting on a fetch
  FAILURE must install its own rejecting stub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: remove redundant root install:clients script

`install:clients` ran the same `node scripts/install-clients.mjs` as
`postinstall`, which fires automatically on `npm install` — so the manual
alias was redundant. Drop it and the doc references (AGENTS.md, spec).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): skip smoke:tui in CI (Ink needs a real TTY)

This PR made CI run the full `npm run smoke`, which surfaced that
smoke:tui fails in headless CI: the Ink TUI throws "Raw mode is not
supported" on mount (no TTY) and exits before rendering its first frame.
The old CI never ran smoke:tui, so this was latent.

smoke-tui.mjs now self-skips when `process.env.CI` is set — the boot/
render check is inherently local-only (a TUI needs a terminal). The TUI is
still built and unit-tested in CI; `npm run smoke` stays the single
command everywhere (full locally, tui-skipped in CI). Verified
`CI=true npm run smoke` is green (launcher/cli OK, tui SKIPPED, web OK).
Docs + CI comment updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:41:33 -04:00
Cliff Hall
a1f40a4222
fix(web): remove config-load warning filter, fix root cause (#1491) (#1494)
The `UNRESOLVED_IMPORT` warnings that `quiet-config-warnings.mjs` suppressed
no longer occur, so the filter (and the build-time `onwarn` suppressions for
the same three deps) are removed.

Root cause: Vite's config bundler (Rolldown) statically walks the node-only
dev backend (`core/mcp/remote/node/server.ts`) when it loads `vite.config.ts`,
reaching `chokidar`, `atomically`, and `@napi-rs/keyring`. `core/` has no
node_modules of its own, so at config-bundle time those deps resolve from
`core/`'s parent chain — the repo-root `node_modules`. Before #1452 the deps
lived only under `clients/web/node_modules` (a sibling, off the resolution
chain), so Rolldown emitted `UNRESOLVED_IMPORT`. #1452 declared them in the
repo-root `package.json` (for the CLI/TUI/launcher, which consume core/'s node
code), installing them at the repo root and making them resolvable. Verified
by hiding the root copy: the warning returns; restore it and the dev/build
startup is clean.

A dynamic import of the backend does not help — Rolldown follows dynamic
imports during config bundling too. The fix is the deps being resolvable, so
the suppression is just removed and the invariant is documented in
`vite.config.ts`.

- Remove `clients/web/server/quiet-config-warnings.mjs` + `.d.mts` and its test
- Restore the `dev` script to plain `vite` (drop the `--import` hook)
- Remove the now-dead `build.rollupOptions.onwarn` UNRESOLVED suppressions
  (never fire: the Hono plugin is `apply: 'serve'`, out of the build graph)
- Document why config-bundle resolution succeeds in `vite.config.ts`
- Update AGENTS.md project tree

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:37:06 -04:00
Bob Dickinson
2488ca5e38
feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452)
Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single
`mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the
`mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server
double-start), the root `postinstall` install cascade with simplified CI, and
spec/AGENTS docs (no-workspaces rationale, Development setup).

Closes #1246.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:27:49 -04:00
Bob Dickinson
62d442b073 Revert "First pass at pulling the v1.5 CLI, TUI, and Launcher clients into v2."
This reverts commit 467b01edd6.
2026-06-09 21:56:38 -07:00
Bob Dickinson
467b01edd6 First pass at pulling the v1.5 CLI, TUI, and Launcher clients into v2. 2026-06-09 17:38:00 -07:00
Cliff Hall
037225a9fb
feat(servers): move oauthClientSecret and stdio env values to OS keychain (#1356) (#1360)
* feat(servers): move oauthClientSecret and stdio env values to OS keychain (#1356)

mcp.json is designed to be tool-shareable (symlinked from Claude Desktop's
config, pasted into bug reports, synced via dotfiles), so storing OAuth
client secrets and stdio env values in plaintext there meant any of those
flows could leak them. Lift both into the OS keychain via @napi-rs/keyring
(active replacement for the archived keytar). The wire shape is unchanged:
the GET /api/servers handler rehydrates from the keychain so browser code
sees the same JSON; the on-disk file no longer contains the secret
material. Includes an idempotent migration that lifts plaintext from older
mcp.json files (or hand-edited ones) into the keychain on first read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(servers): tolerate keychain unavailability on non-set ops (#1356)

CI on Linux failed because `@napi-rs/keyring` hard-errors without
libsecret, and the route handlers were touching the keychain even for
no-secret flows (defensive `deleteAllForServer` on POST/DELETE, sweep
on PUT). The user's intent for the Linux fallback was "hard fail when a
secret is actually involved" — not on every keychain touch.

Make `KeyringSecretStore.get / delete / deleteAllForServer` silently
degrade when the keychain is unavailable (return null / no-op); `set`
remains the one operation that hard-fails with
`KeychainUnavailableError`. The migration path catches the error and
preserves the disk plaintext rather than losing the secret.

Also inject `InMemorySecretStore` in `useServers.test.tsx` and
`servers-events.test.ts` so tests never touch a developer's real
keychain even when libsecret is present.

Adds `KeyringSecretStore` tests (vi.mock'd native bindings) and an
integration suite that simulates Linux-without-libsecret to verify the
tolerance contract end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(servers): close GET migration race; reverse keychain/disk order (#1356)

Addresses code review:

1. **Concurrency**: GET /api/servers was reading + migrating outside the
   write lock, then writing inside it — a concurrent POST/PUT/DELETE
   between the read and the lock acquisition could be clobbered by the
   migration write. Also affected the first-launch seed write. Fix:
   take a fast unlocked peek; if the file is absent (seed branch) or
   carries plaintext to migrate (migration branch), re-read inside the
   write lock and decide there. Added `hasPlaintextSecrets()` as a
   pure predicate so the common no-migration path stays lock-free.

2. **Write ordering**: POST and PUT wrote disk first, then keychain.
   A keychain `set` failure mid-write left a half-configured entry on
   disk and trapped retries at 409. Reversed to keychain-first so a
   503 leaves both stores in their pre-write state. PUT in-place now
   sets new fields, writes disk, then deletes obsolete fields (a
   failed disk write leaves orphan keychain entries — recoverable —
   rather than premature deletions of fields the user still expects
   to see).

3. **Tolerance simplification**: dropped the no-entry regex in
   `KeyringSecretStore.delete` — both "no entry" and "keychain
   unavailable" collapse to the same desired outcome (the entry isn't
   there anymore), so a uniform swallow is clearer.

4. **Parallelism**: `readKeychainEntriesFor` now `Promise.all`s the
   per-field `get` calls. macOS Keychain round-trips are 10-50ms;
   serializing 5+ env vars per server × N servers is a meaningful
   wall-clock cost on GET.

5. **Spec doc**: reflects the tolerance contract (only `set`
   hard-fails) and the keychain-before-disk write ordering rationale.

New regression test in servers-route.test.ts covers the retry-after-503
contract directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(servers): parallel keychain writes + migration partial-state comment (#1356)

Two nice-to-have observations from the re-review:

- `writeKeychainEntriesFor` now `Promise.all`s its per-field sets,
  matching the read side. No ordering requirement among distinct
  (id, field) keys; on unavailability every set rejects and Promise.all
  surfaces the first rejection (still 503 from the route handler).
- `migratePlaintextSecrets` catch block now spells out the
  partial-migration semantics: we return the original config so the
  disk plaintext stays intact, and the next successful GET retries
  with the keychain-wins branch absorbing already-set entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(servers): parallel keychain deletes for symmetry (#1356)

Picks up the symmetry observation from the third review: `deleteKeychainFields`
now uses `Promise.all` like its read and write siblings. Distinct (id, field)
deletes have no ordering requirement, and `secretStore.delete` is already a
silent no-op on unavailability so Promise.all has no failure-mode surprise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:33:46 -04:00
Cliff Hall
5f9bfdc490
feat(servers): hot-reload server list on external mcp.json edits (#1345) (#1357)
* feat(servers): hot-reload server list on external mcp.json edits (#1345)

Watch ~/.mcp-inspector/mcp.json from the backend and push a change event
on a new /api/servers/events SSE channel. The web client subscribes via
fetch + ReadableStream (so the existing bearer-auth contract is
preserved — EventSource can't send custom headers) and re-fetches on
each event. The watcher is lazy: it starts on the first SSE subscriber
and stops when the last one disconnects, so tests that never open the
channel don't spin up a real fs watcher. Self-fires from the backend's
own POST/PUT/DELETE are suppressed by capturing the post-write mtime and
comparing it on each watcher event.

Chokidar is used for cross-platform editor-save semantics (temp-file +
rename produces unlink+add sequences that bare fs.watch handles poorly
on macOS/Linux); awaitWriteFinish coalesces those into a single event.

createRemoteApp now returns a close() to release the watcher, which the
standalone server and the vite dev plugin chain into their existing
close paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(servers): address PR review on hot-reload (#1357)

- writeMcpAndTrackMtime now stat()s the file before writing; if the
  mtime differs from the last value we recorded, an external editor
  slipped in between our writes and chokidar would coalesce both into
  one event whose mtime matches our own write — broadcast directly so
  peer subscribers learn about the external edit (review finding 1, 5).
- Split useServers.refresh into a public refresh() (loading toggle) and
  an internal refreshInternal(background) the SSE handler calls; the
  list now stays visible during background refreshes instead of
  flashing skeleton state on every external mcp.json edit (finding 2).
- Coalesce multiple \n\n frames landing in one decode chunk to a single
  refresh call so back-to-back broadcasts don't race two concurrent
  GETs whose setState order is unspecified (finding 3).
- Clarify createRemoteApp's close() docblock about in-flight SSE stream
  callbacks (finding 7).
- Add integration coverage for PUT/DELETE self-suppression and for the
  external-edit-between-backend-writes broadcast (finding 6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(servers): address pass-2 review on hot-reload (#1357)

- Clarify writeMcpAndTrackMtime comment so a future reader can't
  mistake the broadcast for a content-preservation guarantee: the
  external edit's content may already have been read-around and
  overwritten depending on the interleave with readMcpConfig.
- Drop the no-op GET in the PUT/DELETE self-suppression tests. The
  pre-stat branch is short-circuited by `lastWrittenMtimeMs === null`
  with or without it; matches the POST test's structure.
- Add explicit no-flicker regression test: tracks every `loading`
  value across renders during an SSE-driven refresh and asserts none
  flipped back to true. Catches a future regression that reaches for
  setLoading(true) in the background path even under React batching.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:30:05 -04:00
Cliff Hall
4d74b94bed
Wire App.tsx to v2 core/ hook layer; remove demo stub from InspectorView (#1322)
* feat(web): wire App.tsx to v2 core/ hook layer; remove demo stub from InspectorView

App.tsx mounts InspectorView with live data from the v2 hook layer instead
of the title-page placeholder. InspectorView becomes pure prop-driven; the
demo handshake stub and connection state machinery are removed.

Connection lifecycle:
- App.tsx lazily instantiates `InspectorClient` on the connect edge,
  rebuilding it (and its state managers) when the user picks a different
  server. State managers (Managed{Tools,Prompts,Resources,ResourceTemplates,
  RequestorTasks}State + MessageLog/FetchRequestLog/StderrLog) are
  destroyed on switch.
- `useInspectorClient` + the per-primitive `useManaged*` hooks drive the
  view's data. `latencyMs` is captured at the connecting → connected edge
  via a ref so the intermediate rerenders don't lose the start timestamp.
- `errorMessage` is captured from `client.connect()` rejection; status-
  machine-driven errors that don't surface through the promise are a known
  follow-up (no `error` event in the v2 InspectorClientEventMap yet).

Action handlers:
- `onCallTool` / `onGetPrompt` / `onReadResource` await the corresponding
  InspectorClient method and reflect pending → ok/error in `ToolCallState`
  / `GetPromptState` / `ReadResourceState` panel props. Manually verified
  end-to-end against `npx @modelcontextprotocol/server-filesystem` —
  the Tools result panel renders the real "Allowed directories: /private/tmp"
  output rather than a stub.
- `onSetLogLevel` calls `client.setLoggingLevel(level)` and optimistically
  bumps `currentLogLevel` locally (the request has no echo notification).
- `onSubscribeResource` / `onUnsubscribeResource` / `onCancelTask` route to
  their respective client methods. Server CRUD (`onServerAdd`/`onServerEdit`
  /…) and history pinning are intentionally `todoNoop` for now; the seed
  server list is hardcoded per the agreed scope (a `useServers` v2-only
  hook is a separate effort).
- `notifications/message` notifications flowing through `MessageLogState`
  are filtered into `LogEntryData[]` and passed to the Logs screen, so
  `logging/setLevel` and the resulting log stream round-trip.

InspectorView (`InspectorView.tsx`):
- Removed: `STUB_*` constants, `handshakeTimer` ref, `handleToggleConnection`
  / `disconnect` functions, stub `bridgeFactory`/`sandboxPath`, internal
  connection state, `logLevel` state.
- Added 38 props covering connection state, panel states, log level, and
  action callbacks. The view is pure presentational.
- `availableTabs` is derived from `connectionStatus`; `activeTab` is
  clamped to whatever's currently available (avoiding the `set-state-in-effect`
  lint and keeping the previously-selected tab on reconnect).

Stories + tests:
- `InspectorView.stories.tsx` adds spy callbacks for every action prop and
  new `Connected` / `ConnectionError` stories for the connected/error
  narratives.
- `InspectorView.test.tsx` is rewritten around the prop-driven contract
  (10 tests covering the empty list, server cards, toggle dispatch,
  connected header, error banner, tab snap-back on disconnect, app
  filtering, log-level dispatch, autoScroll local toggle).

Dev backend port wiring (`vite.config.ts`):
- Vite's dev server is now pinned to `CLIENT_PORT` (default 6274) with
  `strictPort: true`, matching the dev backend's `allowedOrigins`. Without
  this, Vite would fall back to 5173 while the Hono origin check still
  required 6274 — breaking every browser `/api/*` fetch out of the box.
- Top-level `resolve.alias` now spreads the same bare-module aliases the
  vitest projects use. Rolldown can't reach `pino/browser.js` /
  `zustand/middleware` from `core/`'s parent (no node_modules there); the
  wired App pulls those modules into the browser dep graph for the first
  time, so the aliases need to be visible at build time too.

createWebEnvironment helper (`src/lib/environmentFactory.ts`):
- Mirrors v1.5's `lib/adapters/environmentFactory.ts`: builds
  `InspectorClientEnvironment` from `createRemoteTransport` +
  `createRemoteFetch` + `createRemoteLogger` + `BrowserOAuthStorage` +
  `BrowserNavigation`. Returns the assembled environment plus the logger
  for direct app-level use.

Closes #1244.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): wire dev-backend auth token into createWebEnvironment + show full stdio command in ServerCard

Two issues surfaced after the #1244 wiring landed locally:

1. **401 on `/api/mcp/connect`** when running `npm run dev` with the
   default auth (no `DANGEROUSLY_OMIT_AUTH`). The dev backend generates a
   random token and prints `http://localhost:6274?MCP_INSPECTOR_API_TOKEN=…`
   in the banner, but App.tsx was calling `createWebEnvironment(undefined, …)`
   so the browser never sent `x-mcp-remote-auth`. The Hono middleware
   correctly rejected with "Authentication required. Use the
   x-mcp-remote-auth header with Bearer token."

   Adds a small `getAuthToken()` helper in App.tsx that reads
   `MCP_INSPECTOR_API_TOKEN` off `window.location.search`, falls back to
   `sessionStorage`, and persists fresh values into sessionStorage so
   client-side navigation / OAuth round-trips don't drop the token. The
   resolved value is passed to `createWebEnvironment` whenever a new
   InspectorClient is built.

2. **Misleading ServerCard display** — the seed server's `config` is
   `{ command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem",
   "/tmp"] }` but the card only rendered `"npx"`, so it looked like the
   command was incomplete. `getCommandOrUrl` now joins `command + args`
   for stdio configs, so the card shows the same string that gets spawned.
   SSE / streamable-http paths unchanged.

Manual end-to-end re-verified against `npm run dev` (no DANGEROUSLY_OMIT_AUTH):
- Banner prints the auth-token URL.
- Browser navigates to that URL → `getAuthToken()` picks the token off the
  query string, persists to sessionStorage, threads it through to every
  `/api/*` request.
- Server card shows `npx -y @modelcontextprotocol/server-filesystem /tmp`.
- Toggle connect → connection succeeds; ViewHeader shows
  `Connected (~1.3s)`; Tools tab populates with the 14 filesystem tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): use dark-9 instead of blue-9 for dark-mode page background

`main.tsx` overrode `--mantine-color-body` to `--mantine-color-blue-9` in
dark mode, while `.storybook/preview.tsx` used `--mantine-color-dark-9`.
The dev app then rendered with a blue background while every story (and
the design intent visible in storybook) used dark grey.

Switch main.tsx to `--mantine-color-dark-9` so the dev app matches the
storybook reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(web): seed the Servers screen with the "everything" reference server

Adds `@modelcontextprotocol/server-everything` to the hardcoded seed list
alongside the filesystem server. The everything server exposes tools,
prompts, resources, sampling, and completion — covering most of the
surface a developer might want to exercise against the wired-up
InspectorView without configuring anything.

Comment updated to explain the two-seed shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): address #1244 PR review feedback (JsonValue cast, client lifecycle, dead code)

- **#1 — Misleading `Record<string, never>` cast**: Imported `JsonValue`
  from `@inspector/core/mcp/index.js` and changed `args as Record<string,
  never>` to `args as Record<string, JsonValue>` in `onCallTool`. The new
  cast honestly narrows from the screen-level `Record<string, unknown>`
  to what `InspectorClient.callTool` actually requires. Added a comment
  explaining the boundary.

- **#2 + #3 — Previous InspectorClient not disconnected on server switch
  or unmount**: Added a `useEffect` keyed on `inspectorClient` whose
  cleanup calls `inspectorClient.disconnect()`. One effect covers both
  the swap-on-switch leak (prior session's transport stayed open until
  GC) and the unmount-during-connected leak (HMR / tests).
  `setupClientForServer`'s existing state-manager `destroy()` calls
  handle the listener side; the new effect handles the transport side.

- **#4 — Dead `void` block at bottom of file**: Deleted the trailing
  `void FetchRequestLogState; void StderrLogState;` block. The state
  managers are already constructed via `new FetchRequestLogState(client)`
  / `new StderrLogState(client)`, so the imports are referenced by the
  linter's reckoning. The `void` pair was dead code with a comment that
  no longer matched reality.

- **#5 / #7 / #8 — Follow-up TODOs**: Added TODO refs to the new
  follow-up issues:
  - TODO(#1323): `error` event in InspectorClientEventMap so mid-session
    transport failures surface (connect-only catch is incomplete).
  - TODO(#1324): negotiated `protocolVersion` through `useInspectorClient`
    (currently hard-coded `"2025-06-18"`).
  - TODO(#1325): `useResourceSubscriptions` hook so subscribe/unsubscribe
    buttons reflect their server-side effect.

Items #6 (useCallback dep churn), #9 (helper unit tests), and #10
(shared no-op bridge factory fixture) are acknowledged in the PR reply
as deferred — no functional impact and out of scope for a "wire the
hook layer" PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:41:55 -04:00
Cliff Hall
37a4d99575
Port v1.5 web dev backend (vite-hono-plugin + remote-server bootstrap) (#1321)
* feat(web): port v1.5 dev backend (vite-hono-plugin + sandbox + remote-server bootstrap)

Ports the v1.5 in-process dev backend into v2 so `npm run dev` serves the
Hono remote-server (`/api/*`) alongside the Vite-served browser app. Without
this, the browser-side `createRemoteTransport` had nothing to talk to during
local development.

Adds under `clients/web/server/`:
- vite-hono-plugin.ts (Hono middleware on the Vite dev server; gated to
  `apply: 'serve'` and skipped when `process.env.VITEST` is set so it stays
  inert in vitest/storybook runs)
- sandbox-controller.ts (standalone HTTP server for the MCP Apps sandbox)
- web-server-config.ts (env parsing + initial-config payload + banner;
  `autoOpen` defaults off under VITEST so test runs don't shell out to `open`)
- server.ts (standalone Hono prod server for the future v2 launcher, #1246)
- start-vite-dev-server.ts (in-process Vite starter for the future launcher)
- vite-base-config.ts (shared optimizeDeps exclusions)

Wires `honoMiddlewarePlugin(buildWebServerConfigFromEnv())` into
`vite.config.ts` with `apply: 'serve'`. Adds the `open` dependency and a
DOM lib + `@inspector/core/*` path alias to `tsconfig.node.json` so the
dev-backend files type-check alongside vite.config.ts.

Coverage: web-server-config.ts (100% lines), sandbox-controller.ts (91% lines),
and vite-base-config.ts (100%) all clear the per-file gate. The Vite plugin,
prod server, and dev-server starter are excluded from coverage as runtime
glue exercised by `npm run dev` end-to-end (callers covered separately;
their logic lives in core/mcp/remote/node, which integration tests already
exercise).

Closes #1320.
Unblocks #1244 (Wire App.tsx) — its test plan can now run end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web/server): address PR review feedback on the dev-backend port

- sandbox-controller: resolve start() with empty values on listen error
  (e.g. EADDRINUSE on a fixed MCP_SANDBOX_PORT) instead of leaving the
  promise pending forever. Without this, the Vite plugin's
  `await sandboxController.start()` could hang the entire dev backend on a
  port collision. Banner now silently omits the sandbox line; getUrl()
  keeps returning null. Adds a regression test exercising the EADDRINUSE
  path against a real socket.
- vite-hono-plugin: listen for `error` and `close` on the incoming request
  stream (in addition to `end`) so an aborted POST/PUT (browser navigates
  mid-upload) doesn't leak the middleware indefinitely.
- vite-hono-plugin: add a `.catch()` to the initial `pump()` kickoff so a
  sync throw before the first `await reader.read()` can't surface as an
  unhandled rejection.
- sandbox_proxy.html: drop the stale CSP comment — v1.5's sandbox HTTP
  server never set CSP either; this PR doesn't regress anything. Replace
  with the actual isolation model (iframe sandbox attribute + origin
  allowlist) so future readers don't assume a CSP header is enforced.
- Hoist `WebServerHandle` to a shared `server/types.ts` so the future
  launcher port (#1246) doesn't need to pick one of two duplicate
  declarations.
- Extract `resolveAutoOpen()` helper out of the triple-nested ternary in
  `buildWebServerConfigFromEnv` for legibility, with the three-way logic
  documented in one place.
- Add an inline comment explaining why
  `webServerConfigToInitialPayload` falls back to streamable-http for
  unknown discriminators (forward-compat with future SDK transports)
  rather than throwing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 19:22:14 -04:00
Cliff Hall
8bb389c8e3
test: switch integration project to folder-based manifest (#1314) (#1318)
* test: switch integration project to folder-based manifest (#1314)

Replace the `integrationTests` enumeration in vite.config.ts with a
folder glob (`src/test/integration/**/*.test.ts`). Files under that
folder run in the integration vitest project (node env, 30s timeouts);
everything else runs in the unit project (happy-dom).

This removes the silent misclassification trap from #1307: a new
integration-style test no longer falls into the unit project (and
happy-dom) just because someone forgot to add it to the array.

The 13 ported v1.5 tests + their helpers move under src/test/integration/
in a strict mirror of core/ source layout (mcp/, auth/, auth/node/,
storage/). Renames use git mv so history is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: address PR review on #1318 (glob extensions + AGENTS doc)

- vite.config.ts: widen integrationGlob from `*.test.ts` to
  `*.test.{ts,tsx}` so the integration folder fully owns its contents.
  Without this, a `.test.tsx` placed under src/test/integration/ would
  match the unit project's `{ts,tsx}` include, fail the unit `.ts`-only
  exclude, and silently land under happy-dom — the exact misclassification
  trap this PR set out to close.
- AGENTS.md: update the line describing where integration tests live;
  it still pointed at the old `src/test/core/` location and the removed
  `integrationTests` enumeration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:54:49 -04:00
Cliff Hall
02182e3b6c
test(core): backfill coverage on inspectorClient + remote server (#1317)
* test(core): backfill coverage on inspectorClient + remote server

Resolves #1310. Drops the two TODO(#1310) exclusions from
clients/web/vite.config.ts (coverage.exclude); both files now pass the
per-file gate (lines >=90, statements >=85, functions >=80, branches >=50).

core/mcp/remote/node/server.ts (88.15 / 78.37 -> 97.29 / 100):
- Extract createTokenAuthProvider to its own file
  (core/mcp/remote/node/tokenAuthProvider.ts) so the 7 no-op
  OAuthClientProvider stubs can be unit-tested directly.
- Add focused integration tests covering the remaining gaps:
  * createTransportNode failure path on POST /api/mcp/connect
  * /api/mcp/send when transport is dead
  * /api/log non-JSON body (catch fallback)
  * forwardLogEvent unknown-level / no-messages / string-first-message
    fallbacks (lines 232, 240-242, 257-265 in the original)
  * /api/fetch missing-url + upstream-throw paths
  * /api/storage POST invalid storeId + invalid JSON body
  * /api/storage GET when the on-disk store is unparseable

core/mcp/inspectorClient.ts (73.48 / 72.09 -> 91.16 / 89.92):
Added a single "Coverage backfill (#1310)" describe block to the existing
integration test, plus a few unit-style cases:
- OAuth getters / setOAuthConfig / authenticate when oauthManager is unset.
- Simple session, roots, subscription, and instructions accessors.
- subscribe / unsubscribe error paths against a server without the
  subscribe capability; happy path against one with it.
- getPrompt + readResourceFromTemplate happy path + expansion-failure.
- setLoggingLevel "Server does not support logging" guard.
- getAppRendererClient (null before connect, cached proxy after).
- OAuth + stdio: authenticate() throws via getServerUrl()'s stdio check.
- listChanged notifications: tools/resources/prompts handlers via the
  fixture add_tool / add_resource / add_prompt tools.
- Misc tiny branches: re-connect no-op, elicit form mode, getTaskCapabilities
  on a tasks-less server, receiver-task TTL cleanup + cancel via the
  private surface (the focused-unit pass the issue describes).
- defensive `if (!this.client) throw` guards: one nullify-and-call test
  exercises 16 of them via a single instance, plus the connect() entry
  guard.

The receiver-task internals test uses (instance as unknown as
{ private fields... }) to drive createReceiverTask / cancelReceiverTask
/ TTL cleanup directly; per the issue, refactoring private helpers to
be testable from a unit test was authorized.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(core): address #1317 PR review feedback

- Rename the misnamed "listChanged notifications" describe in
  inspectorClient.test.ts to "capability detection after connect", since
  the test asserts only on getCapabilities() round-tripping and never
  triggers or observes a notification dispatch. Real dispatch coverage
  lives in the adjacent "list_changed + resourceUpdated notifications"
  block. Kept the resources/prompts fixtures with a note explaining why
  they are required for the server to advertise the matching capabilities.
- Expand the comment on the 250ms wait in remote-transport.test.ts'
  /api/mcp/send dead-transport test so it is clear the >= 500 assertion
  is intentionally permissive across both the dead-transport short-
  circuit and the downstream send-failure branches.
- Add a "keep this in sync" pointer on the nullify() helper so a future
  cleanup that removes any of the defensive `if (!this.client) throw`
  guards in inspectorClient.ts is reminded to drop the matching call here
  rather than silently under-covering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:43:55 -04:00
Cliff Hall
88b55b093b
test: run v1.5-ported integration tests under node-env vitest project (#1307) (#1312)
* feat(test): run v1.5-ported integration tests under node-env vitest (#1307)

Adds an `integration` vitest project alongside `unit` and `storybook` so
the 13 v1.5-ported tests that need real HTTP/stdio servers (or fail under
happy-dom's SDK mock resolution) run again under node env with 30s
timeouts. Restores test-servers/ as a buildable package so the stdio
subprocess spawns a real .js file. Drops the v1.5-source `coverage.exclude`
globs and fills coverage gaps across the v1.5-ported core/ modules; two
large files (inspectorClient.ts, remote/node/server.ts) keep narrow
per-file excludes tracked in #1310.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(core): address PR #1312 review — TS6-safe tsconfig, real transport_error path

- Drop `baseUrl` / `paths` / `typeRoots` / `declaration*` from
  test-servers/tsconfig.json. They were only needed for type
  resolution, and we already pass `--noCheck` from `test-servers:build`.
  Under TS 6+ the deprecated `baseUrl` exited with code 2, which
  silently skipped vitest in the `&&` chains of test:integration /
  test:coverage / validate.
- Rework the "send() throws after closed" test to drive `closed` via
  an actual transport_error SSE event instead of mutating private
  state through a cast — matches the real path the branch guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:15:17 -04:00
Cliff Hall
0ead1deea6
feat(core): port v1.5 InspectorClient + transports + auth subsystem (#1302) (#1306)
* wip(core): bulk-copy v1.5 InspectorClient runtime files (#1302)

Verbatim ports from origin/v1.5/main into v2/main core/:

- logging/ (2 files)
- storage/ + storage/adapters/ (4 files)
- auth/ + auth/browser/ + auth/node/ + auth/remote/ (16 files)
- mcp/config.ts + mcp/node/ (4 files, stdio transport)
- mcp/remote/ + mcp/remote/node/ (12 files, HTTP/SSE transport)
- mcp/inspectorClient.ts (~2169 LOC), messageTrackingTransport,
  fetchTracking, oauthManager, index, state/index

Total: ~7,800 LOC of source files, copied unmodified via
`git show origin/v1.5/main:<path> > <dest>`.

Also:
- Add deps: pino, hono, @hono/node-server, atomically, zustand, @types/node
- tsconfig.app.json: extend paths to resolve new node-only deps from
  core/, add "node" to types, disable erasableSyntaxOnly (v1.5 code
  uses parameter properties — TODO to re-enable in follow-up)

Compiles down to 47 type errors, all in three categories:
- 24 TS2305: v2 dropped v1.5 type exports (InspectorClientOptions,
  CreateTransport, InspectorClientEnvironment, AppRendererClient,
  SamplingCreateMessage class, ElicitationCreateMessage class,
  jsonUtils helpers, OAuth events). Per user direction, will re-add
  these to v2's diverged files (types.ts, samplingCreateMessage.ts,
  etc.) so the ported code stays verbatim.
- 15 TS7006: implicit-any params in v1.5 code (v1.5 tsconfig was
  looser than v2's noImplicitAny). Need explicit annotations.
- 8 other TS errors: ../package.json import, Transport|null narrowing,
  hono Context generic mismatch, one unused parameter.

Tests not yet ported (19 files, ~11,170 LOC).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* wip(core): integrate ported v1.5 InspectorClient + port tests (#1302)

Source integration (typecheck + build now pass):
- Re-add v1.5 types/classes dropped during v2 evolution:
  - types.ts: CreateTransport*, AppRendererClient, InspectorClient{Environment,Options}
  - samplingCreateMessage.ts / elicitationCreateMessage.ts: classes + keep v2's
    InspectorPending{Sampling,Elicitation} interface
  - inspectorClientEventTarget.ts: oauth* event keys
  - json/jsonUtils.ts: convertParameterValue / convertToolParameters /
    convertPromptArguments + ParameterSchema
- inspectorClient.ts: replace `../package.json` import (v2 has no
  core/package.json) with a hardcoded identity constant
- remote/node/server.ts: cast hono Context for streamSSE (hono's
  generic typing tightened since v1.5)
- oauth-callback-server.ts: drop unused hostname field
- tsconfig: enable @types/node, add paths for new bare-module deps
  (pino, zustand, hono, atomically) in app + test configs
- vite.config.ts: mirror those module aliases for the unit project
  (which runs from repoRoot)

Tests ported (19 files, ~11K LOC) under clients/web/src/test/core/:
- All core/__tests__/auth/* (7 files)
- inspectorClient.test.ts + 4 OAuth variants
- mcp/oauthManager.test.ts, jsonUtils.test.ts
- transport, remote-transport, remote-server-config, storage-adapters
- helpers/oauth-client-fixtures.ts
Bulk-rewrote `from "../mcp/…"` → `from "@inspector/core/mcp/…"` etc.

Current state:
- typecheck: 0 errors
- build: passes (vite v8, 788 modules)
- unit tests: 1014 / 1034 pass (937 pre-existing + 77 new core tests)
- 20 failures in 13 files; failure modes:
  - 13 file-load failures needing the v1.5 `test-servers/` package
    (@modelcontextprotocol/inspector-test-server) which isn't in v2 yet
    or real fs/network not available in happy-dom
  - 7 assertion failures in discovery.test.ts + state-machine.test.ts
    (SDK 1.29 vs 1.25 API drift — mocks don't match new shape)

Coverage gate not yet checked against new files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* wip(core): port test-servers, more types, partial test integration (#1302)

- Port v1.5 test-servers/ package (12 files, ~5K LOC) under
  repo-root/test-servers/, wire @modelcontextprotocol/inspector-test-server
  alias in both vite.config.ts (regex form) and tsconfig.test.json.
- Add deps: express ^5, yaml ^2, @types/express ^5.
- Re-export InspectorClientOptions/Environment/Transport* from inspectorClient.ts
  so v1.5 tests that import them from that module path keep resolving.
- Switch InspectorClientEventMap to use SamplingCreateMessage /
  ElicitationCreateMessage classes (matches v1.5 event payload contracts)
  while keeping the InspectorPending{Sampling,Elicitation} interfaces in
  the *.ts modules for v2 consumers that still use them.
- Fix verbatim-module-syntax issue in test-servers and drop unused stdio
  config field.
- Adjust inspectorClient.test.ts task types to TaskWithOptionalCreatedAt
  (SDK 1.29 tightened Task shape vs v1.5's 1.25).
- Drop spurious maxMessages/maxFetchRequests/maxStderrLogEvents from
  remote-transport.test.ts (never on InspectorClientOptions).

State:
- typecheck: 0 errors
- build: passes
- unit tests: 1099 / 1270 pass
- 171 failures in 10 test files, almost all integration tests that need
  real HTTP/fs/network (happy-dom can't host them). Next: add a node-env
  vitest project to run these properly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(core): port v1.5 InspectorClient + transports + auth subsystem (#1302)

Bulk-port of the v1.5 core runtime (~7,800 LOC source + ~16K LOC tests +
~5K LOC test-servers/) into v2. Files copied verbatim from origin/v1.5/main
via `git show >`; only minimal adapters added on the v2 side.

Subsystems landed
-----------------
- core/logging/, core/storage/, core/auth/ (browser + node + remote)
- core/mcp/ runtime: inspectorClient.ts (2169 LOC), oauthManager,
  messageTrackingTransport, fetchTracking, config, index
- core/mcp/node/ (stdio transport)
- core/mcp/remote/ + remote/node/ (HTTP/SSE transport + Hono server)
- test-servers/ package (composable MCP test servers, fixtures, harness)

v2 adapter layer
----------------
- types.ts: append CreateTransport*, AppRendererClient,
  InspectorClient{Environment,Options} (v2 had pruned these — v1.5 needs them)
- samplingCreateMessage / elicitationCreateMessage: keep v1.5 classes AND
  v2's Inspector Pending* interfaces in the same files
- inspectorClientEventTarget: add oauth* event keys; switch pending
  sampling/elicitation payloads to the classes (matches v1.5 contracts)
- json/jsonUtils: append convertParameterValue/convertToolParameters/
  convertPromptArguments + ParameterSchema
- inspectorClient.ts: re-export Options/Environment/Transport* so tests
  that import these from `inspectorClient.js` keep resolving; replace
  `../package.json` import with a hardcoded identity (v2 has no core/package.json)
- remote/node/server.ts: cast hono Context for streamSSE (hono generic
  tightened since v1.5)
- oauth-callback-server: drop unused hostname field
- tsconfig.app.json / tsconfig.test.json: add @types/node + paths for
  pino/zustand/hono/@hono/node-server/atomically/express/yaml, disable
  erasableSyntaxOnly (parameter-property syntax from v1.5)
- vite.config.ts: regex-form module aliases (so the package's exports
  field handles subpaths like `hono/streaming`, `zustand/middleware`),
  alias for @modelcontextprotocol/inspector-test-server → test-servers/src

Tests
-----
- 19 v1.5 test files copied under clients/web/src/test/core/,
  imports rewired to @inspector/core/* and @modelcontextprotocol/inspector-test-server
- 1004 unit tests pass; 298 storybook tests pass; coverage gate passes

Known follow-ups (out-of-scope here, tracked in TODOs)
------------------------------------------------------
- 13 integration test files are excluded from the unit project (spawn real
  HTTP/stdio servers; run real OAuth flows; mock SDK auth → happy-dom CORS).
  Needs a node-env vitest project. Files listed under `test.projects[0].test.exclude`.
- Source files exercised only by those integration tests are excluded from
  the coverage gate pending the node-env project. Listed under
  `test.coverage.exclude` with comment.
- Manual smoke tests against real stdio / HTTP / OAuth server per the AC
  on #1302 still need verification locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs/fix: address #1306 review (AGENTS tree, #1307 links, @types/node, .substr)

- AGENTS.md: expand the project-structure tree to include all v1.5-ported
  subsystems (core/auth/{browser,node,remote}, core/storage, core/logging,
  core/mcp/{node,remote/{,node}}, core/mcp/state) plus the new top-level
  test-servers/ directory (with a note about its @modelcontextprotocol/
  inspector-test-server alias).
- vite.config.ts: reference #1307 from both the test exclude block and the
  coverage exclude block so the next person editing either sees the
  marching order for shrinking the lists.
- package.json: restore @types/node to ^24 (an earlier `npm install
  --save-dev @types/node@^22` accidentally pinned it lower than the repo
  was using; nothing here required ^22).
- core/mcp/fetchTracking.ts: replace deprecated .substr(2, 9) with
  .slice(2, 11) to match the convention in core/mcp/inspectorClient.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:36:17 -04:00
Cliff Hall
e154378178
fix(coverage): include core/ files in v8 per-file gate (#1301) (#1305)
* fix(coverage): include core/ files in v8 per-file gate (#1301)

Two issues prevented coverage.include's '../../core/...' globs from
reaching core/ files:

1. allowExternal defaults to false — v8 drops any file whose absolute
   path doesn't start with the project root (clients/web/) before the
   include filter runs. Set allowExternal: true, and switch the globs
   to absolute paths so tinyglobby + picomatch handle them correctly.

2. vitest's coverage transformer only processes files inside a project
   root; for everything else it falls back to raw-TS parsing in
   rolldown, which can't handle TS-only syntax (e.g. import type) —
   silently dropping untested core/ files and bypassing the gate.
   Root the unit project at the repo root so the transformer reaches
   core/. The repo root has no node_modules, so add an explicit react
   alias and shift include/setupFiles paths accordingly.

Also add the four pure-type core files (samplingCreateMessage,
sessionStorage, inspectorClientProtocol, inspectorClientEventTarget)
to the exclude list with a comment — they have no executable code.

Verified by introducing an untested probe file, confirming exit=1 with
threshold errors, then removing it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(coverage): address review feedback on #1305

- Drop allowExternal: true — with the unit project rooted at repoRoot,
  core/ files are no longer "external" to any project that collects
  coverage (the storybook project doesn't), so the flag is redundant.
- Extract sharedAliases / sharedDedupe constants and reference them
  from both the top-level resolve and the unit project resolve, so
  the two stay in sync if a new core/* alias is added.
- Add TODO(#1243) on the inspectorClientEventTarget.ts exclusion so
  it's revisited once the class gains real behavior in the
  InspectorClient port.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 20:17:10 -04:00
Cliff Hall
e3e70470b5
feat(core): port v1.5 hook layer (#1243) (#1300)
* feat(core): port v1.5 hook layer (useInspectorClient + useManaged*/usePaged* + log hooks)

Wholesale port of v1.5's React hook layer and the EventTarget-based state machinery the hooks subscribe to (#1243). Hooks decouple from the concrete InspectorClient via a new `InspectorClientProtocol` interface so this layer can land before the runtime class is ported.

Added under `core/`:
- `core/react/` — useInspectorClient + 5 useManaged* + 5 usePaged* + 3 log hooks
- `core/mcp/state/` — 5 managed*State + 5 paged*State + messageLogState + stderrLogState + fetchRequestLogState
- `core/mcp/inspectorClientProtocol.ts`, `inspectorClientEventTarget.ts`, `typedEventTarget.ts`, `samplingCreateMessage.ts`, `sessionStorage.ts`
- v2-only wrapper types in `core/mcp/types.ts`: InspectorUrlElicitRequest, InspectorPendingRequest, InspectorRequestHistoryItem

Tests live under `clients/web/src/test/core/` (28 new test files, 264 new tests). Test fake: `core/mcp/__tests__/fakeInspectorClient.ts`.

Build wiring:
- `vite.config.ts`: dedupe react/react-dom so core/ files resolve them via clients/web/node_modules.
- `tsconfig.app.json` / `tsconfig.test.json`: paths map react/react-dom/vitest to clients/web/node_modules so `tsc -b` resolves bare imports from core/ source files (which sit above the package's node_modules). Exclude core/__tests__ from app config; include in test config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: address PR #1300 review findings

Bugs:
- fetchRequestLogState: add .catch() on sessionStorage.loadSession so a corrupt
  or unreadable session doesn't surface as an unhandled rejection (matches the
  saveSession swallow pattern).
- messageLogState: sync pendingRequestEntries when clearMessages() evicts a
  pending request entry, so a later response with the same id falls through
  to the append branch instead of mutating an unreachable map entry. Add tests
  for both predicate and no-predicate paths.

Cleanups:
- Extract mergeTaskIntoList helper to core/mcp/state/mergeTaskIntoList.ts so
  managed/paged RequestorTasks state classes share one definition.
- Add a one-line JSDoc note on each PagedTools/Prompts/Resources/Templates
  state explaining why they deliberately do not subscribe to *ListChanged
  (cursor invalidation mid-pagination).
- Document on useInspectorClient that appRendererClient is read lazily on
  every render and is not subscribed (changes once at connect time).

Carry-overs from v1.5 deliberately left for the follow-up InspectorClient
runtime port:
- Refresh/status-change race in Managed*State.refresh() (status check is only
  at entry; a disconnect during await can re-clear before refresh resumes).
- nextCursor exposure asymmetry across usePaged* (only usePagedRequestorTasks
  surfaces it via state field; others return it from loadPage() only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:43:07 -04:00
Cliff Hall
75f74d64c1
test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245) (#1269)
* test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245)

Establishes the unit-test runtime AGENTS.md mandates: a vitest "unit"
project alongside the existing storybook project, RTL + jest-dom +
happy-dom, a Mantine-aware render helper, and a 90%-line per-file
coverage gate enforced by CI. Adds 66 test files covering all 62
components under elements/groups/screens/views, plus core/mcp and
clients/web/src/utils. Total: 577 tests, 98.7% line coverage globally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(coverage): address PR #1269 review feedback

- AGENTS.md: correct coverage gate description (lines ≥ 90, statements ≥ 85,
  functions ≥ 80, branches ≥ 50) with rationale for the relaxed branch
  threshold
- InspectorView.test.tsx: document what the Math.random() mock values
  (0.1 / 0.99) control vs. STUB_SUCCESS_RATE in InspectorView.tsx
- vite.config.ts / tsconfig.test.json: drop unused `globals: true` and the
  `vitest/globals` type now that every test file imports describe/it/expect/vi
  explicitly; manual `afterEach(cleanup)` in setup.ts remains required because
  testing-library auto-cleanup relies on globalThis.afterEach

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:15:32 -04:00
cliffhall
8edff4ea77 Phase 0 foundations for component interfaces refactor
Stands up the type-only prerequisites for the v2 UX component
interfaces refactor per specification/v2_ux_interfaces_plan.md.

Phase 0.1 — MCP SDK dependency:
- Add @modelcontextprotocol/sdk ^1.29.0 and zod ^4.3.6 to both the
  root package.json and clients/web/package.json so component files
  can import Tool / Prompt / Resource / Task / LoggingLevel / etc.
  directly from the SDK.

Phase 0.2 — Inspector wrapper-types module:
- Create core/mcp/types.ts mirroring the type-only subset of v1.5's
  core/mcp/types.ts: MCPServerConfig (discriminated union), ServerType,
  MCPConfig, ConnectionStatus, StderrLogEntry, MessageEntry (Date
  timestamps, not ISO strings), FetchRequestEntry(+Base), ServerState,
  and the *Invocation types. Adds v2-only wrappers
  InspectorResourceSubscription and InspectorServerJsonDraft.
- Add core/mcp/taskNotificationSchemas.ts for the Inspector-owned
  notifications/tasks/list_changed signal (not in the SDK).
- Add core/mcp/elicitationCreateMessage.ts re-exporting ElicitRequest
  and ElicitResult plus an InspectorPendingElicitation interface for
  typing the pending-queue.
- Add core/json/jsonUtils.ts for the shared JsonValue / JsonObject
  types used inside core/mcp/types.ts.
- Copy v1.5's clients/web/src/utils/jsonUtils.ts (JsonSchemaType) and
  schemaUtils.ts (AJV-backed validators) verbatim so SchemaForm can
  switch off its local JsonSchema type.
- Copy v1.5's clients/web/src/lib/types/customHeaders.ts verbatim for
  ServerSettingsForm and the experimental panel.
- Add clients/web/src/types/navigation.ts with InspectorTab (UI-routing
  concept, lives with the web client rather than in core/).
- Wire tsconfig.app.json + vite.config.ts with the @inspector/core/*
  path alias so component files can import from core/ without
  relative-path hell.

Phase 0.3 — Vet (likely) hedges in v2_ux_interfaces.md:
- Follow-up sweep on commit 2ef257da. Replaces the remaining
  useMcpClient references with useInspectorClient, ties pending
  elicitation / sampling callbacks to the in-InspectorClient queue
  rather than a non-existent useElicitation / useSampling hook,
  corrects Task references to the SDK type (not an InspectorTask
  wrapper), corrects log hook wiring to useMessageLog, and strikes
  all remaining v2-only hooks with explicit "no v1.5 analog"
  annotations. grep "likely" specification/v2_ux_interfaces.md now
  returns zero results.

Phase 0.4 — Local-redeclaration inventory:
- specification/v2_ux_interfaces_phase_0_4_inventory.md enumerates
  every ad hoc local type (JsonSchema, LogLevel, TaskStatus,
  PromptItem, ResourceItem, ToolListItemProps, RootEntry, KeyValuePair,
  inline ConnectionStatus / transport unions) that will be replaced
  in Phases 1–4, including the file it lives in, every site that
  re-imports it, and the SDK / wrapper type that replaces it. Intended
  for the Phase 0 PR description and as a running checklist during
  the refactor.

Build state: npm run build / lint / format:check all pass in
clients/web.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:06:23 -04:00
cliffhall
fcd0d156f4 * Added base project to clients/web
* Added storybook build plan
2026-03-16 22:47:03 -04:00