feat(web,core): persist & edit STDIO cwd/env in Server Settings (#1515) (#1518)

* 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>
This commit is contained in:
Cliff Hall 2026-06-24 19:15:43 -04:00 • committed by GitHub
commit 5ee347571f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,6 +13,7 @@ const initialSettings: InspectorServerSettings = {
{ key: "Authorization", value: "Bearer token-abc-123" },
{ key: "X-Request-Id", value: "req-456" },
],
env: [],
metadata: [{ key: "userId", value: "user-789" }],
connectionTimeout: 30000,
requestTimeout: 60000,
@ -52,6 +53,7 @@ const meta: Meta<typeof ServerSettingsModal> = {
render: InteractiveRender,
args: {
opened: true,
isStdio: false,
onClose: fn(),
onSettingsChange: fn(),
},
@ -70,6 +72,7 @@ export const EmptySettings: Story = {
args: {
settings: {
headers: [],
env: [],
metadata: [],
connectionTimeout: 30000,
requestTimeout: 60000,
@ -79,3 +82,25 @@ export const EmptySettings: Story = {
},
},
};
// A stdio server surfaces the Working Directory field (Options) and a dedicated
// Environment Variables section.
export const StdioServer: Story = {
args: {
isStdio: true,
settings: {
headers: [],
env: [
{ key: "API_KEY", value: "abc-123" },
{ key: "LOG_LEVEL", value: "debug" },
],
metadata: [],
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,
maxFetchRequests: 1000,
cwd: "/srv/my-server",
roots: [],
},
},
};