Closes#1910
`_meta` was typed `Record<string, string>` end to end in the Inspector —
on disk as a `{ key, value }[]` pair array, in the Server Settings form as
key/value rows, and in every request verb's per-call metadata parameter.
Nothing in the MCP spec restricts a `_meta` value to a string, and the SDK
models the field as a `z.looseObject`, so the restriction was ours alone.
Widen the whole path to `RequestMetadata` (`Record<string, JsonValue>`):
- Per-server default metadata is a JSON **object** on disk and in memory.
The pre-#1910 pair array is still read (`normalizeStoredMetadata`) so an
existing `mcp.json` keeps working; it is never written back, so a save
migrates the entry.
- The Server Settings → Request Metadata section is a JSON editor
(`JsonObjectInput`) instead of key/value rows — rows cannot express an
object value. It keeps the typed text as the display source and emits
only parsed JSON, the same draft split `SchemaJsonField` uses, so the box
stays typeable; invalid text is flagged inline and not applied.
- The CLI stops flattening `--metadata` / `--tool-metadata` through
`metaValueToString`. Those values were already JSON-parsed, so
`trace={"id":"abc"}` used to reach the wire as the *string* `{"id":"abc"}`
and now reaches it as an object.
- `servers/show` redaction replaces a sensitive key's whole value rather
than a string, so a structured value cannot leak through its members.
- `progressTokenOf` narrows the reserved `progressToken` key back to
`string | number` before it reaches the SDK; any other JSON still travels
inside `_meta` but wires up no progress callback.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>
Adds enterprise-managed authorization (EMA): browser-side IdP login + RFC 8693 token-exchange and JWT-bearer resource-token mint, per-server EMA opt-in in Server Settings, IdP config in Client Settings, and IdP clientSecret keychain storage. Includes the v2/main merge and review follow-ups (400 on malformed client config; normalizeIdpIssuer import consolidation).
* feat(web,core): edit STDIO cwd/env in Server Settings (#1515)
Persist and edit a STDIO server's working directory and environment
variables through the Server Settings modal.
- Add `env` (pair-array, mirrors headers) and `cwd?` to
`InspectorServerSettings`.
- `storedFieldsToInspectorSettings` lifts the stdio config's `env`/`cwd`
into settings so the modal can show them; they stay on `config` for the
transport. Add `envRecordToPairs` / `envPairsToRecord` helpers.
- `env`/`cwd` remain config-owned on disk (the standard mcp.json location):
the `/api/servers` PUT route writes an edited settings.env/cwd back onto
the stored stdio config on a settings-only patch (supporting clear), and a
config-providing PUT keeps config authoritative. `validateSettings`
accepts `env`/`cwd`.
- Server Settings modal: Working Directory field in Options and a dedicated
collapsible Environment Variables section — both STDIO-only.
- Registry server.json import sets no `cwd` (the schema has no
working-directory concept); documented in serverJson.ts.
Tests: serverList converter round-trip, route write-through (set/clear,
config-precedence, non-stdio no-leak, validation), import preservation
(client-config cwd, registry cwd skip), and form/modal component coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): preserve config env/cwd when a settings PUT omits them (#1515 review)
Address Claude's review of #1518.
- `/api/servers` PUT now distinguishes an *absent* `env`/`cwd` (preserve the
stored value) from an explicit empty list / blank string (clear). Previously
`validateSettings` coerced a missing `env` to `[]`, so an env-unaware caller
patching only a timeout would silently wipe the server's env/cwd. The
integrated web client always resends the full GET-rehydrated env, so it was
unaffected — this protects the raw HTTP contract.
- `validateSettings` reports `envProvided`/`cwdProvided`; the write-through only
touches `config.env`/`cwd` for fields the caller actually sent.
- On the omit path, rehydrate the untouched env's keychain values onto the
rebuilt entry so the secret reconcile re-persists them rather than sweeping
the blanked-on-disk keys out of the keychain.
- Minor: guard the stdio cast in `mcpConfigToServerEntries` on the transport
type instead of casting unconditionally.
- Test: settings-only PUT omitting env/cwd preserves config keys + cwd + the
keychain secret.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): trace dropped fetch body update when entry rotates out (#1390)
FetchRequestLogState.onFetchRequestBodyUpdate silently dropped a deferred
response-body update when the matching entry had already been evicted by
log rotation (>= maxFetchRequests newer requests appended between the
append and the fire-and-forget body read). The user saw an empty Response
Body with no way to distinguish "body never came back" from "body dropped
after eviction".
Plumb an optional pino logger into FetchRequestLogState (defaulting to the
silent logger) and emit a logger.debug at the early-return site so the drop
is traceable. The web client wires the existing remote logger from
createWebEnvironment into the state manager.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): emit dropped-body trace at warn level so it is observable (#1390)
Review feedback: the web remote logger (createRemoteLogger) defaults to
level 'info' with no override at the environmentFactory call site, so a
logger.debug at the drop site is filtered out by both the pino level gate
and transmit.level — the diagnostic never reaches /api/log and the drop
stays invisible in the shipped web app, defeating the issue's goal.
Promote the trace to logger.warn (>= info, so it transmits by default). A
response body lost to log rotation is unexpected data loss, not routine
diagnostic noise, so warn is the appropriate severity. Update the comment
to explain the level choice and switch the test stub from debug to warn,
extracting a small typed makeWarnLogger helper per review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): configurable Network Log Size per server + body-dropped toast (#1390)
Turn the otherwise-invisible "response body dropped after rotation" case into
an actionable signal:
1. Per-server `maxFetchRequests` setting (default 1000, 0 = unlimited),
editable as "Network Log Size" in the first (Options) accordion section of
the Server Settings form. Plumbed through the full settings pipeline:
InspectorServerSettings + StoredMCPServer types, the serverList disk
converters (read defaults to 1000, write omits the default to keep diffs
minimal — 0 is a meaningful non-default), and the node server's GET-sanitize
+ write-side validateSettings (non-negative number). Applied live to the
active connection on settings-modal close via a new
FetchRequestLogState.setMaxFetchRequests (shrinking trims immediately), and
at connect time for the next session.
2. FetchRequestLogState now emits a `fetchRequestBodyDropped` event, but only
for genuine rotation drops (log at capacity with a finite cap) — not for
benign post-clear stragglers or unlimited logs, which previously also hit
the warn path. App shows a deduped, persistent toast (stable per-server id,
no auto-close) explaining what happened and the likely causes, with a link
that opens this server's settings on the Options section (modal remounts via
key) so the user can raise the limit if it's just a high-traffic server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): don't treat a cleared Network Log Size field as unlimited (#1390)
Review feedback: clearing the NumberInput emits "", and `parseInt("") || 0`
coerced that to 0 (unlimited). Since the value applies live on modal close, a
user who cleared the field to retype and then closed the modal would silently
switch the log to unlimited. Fall back to the current value on an empty/NaN
parse and reserve 0 for an explicit numeric entry. Adds a cleared-input test.
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(web): add per-server Roots config + client roots capability (#1425)
Add MCP roots support to the v2 web Inspector as a per-server config
section in the server settings form (no separate Roots tab/screen).
- Persistence: add `roots: Root[]` to `InspectorServerSettings` and
`StoredMCPServer`; thread through serverList converters (lift on read,
drop empty-uri rows / blank names on write, omit when empty) and the
remote server's read-side drop guard + `validateSettings`.
- Capability: `setupClientForServer` always advertises the roots
capability (`roots: cleanRoots(savedSettings?.roots ?? [])`) so the
server can issue `roots/list` and receive `roots/list_changed`.
- Live apply: edits to a connected server's roots are pushed via
`inspectorClient.setRoots(...)` once, on settings-dialog close — diffed
against the client's current roots — rather than per keystroke, to
avoid flooding the server with `roots/list_changed` / `roots/list`.
- Form: new Roots accordion section with a required URI and an optional
name per row (unlike v1, which was URI-only), + Add Root, and remove.
Tests cover the persistence round-trip (serverList + servers-route),
the form/modal section, and reuse the existing InspectorClient
roots/`setRoots` integration coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(roots): address review — dedup guards/cleanRoots, preserve extra fields, test live-apply
Responding to the PR #1431 code review:
- Dedup `isRootArray`: drop the copy inside `validateSettings` and reuse the
module-scope guard the read path uses, so the two can't drift
(core/mcp/remote/node/server.ts).
- Dedup the clean-roots normalizer: hoist a single exported `cleanRoots`
into core/mcp/serverList.ts and reuse it from both
`inspectorSettingsToStoredFields` and App.tsx (drops the local copy).
- Preserve non-form root fields (e.g. `_meta`): `cleanRoots` now spreads the
carried-through fields and only normalizes uri/name, and the modal's
`handleRootChange` spreads the existing root on edit — so a hand-edited
root's `_meta` survives load + persist + edit instead of being silently
dropped.
- Test the App live-apply-on-close path: new App.test.tsx cases assert
setRoots is called once with cleaned roots when changed on the active
server, and not called when the cleaned roots already match what the
client advertises. Adds a cleanRoots unit test (blank drop + _meta
preserve).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires the ported Tasks screen + core task subsystem through App.tsx: sticky Clear Completed, cancel/refresh error toasts, core-side per-task progress correlation, Run-as-task on the Tools screen (gated on server support), per-server Task TTL, and live task-status toasts.
Distinguishes server `id` (the unique map key in MCPConfig.mcpServers)
from `name` (display label) so containers can dispatch by id without
relying on display strings. ServerCard, ConnectionToggle, ServerListScreen,
and supporting stories all updated.
- ServerCard: add `id` prop; all action callbacks now `(id: string) => void`
- ConnectionToggle: collapse `onConnect`/`onDisconnect` into single `onToggle`
- ServerListScreen: lift `activeServer` to a prop; combine `onSetActive` +
`onToggleConnection` into a single `onToggleConnection(id)` callback
- Stories: use UUIDs for server ids so callbacks visibly carry ids, not names
Removes the "Direct"/"Via Proxy" connection mode concept, which is no
longer planned. ServerSettingsForm drops the section, ServerCard drops
the badge, InspectorServerSettings drops the field.
Removes the "Test Client Features" dropdown from ServerCard along with
its four handlers; that surface will live elsewhere.
TransportBadge now collapses `sse` and `streamable-http` into a single
"HTTP" label; ServerCard adds a grey transport descriptor next to the
badge ("Standard I/O", "SSE (Server Sent Events) [deprecated]",
"Streamable HTTP"). The version badge moves from the header row to sit
beside the transport badge.
ServerCard header no longer wraps; long server names truncate with
ellipsis.
Specification docs (`v2_scope.md`, `v2_screenshots.md`,
`v2_ux_interfaces.md`, `v2_ux_storybook.md`) updated to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use args + useArgs pattern so onSettingsChange and onClose fire
through Storybook's args system. Every form interaction now logs
to the Actions panel with the full updated settings object.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap modal stories in AppShell to provide a page background behind
the modal overlay. Add fullscreen layout parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert ServerSettingsForm to a controlled component with
expandedSections/onExpandedSectionsChange props. The form is a
dumb component — accordion state belongs to its container.
Create ServerSettingsModal group that manages:
- Accordion expand/collapse state with ListToggle expand-all button
- Add/remove/edit header pairs
- Add/remove/edit metadata pairs
- Connection mode, timeout, and OAuth changes
- All changes flow through a single onSettingsChange callback
Update ServerSettingsForm stories with useState render functions
so accordion sections are interactive.
Update UnconnectedView WithSettingsModal story to use the new
ServerSettingsModal component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>