Commit graph inspector/clients/web/src/components/groups/ServerSettingsForm
Author SHA1 Message Date
Cliff Hall
85a4b4c057
feat(servers): persist per-server settings to mcp.json (#1352) (#1353)
* feat(servers): persist per-server settings to mcp.json (#1352)

- Add optional `settings` node alongside the transport config in each
  `~/.mcp-inspector/mcp.json` entry. Round-trips through
  `mcpConfigToServerEntries` / `serverEntriesToMcpConfig` and the
  `/api/servers` POST/PUT routes.
- Drop `MCPServerConfig.headers` from `SseServerConfig` /
  `StreamableHttpServerConfig` and remove the headers textarea from
  `ServerConfigModal`. Custom headers are now entered only in
  `ServerSettingsForm` and reach the wire via `settings.headers`.
- Wire `ServerSettingsModal` into `App.tsx`; saves dispatch through a new
  `useServers.updateServerSettings` mutator. `updateServer` carries the
  existing settings across config-only saves so they aren't dropped.
- Apply settings on the *first* outbound request after a server loads —
  no need to open the settings form first:
  - `settings.headers` → SSE / streamable-http wire headers.
  - `settings.metadata` → `InspectorClientOptions.defaultMetadata`,
    merged into every outgoing `_meta` (call-time keys win).
  - `settings.requestTimeout` → `InspectorClientOptions.timeout`.
  - `settings.connectionTimeout` → Promise.race wrapper around connect().
  - `settings.oauth*` → pre-seeded OAuth client credentials.
- `RemoteConnectRequest` carries `settings` so the remote backend can
  source transport headers from the same source as the local path.
- `oauthClientSecret` is persisted in `mcp.json` alongside stdio `env`
  values, both protected by the file's `0o600` permission.
- Update `specification/v2_servers_file.md` with the new shape and the
  removal of the legacy headers field.

Tests: round-trip in `serverList.test.ts`; persistence on POST/PUT in
`servers-route.test.ts`; first-connect header injection in
`transport.test.ts`; `defaultMetadata` merging in
`inspectorClient.test.ts`; `useServers.updateServerSettings` +
`updateServer` settings-preservation in `useServers.test.tsx`.

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

* fix(servers): rebuild InspectorClient on every connect so settings refresh

The previous onToggleConnection branch reused the existing InspectorClient
on a same-server reconnect, which froze settings at the moment the client
was first constructed. Editing headers, metadata, timeouts, or OAuth
credentials in the settings form was therefore invisible until the user
either switched servers or reloaded the page — surprising right after a
visible save.

Always call setupClientForServer on (re)connect so the latest
target.settings flow through to the transport's headers / requestInit, to
defaultMetadata, and to the OAuth manager.

Also adds remoteClientTransport tests that pin the wire-level contract:
settings round-trip through the /api/mcp/connect body when supplied, and
the field is omitted when no settings are configured.

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

* fix(history): stop wiping pendingRequestEntries inside the connect event

State managers (ManagedToolsState, ManagedPromptsState, ManagedResourcesState,
ManagedResourceTemplatesState, ManagedRequestorTasksState) handle the
client's "connect" event by calling `void this.refresh()`. The synchronous
portion of refresh runs through SDK Protocol.request → transport.send →
MessageTrackingTransport.send → trackRequest, all before yielding to the
network — so each list request gets tracked into MessageLogState's
pendingRequestEntries inside the same connect event dispatch.

MessageLogState's own "connect" listener was registered after those state
managers (it's the last one constructed in App.tsx) and cleared
pendingRequestEntries on every connect. Listener order meant the clear ran
*after* the requests were tracked but before their responses arrived,
leaving the history with unmatched "response" entries marked PENDING.

The clear-on-disconnect (via statusChange → disconnected) already handles
session-boundary cleanup, so clear-on-connect was purely defensive and now
actively harmful. Drop the connect listener; keep the disconnect path.

Tests: updated the unit test to assert the new behavior (connect does not
clear); added an integration regression that wires a sibling connect
listener firing listTools and asserts the response folds into the request
entry instead of appearing as an orphan.

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

* chore: drop accidentally tracked debugging screenshot

pending-requests.png was a local debugging artifact attached to a
conversation, not source. Remove it from the tree (the working copy is
kept so the user still has it locally) and rely on the existing untracked
behavior going forward.

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

* test(servers): add e2e check that settings.headers reach upstream MCP

Stitches the full v2 pipeline together in one assertion:

  browser-style RemoteClientTransport
    → POST /api/mcp/connect (settings in body)
    → backend createTransportNode applies settings.headers to requestInit
    → SDK StreamableHTTPClientTransport
    → upstream MCP test server

Captures the upstream HTTP request via FetchRequestLogState and asserts
both `X-Tenant` and `X-Trace` are present on the outbound POST. Catches
any regression that breaks the wire between the form and the upstream
server in a single integration test, instead of the previous coverage
which exercised each hop in isolation.

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

* fix(ServerSettingsModal): invert ListToggle compact prop so icon matches state

ListToggle's `compact` prop represents the list's *current* compact state:
true means "currently compact" and renders the expand icon to advertise
the action (open up). The settings modal was passing
`compact={allExpanded}` — exactly inverted. When all sections were open
it showed the expand icon (suggesting "open more") and when collapsed it
showed the collapse icon, opposite of every other ListToggle caller in
the app.

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

* style(HistoryListPanel): match ServerListScreen Export button — rename + default variant

Renames "Export JSON" to "Export" and switches from the subtle ToolbarButton
to the same `Button variant="default"` used by ServerListControls so the
two Export affordances look identical across screens. Also moves the
button to the right of the ListToggle (mirroring the order on the server
list screen) and disables it when there are no entries to export.

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

* style(HistoryListPanel): move Clear button to top toolbar, match Export style

Moves the Clear affordance out of the "History (N)" section header and
up to the panel toolbar alongside Export, where it now uses the same
`Button variant="default"` styling. Order in the toolbar is now:
ListToggle · Clear · Export. Clear is disabled when there are no
unpinned entries to remove (pinned entries are intentionally preserved).

ToolbarButton helper is unused after this change and dropped.

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

* chore: ignore local debugging screenshots at repo root

Stops `git add -A` from re-staging the `*.png` files Claude Code drops
at the repo root from attached conversation screenshots. The pattern
only matches the top-level (`/*.png`) so legitimate image assets in
subdirectories (e.g. `clients/web/public/`) still get tracked when
explicitly added.

Also untracks the two screenshots that slipped through previously
(initialize-request.png, pending-requests.png).

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

* style(LogStreamPanel): match HistoryListPanel button styling

Clear / Export / Copy All on the Logging screen were rendered with the
subtle ToolbarButton helper while History's equivalents use
`Button variant="default"`. Switch all three to the default variant so
the toolbars look identical across screens and mirror the disabling
behavior — each button is now disabled when `entries.length === 0`.

ToolbarButton helper is unused after this change and dropped.

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

* fix(servers): address PR review — debounce save, preserve-on-omit PUT, validate shape, lift connect timeout

Round-up of fixes from https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4526861959:

1. ServerSettingsModal saves were firing on every keystroke (one PUT +
   full mcp.json refresh per character). App.tsx now debounces by 300ms
   and flushes on modal close so a burst of edits coalesces into one PUT
   and the form value can't flicker back mid-typing.

2. `PUT /api/servers/:id` with an omitted `settings` field used to
   silently wipe the persisted settings node. The route now treats the
   three intents distinctly:
     - field omitted  → preserve existing on-disk settings
     - explicit null  → clear the settings node
     - settings object → validate + apply
   `useServers.updateServer` is simplified to drop the client-side
   carry-through hack, and the test that documented the broken behavior
   is flipped + augmented with explicit-clear and bad-shape cases.

3. `connectionTimeout` Promise.race is moved from App.tsx into
   `InspectorClient.connect()`, reading `this.serverSettings?.connectionTimeout`.
   TUI/CLI consumers now get the same behavior; on timeout the client
   internally disconnects so the next connect starts clean.

4. Custom Headers hint in `ServerSettingsForm` notes that `Authorization`
   is owned by the OAuth flow when OAuth is configured — avoids the
   sharp edge where the SDK's authProvider silently overwrites a
   user-set Authorization header.

5. New `validateSettings` on the server route rejects malformed shapes
   (`settings: []`, `settings.headers: "oops"`, non-numeric timeouts,
   etc.) with 400 instead of letting them persist and crash the UI.

7 (nit). Renamed `settings` → `savedSettings` inside `setupClientForServer`
   so the variable holding the persisted settings doesn't shadow the
   later locally-derived values.

Follow-ups opened separately:
  #1356 — Move oauthClientSecret out of mcp.json into the OS keychain (review #6).

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

* fix(servers): address PR review pass 2 — connect-timeout test, unmount cleanup, tagged intent, lenient settings read

Pass-2 review at https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4527089705:

(a) New integration test pins the new client-layer connect-timeout
    contract: a hanging stub transport with `connectionTimeout: 50`
    rejects with the descriptive error and lands the client in "error"
    status. Documents what TUI/CLI consumers should expect from the
    lifted Promise.race in InspectorClient.connect().

(b) useEffect cleanup clears `pendingSettingsTimerRef` on unmount so a
    route change or HMR mid-debounce can't fire one stale PUT against
    an unmounted component.

(c) Refactor the PUT-route `settingsIntent` discriminator from
    `"preserve" | "clear" | { value }` to a tagged union with `kind`,
    and consume it with a `switch` for exhaustiveness — reads cleaner
    and makes adding a fourth intent painless.

(d) `normalizeMcpServers` now runs `validateSettings` on the on-disk
    settings node and silently strips it if invalid, mirroring the
    existing lenient-on-read pattern from `normalizeServerType`. Closes
    the read/write asymmetry where a hand-edited malformed `settings`
    could survive on disk and get carried through preserve-on-PUT.
    New route test pins the strip-on-GET + no-propagate-on-preserve-PUT
    behavior.

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

* fix(servers): pass-3 review — patch-style PUT, toast on close-flush fail

Pass-3 review at https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4529276897.

(b) `PUT /api/servers/:id` now treats both `config` and `settings` as
    optional patches:
      - field omitted → preserve the on-disk value
      - explicit body value → apply
      - settings: null → clear
    `updateServerSettings` stops reading `existing.config` from the
    in-memory `servers` snapshot, which closes the closure-capture
    hazard where a refresh between debounce-schedule and flush could
    silently revert a separate edit. The route is the single source of
    truth for transport config now. New tests cover settings-only PUT,
    rejected non-object config, and the empty-body no-op patch case.
    The previous "rejects a missing config" test is rewritten to
    document the new no-op-preserve semantics.

(c) Inline comment near the PUT rebuild loop documents that writing
    the full mcpServers map back is what gives the route its desirable
    self-healing property for malformed settings on *other* entries —
    deliberate side-effect, not an accident of normalization.

(d) `flushPendingSettings` surfaces failures via @mantine/notifications
    instead of swallowing them. The modal already closed by the time
    the flush PUT resolves, so a silent fail would leave the user
    thinking their last edits saved (especially painful for the OAuth
    client secret). The notifications wiring was already mounted in
    main.tsx.

Pass-3 (a) — settings form being effectively uncontrolled mid-edit — is
acknowledged in the reply: working as intended today; the proper fix is
internal modal state when file-watching (#1345) forces the issue.

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

* fix(servers): pass-4 review — strip smuggled settings out of config

Pass-4 review at https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4529861349.

Main finding: `normalizeServerType` spreads unknown keys, so a body
that nested `settings` inside `config` would smuggle a settings field
onto the stored entry without ever passing through `validateSettings`.
Strip the settings key off the incoming config in `buildStoredEntry`
so `validateSettings` remains the single write path for the settings
node — the "one source of truth" invariant pass 2 set up holds again.

Two new tests pin the strip on both routes:
  - POST with `config.settings = { bogus }` lands an entry with no
    settings node on disk.
  - PUT with `config.settings = { bogus }` + `settings: null` clears
    the real settings *and* doesn't re-attach the bogus payload via
    the spread.

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

* fix(servers): pass-5 review — absorb late connect rejection, tighten validateSettings, log smuggle warning

Pass-5 review at https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4529954490.

Main finding: when the connect-time `Promise.race` lost to the timeout,
the original `this.client.connect(this.transport)` promise was left
pending. After `disconnect()` tore the transport down, real transports
(SSE / streamable-http) would reject that promise later — with no
handler attached, producing a Node `unhandledRejection` (and a vitest
suite warning). Attach a no-op `.catch` right after creating the
connect promise so Node sees the late rejection as handled.

Nit (a): `validateSettings` was casting the raw object through to
`InspectorServerSettings`, which let unknown keys ride along onto disk.
Replaced the cast with an explicit pick-and-build over the known
fields. Unknown stowaways now silently drop on the way through. New
route test pins the behavior.

Nit (b): `buildStoredEntry` now logs a `warn` via the route's pino
logger when an incoming `config` carries a `settings` key. The strip
is still defensive (it isn't a documented contract) but the warning
lets a misconfigured client find out about the wasted field instead
of debugging a silent drop.

Nit (c) — the defensive `?? {}` fallback in `buildStoredEntry` —
acknowledged in the review reply; left in place as belt-and-braces.

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

* fix(servers): pass-6 polish — scope absorber, thread id, drop empty OAuth, guard `in`

Pass-6 review at https://github.com/modelcontextprotocol/inspector/pull/1353#issuecomment-4529979135. Reviewer signed off as ready; landing the four marginal cleanups since they're all small.

(a) `connectPromise.catch(() => {})` moves inside the
    `if (connectTimeoutMs > 0)` branch. The await-only path propagates
    rejection cleanly through the outer try/catch — the absorber is
    only needed when the race leaves the original promise unattended.
    Code now reads as "absorber is tied to the race," which is the
    real invariant.

(b) Thread `id` (POST id, PUT newId) through `buildStoredEntry` and
    include it in the smuggle warning's pino bindings. Two clients
    smuggling simultaneously are distinguishable in the log instead
    of indistinguishable.

(c) `validateSettings` coerces empty-string OAuth fields to absent.
    The form emits `""` when the user clears an input, and an empty
    `oauthClientId` on disk would later be misread as "OAuth configured."
    New route test pins it.

(d) `buildStoredEntry` adds a null/object guard before `"settings" in
    configObj`. The route layer pre-checks this on POST and PUT, but
    the helper's `unknown` parameter is a weak contract; the guard
    makes it safe to call from anywhere.

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

* docs(spec): reflect settings persistence + patch-PUT contract in v2_servers_file

The doc was authored when `mcp.json` only stored the server list. Bring it
forward to match what shipped in #1352 / PR #1353:

- Summary calls out that the file also stores per-server settings and
  links into the Per-server settings section.
- On-disk format note explains that `settings` is an optional Inspector-
  specific extension that other MCP tools ignore.
- New top paragraph on the Per-server settings section explains the
  basic-config / settings UI split rationale (simpler form for the
  common case, advanced settings tucked behind a second dialog).
- UI bullet is rewritten to match the patch-style PUT semantics that
  emerged across the review cycle: settings-only and config-only
  PUTs preserve their counterpart inside the write lock, so neither
  modal can silently clobber the other half. The earlier "must
  re-send settings on PUT" hack is gone.
- New bullets capture the save cadence (300ms debounce + flush-on-close
  + failure toast), the patch-PUT contract (omit-preserve / null-clear /
  rename), and the write-path / read-path validator gates.
- Secret storage bullet links to #1356 (OS-keychain follow-up).

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

* docs(spec): show a third entry with the optional settings node

The On-disk format example demonstrated only basic stdio entries. Add a
streamable-http entry with a populated `settings` node so readers can
see the shape inline rather than having to scroll to the Per-server
settings section. Inline comment in the block cross-links there for the
full contract and notes that other MCP tools ignore the extension key.

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-24 19:23:26 -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
1149288ea6 Refactor ServerCard stack to use id-based callbacks
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>
2026-04-25 18:33:38 -04:00
cliffhall
d27018b4f2 Change onOAuthChange to pass all OAuth fields together
Replace onOAuthChange(field, value) with onOAuthChange(oauth) where
oauth is { clientId, clientSecret, scopes }. Each keystroke now
reports the full OAuth state, not just the single field that changed.
This lets the consumer see the complete picture on every change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 20:15:12 -04:00
cliffhall
e04befa4dd Fix ServerSettingsForm stories to use useArgs for action logging
Use Storybook's useArgs hook from storybook/preview-api to update
args from within the render function. This wires fn() callbacks to
the Actions panel AND updates the UI state through Storybook's args
system. All form interactions (add/remove/edit headers/metadata,
accordion open/close, connection mode, timeouts, OAuth) now both
update the UI and log to the Actions panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 19:37:27 -04:00
cliffhall
a3be456f56 Remove unused fn() from ServerSettingsForm stories
The Storybook config has no addon-actions/addon-essentials, so fn()
calls never produce visible output. The stories are fully interactive
via useState — add/remove/edit headers, metadata, timeouts, OAuth,
and connection mode all work visually in the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:13:40 -04:00
cliffhall
66dcd82a42 Log actions alongside state updates in ServerSettingsForm stories
Each handler now both updates local state (so the UI reacts) and
calls a Storybook fn() spy (so the Actions panel logs every
callback with its arguments).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:04:16 -04:00
cliffhall
39ed8b0fec Make ServerSettingsForm stories fully interactive
Replace fn() stubs with useState handlers so add/remove/edit for
headers, metadata, connection mode, timeouts, and OAuth all work
interactively in Storybook. The InteractiveForm wrapper manages
settings state the same way ServerSettingsModal does.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:00:05 -04:00
cliffhall
a6fc900271 Add ServerSettingsModal group, revert form to controlled component
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>
2026-04-21 17:13:57 -04:00
cliffhall
adb909daf6 Internalize accordion state in ServerSettingsForm
Move expandedSections/onExpandedSectionsChange from props to internal
useState. The accordion open/close is local display logic, not parent
state. Add optional defaultExpandedSections prop for initial state.

Simplify UnconnectedView WithSettingsModal story — no longer needs
external state management for accordion sections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 17:00:25 -04:00
cliffhall
cc9be5e930 Address PR #1221 code review feedback
- Fix getCapabilityEntries to iterate fixed key sets so unsupported
  capabilities render red X (was only showing present/supported ones)
- Merge duplicate @mantine/core import in ViewHeader
- Move ConnectionState from ServerCard to core/mcp/types.ts
- Narrow onConnectionModeChange to "proxy" | "direct" instead of string

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 16:24:27 -04:00
cliffhall
771a67ee20 Phase 2: Refactor Server/Settings/ViewShell cluster to MCP SDK types
ServerCard: Replace flat scalar props (name, version, transport, status,
error) with grouped objects (config: MCPServerConfig, info?: Implementation,
connection: ConnectionState). Remove auto-connect useEffect — dumb
components must not self-dispatch side effects. Derive transport and
command/url from config.

ServerInfoContent: Replace flat props with initializeResult: InitializeResult
and clientCapabilities: ClientCapabilities from MCP SDK. Derive capability
lists by iterating the ServerCapabilities record.

ServerSettingsForm: Collapse flat setting props (connectionMode, headers,
metadata, timeouts, oauth*) into settings: InspectorServerSettings. Add
InspectorServerSettings type to core/mcp/types.ts.

ImportServerJsonPanel: Replace scattered scalar props (jsonContent,
selectedPackageIndex, serverName) with draft: InspectorServerJsonDraft.

ViewHeader: Replace serverName: string with serverInfo: Implementation.
Use ConnectionStatus from core types.

ConnectedView: Update to pass serverInfo: Implementation instead of
serverName. Use ConnectionStatus type.

Update all stories for ServerCard, ServerInfoContent, ServerSettingsForm,
ImportServerJsonPanel, ViewHeader, ServerListScreen, ConnectedView, and
UnconnectedView.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 15:22:08 -04:00
cliffhall
8483b98c37 Collapse ServerSettingsForm into accordion and add subtle ListToggle variant
Wraps every section of ServerSettingsForm (Connection Mode, Custom
Headers, Request Metadata, Timeouts, OAuth Settings) in a controlled
Mantine Accordion. The accordion is driven by new required
`expandedSections` / `onExpandedSectionsChange` props so the parent
owns expand/collapse state — consistent with the dumb-component
principle.

Adds a `variant?: "default" | "subtle"` prop to ListToggle. In subtle
mode it renders as an ActionIcon with `variant="subtle"`, `color="gray"`,
and a 16px icon so it visually matches Mantine's CloseButton in modal
headers (transparent until hover).

Adds a WithSettingsModal story on UnconnectedView that presents the
form inside a Mantine Modal with a custom three-column header: subtle
ListToggle on the left (expands/collapses all sections at once),
"Server Settings" Title centered, CloseButton on the right.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:36:14 -04:00
cliffhall
dd46acba87 Refactor renamed the component groups to something less obtuse:
Elements -> Groups -> Screens -> Views
2026-03-28 12:58:47 -04:00