Round 8 of the review asked for an AST. That was the right diagnosis of the
wrong problem: the question was never which parser to use, it was why a
test-timeout guard was parsing JavaScript at all.
It got there one reasonable step at a time. The guard enforces two rules no
Vitest config can report — a per-test `retry`, and Testing Library's
`asyncUtilTimeout` — so it read them out of source text, and the review found a
new valid spelling it missed in five consecutive rounds: a trailing comment,
nested parens in `it.each([makeCase()])`, a member-access prefix, the shorthand
`{ retry }`, an aliased import, a non-literal title. Each fix was correct and
each made it more parser-shaped, until it carried eight helpers doing quote
tracking and bracket balancing.
Both rules are observable at runtime, exactly:
- `vitest.setup.shared.mts` is loaded by all six projects and reads
`ctx.task.retry` — the value Vitest resolved for the test. That covers
strictly more than the scan did: a per-test option, a `describe` option
inherited by children, a project-level setting, and a `--retry` flag typed at
a command line, which the scan could never see since it only read npm scripts.
- `clients/web/src/test/asyncUtilTimeout.test.ts` asserts
`getConfig().asyncUtilTimeout`, the value this project's own waitFors read.
Neither has a spelling to anticipate, so neither can be outrun by a new one.
Both were observed firing before being trusted.
The guard keeps only what it can answer by asking Vitest: resolved budgets,
project-level retry, config discovery, and that each project still loads the
setup file — the one thing about the runtime assertion a config CAN report.
868 lines to 361, every scanner helper gone, and 33 of 48 unit tests deleted
because they pinned edge cases of a thing that no longer exists.
storybookSetup.ts goes with it. At the library's own value its configure() call
was a no-op, and the assertion is what pins the number.
AGENTS.md gains the general rule: a rule about what the code DOES is asserted at
runtime, not read out of the source — reach for a verify:* guard when the
question is about configuration, which a tool can be asked to resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
Two suppressed comments, both accurate.
- Pin zod, zod/v4 and open to the root in vitest.shared.mts, and add matching
paths to the three clients/web tsconfigs. The claim they replace — that these
resolve unambiguously by walk-up — was wrong: both still sit at the top level
of clients/web/node_modules as transitive copies (zod under
eslint-plugin-react-hooks, open under Storybook), so an unpinned bare import
from a web test resolved the client copy while core/ and the SDK packages
resolved the root's. For open that is only wasteful; for zod it is a schema
built by one copy and an instanceof check made by the other across the whole
@modelcontextprotocol/* surface. zod is deduped as well, and zod/v4 gets its
own entry because first-party code imports both specifiers and pinning one
would split the package rather than collapse it.
- Update the consumer comments that #2195 falsified. clients/web/vite.config.ts
(three places) and server/start-vite-dev-server.ts all still said the shared
aliases resolve pino/hono and friends from clients/web/node_modules; they now
state the split — react and react-dom from the client install, everything
root-declared from the repo root.
- local-dev skill: replace the wrong 'resolves unambiguously' list with the
distinction that actually matters. Deleting a declaration is not the same as
the package resolving from the root, because chokidar, zod, open and react
all sit in a client install transitively; check the install, not the manifest.
ajv, commander and undici carry no pin because no client install has a copy.
npm run local:gate passes from a cold tsc build.
Signed-off-by: cliffhall <cliff@futurescale.com>
- clients/web/tsconfig.{app,node,test}.json: repoint the pino, hono,
hono/streaming, @hono/node-server, atomically, chokidar and @napi-rs/keyring
path mappings from ./node_modules to ../../node_modules. The stale mappings
were not failing the build — an unresolvable paths entry falls back to
ordinary walk-up resolution, which finds the root copy — but chokidar is the
case where the fallback never runs: Vite's transitive copy still sits under
clients/web, so tsc was type-checking against the client's copy while Vitest
and the shipped bundle both resolved the root's. Same version today, silent
skew tomorrow. Both path prefixes now carry a comment saying which ownership
each one asserts.
- local-dev skill: the consolidation rationale no longer groups commander, open
and @hono/node-server (client-imported) with the core/ set, no longer claims
#2195 moved undici (it was already root-only), and no longer implies every
root-declared package has an alias.
npm run local:gate passes with clients/web/node_modules/.tmp cleared, so the
tsc results are from a cold build rather than an incremental cache.
Signed-off-by: cliffhall <cliff@futurescale.com>
* feat(web): expand ContentViewer to render PDF, CSV, HTML, XML, JSON, CSS (closes#1329)
Turn ContentViewer into a per-MIME dispatch surface for resource content.
It now accepts raw TextResourceContents | BlobResourceContents (in addition
to the existing ContentBlock path), so ResourcePreviewPanel.toContentBlock
collapses to a passthrough and blob-typed resources render natively instead
of falling back to opaque "[Binary content …]" text.
New renderers (clients/web/src/components/elements/):
- CodeHighlight: lazy react-syntax-highlighter prism-light element. Runtime,
tomorrow theme, and each grammar dynamic-import on first use (one chunk per
grammar). Module-level registeredLanguages / failedLoads / loadingPromises
caches; plain Code block until a grammar resolves.
- PdfFrame: iframe over a Blob URL (#view=FitH), revoked on unmount.
- CsvTable: papaparse -> Mantine Table (first 100 rows), plain-Code fallback.
- HtmlFrame: sandbox="" iframe over a Blob URL with an injected CSP meta
(no script-src, falls through to default-src 'none').
- contentViewerUtils: getMimeKind, base64 decode helpers, formatXml,
wrapHtmlWithCsp, SAFE_HREF markdown-anchor allowlist (unsafe href -> span).
ResourcePreviewPanel: extend the URI-suffix MIME inference table
(.csv/.json/.xml/.html/.htm/.css/.pdf alongside .md).
Deps: papaparse + react-syntax-highlighter (+ @types). tsconfig paths stubs
redirect the deep RSH imports for typecheck (the @types ambient declarations
are shadowed by the real .js under bundler resolution) — same pattern the
repo uses for react/pino.
Tests cover PDF blob-URL creation + cleanup, CSV table render + fallback,
HTML sandbox attributes + CSP injection, JSON/XML/CSS highlighted output,
the lazy-load/alias/failed-load/shared-runtime paths, and the URI-suffix
MIME-inference paths. validate, test:coverage (per-file gate), test:storybook,
and integration all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): harden ContentViewer blob/CSV rendering per review (#1551)
Address review feedback on the ContentViewer MIME-dispatch PR:
- useObjectUrl: fix a dev-only StrictMode bug where the committed object
URL was revoked but never recreated (setup→cleanup→setup with no
re-render), blanking PDF/HTML previews. Revocation is now deferred to a
microtask and guarded by a "live URL" ref so a StrictMode re-setup
cancels it, while a real unmount / blob change still releases the URL.
Avoids set-state-in-effect; mirrors AppRenderer's deferred-disposal.
- atob: guard base64 decoding with tryDecodeBase64ToUtf8 /
tryDecodeBase64ToBytes so a malformed blob from an external server
degrades to the binary-content notice instead of throwing during render
and taking down the whole preview panel.
- CsvTable: surface the 100-row display cap with a
"Showing first N of M rows" caption (no silent truncation).
- Extract BinaryNotice into its own module so PdfFrame can reuse it
without an import cycle.
Tests added for the StrictMode path, malformed-base64 fallbacks (PDF +
textual blob), the safe-decode helpers, and the truncation caption.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
* 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>
* refactor(core): apply v1.5-port cleanups deferred from #1306
Addresses issue #1309 — five small, mechanical cleanups deferred from the
v1.5 runtime port:
1. Collapse BrowserOAuthStorage / NodeOAuthStorage / RemoteOAuthStorage
onto a shared OAuthStorageBase. Three ~130-line duplicates become
thin adapter wrappers; behavior + public API unchanged.
2. Re-enable erasableSyntaxOnly in tsconfig.app.json by rewriting the
seven (plus two the issue missed in sampling/elicitation create-
message) parameter-property constructors as explicit field + assign.
3. Route the lone console.error in inspectorClient.ts through this.logger.
4. Replace `${Date.now()}-${Math.random()}` with crypto.randomUUID() at
the five local-only ID sites (message tracking + sampling/elicit).
5. Pin the remote-server hono app generic at new Hono<Env>() and drop
the streamSSE cast. Root cause of the cast was an unresolved hono
subpath: the tsconfig "hono/*" wildcard didn't honor the exports
map, so hono/streaming leaked to the root-hoisted hono@4.12.12 while
hono itself resolved to clients/web/hono@4.12.18. Replaced the
wildcard with an explicit hono/streaming entry in both tsconfig.app
and tsconfig.test, removing the package-identity mismatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(core): address #1316 PR review feedback
- Drop the stranded no-op getServerState() read at the top of
clearClientInformation in OAuthStorageBase (carried over verbatim
from the three originals).
- Drop the crypto.randomUUID() guard at the task-ID site so all
six in-scope sites are consistent; the fallback branch was dead
code on every supported runtime.
- Remove the residual `c as Context` cast at the streamSSE call site
in core/mcp/remote/node/server.ts — once both hono imports
resolve to the same package the route-typed Context flows through
to streamSSE with no cast at all.
- Move the new `options` / `config` field declarations in
RemoteClientTransport up with the rest of the field block, matching
the layout used by the other five rewritten classes in this 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>
* 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>
* 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>
* 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>