inspector/clients/web/src/test/integration/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
cliffhall 339aea3729 fix(auth): replace the secrets-file lock with optimistic verify-and-retry, and address review round 9
Two changes, interleaved in the same files because both are about the
same read-modify-write path.

## The lock is gone (maintainer decision on the round-5/7/8 finding)

Three consecutive review rounds found a real race in the `mkdir` lock, and
the last one is not patchable with what Node exposes: claiming a stale lock
needs compare-and-swap on a directory entry (`renameat2(RENAME_EXCHANGE)`).
Without it, `canSteal` inspects one lock but `rename` acts on whatever is at
that path *later*, so a waiter that lost the first race can move the
winner's fresh lock aside and enter alongside it.

Rather than patch it a fourth time, writers now collide and the loser
notices. `mutate` reads `M0`, applies the mutation to get `M1`, writes it,
then reads back `M2`; if they differ, someone wrote in between, so it
re-applies onto what they left and retries, bounded.

The comparison is over the **whole map**, and that is the load-bearing
detail. Checking only the entry we touched passes in exactly the case that
loses data — ours is present, and it is the *other* writer's that is gone.
With A and B from the same `M0`: A writes `MA`, B writes `MB` and clobbers
A; B verifies and correctly sees `MB`; A verifies, sees `M2 !== MA`,
re-applies onto `MB`, writes again. Both survive, and the writer that was
clobbered is the one that repairs it.

Two things this deliberately does not claim:

- It is **not mutual exclusion**. A process that crashes between its write
  and its verifying read cannot notice it was clobbered, and nothing else
  will. That window is strictly narrower than the lock's — which lost
  updates with every participant alive — and needs no primitive Node lacks.
  Stated in the code rather than left to be rediscovered in review.
- Non-convergence **fails loudly**. After five lost rounds `set` throws
  rather than returning, because returning would reintroduce the silent
  loss with extra steps. `delete` stays silent, per the interface contract.

`serialize` is now keyed on the resolved path **process-wide** rather than
per instance. Two `FileSecretStore`s on one file are ordinary — the resolved
store holds one and `absorbFileSecretsIntoKeyring` builds another — and a
per-instance queue does not order them against each other at all. The new
convergence test caught this on its first run: two instances racing `set`
lost an entry. In-process is now correct by construction; the verify covers
what it cannot see, a second Inspector process.

The hand-off's read → copy → delete was the lock's other user. Its delete is
now conditional: the file is re-read immediately before removal and compared
against what was copied, and it declines to delete when they differ. Leaving
the file costs nothing (keychain-wins makes the next run idempotent) while
deleting on a stale read costs a secret.

## Round 9 (two inline, two suppressed)

- **`KeyringSecretStore.getStrict` let raw binding errors escape.** Both
  plaintext migrations keep their source file only when they catch
  `SecretStoreUnavailableError`, so a transient keychain failure 500'd
  `GET /api/servers` instead of abandoning the migration. Wrapped, as `set`
  already does.
- **`FileSecretStore` had no strict read at all**, so `secretStoreGetStrict`
  fell back to the tolerant `get` and mapped every read failure to `null` —
  leaving the exact inversion the seam was introduced to prevent open on the
  store it was introduced for.
- **`readOnDiskEncryption` reported "encrypted" for an envelope it cannot
  open.** Naming the cipher is not the same as being openable: a file with no
  `kdf`/`data`, or a `data` that is not `iv.tag.ciphertext`, produced the
  quiet reassuring footer for a file whose next save was guaranteed to fail.
- **`/api/config` kept a stale descriptor when the resolver returned
  `undefined`**, because the conditional spread left the startup value
  standing. Assigned unconditionally now; `c.json` omits the key.

Full `npm run ci` green.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-22 18:05:26 -04:00
..
import test: raise per-file coverage gate to 90 across all clients (closes #1271) (#1532) 2026-06-27 15:26:03 -04:00
node OAuth: re-key store to (server, issuer) + SEP-837/2350/2352 auth UX (#1694) 2026-07-16 16:52:53 -04:00
remote fix(auth): replace the secrets-file lock with optimistic verify-and-retry, and address review round 9 2026-08-22 18:05:26 -04:00
duplicate-tool-names.test.ts test: cover the duplicateToolNames server option over a live connection 2026-08-10 20:34:14 -04:00
ema-mock-servers.ts test: cover EMA interactive leg 1 end-to-end (#1693) (#1765) 2026-07-24 13:11:51 -04:00
extensions-mimetype.test.ts Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) (#1744) 2026-07-23 10:11:36 -04:00
inspectorClient-advertised-extensions.test.ts Extensions: advertised-extensions toggle in Server Settings (Phase 2 of #1633) (#1743) 2026-07-22 16:23:43 -04:00
inspectorClient-coverage-backfill.test.ts fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
inspectorClient-ema-e2e.test.ts test: cover EMA interactive leg 1 end-to-end (#1693) (#1765) 2026-07-24 13:11:51 -04:00
inspectorClient-excluded-tools.test.ts fix: address Copilot round 2 on the list-salvage path (#1909) 2026-08-15 17:52:36 -04:00
inspectorClient-malformed-list.test.ts fix: stop the exclusion scan writing the rendered list's report (#1909) 2026-08-15 20:38:10 -04:00
inspectorClient-modern-era-oauth.test.ts fix(core): start OAuth on a modern/auto-era connect (#1805) (#1809) 2026-07-27 15:06:01 -04:00
inspectorClient-modern-era.test.ts test(integration): drive the mrtr_empty reproduction through the showcase config 2026-08-16 18:41:36 -04:00
inspectorClient-oauth-direct-mid-session-e2e.test.ts Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725) 2026-07-19 22:36:21 -04:00
inspectorClient-oauth-e2e.test.ts Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725) 2026-07-19 22:36:21 -04:00
inspectorClient-oauth-fetchFn.test.ts SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624) 2026-07-15 10:44:24 -04:00
inspectorClient-oauth-remote-mid-session-e2e.test.ts Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725) 2026-07-19 22:36:21 -04:00
inspectorClient-oauth-remote-storage-e2e.test.ts Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725) 2026-07-19 22:36:21 -04:00
inspectorClient-oauth.test.ts Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725) 2026-07-19 22:36:21 -04:00
inspectorClient-response-rejected.test.ts fix: surface list-fetch failures instead of swallowing them 2026-08-08 16:48:48 -04:00
inspectorClient-subscriptions-era.test.ts fix: only announce the connection if it is still live after the stream open 2026-08-11 00:05:56 -04:00
inspectorClient-tasks-era.test.ts Tasks: io.modelcontextprotocol/tasks extension on modern servers (era fork) (#1731) 2026-07-21 19:17:04 -04:00
inspectorClient-xmcpheader-mirroring.test.ts fix(core): mirror SEP-2243 x-mcp-header args to Mcp-Param-* on tools/call (#1847) 2026-07-28 19:19:48 -04:00
inspectorClient.test.ts Merge pull request #1958 from modelcontextprotocol/v2/fix/unhandled-rejections-progress-tests 2026-08-11 11:17:55 -04:00
listSalvage-era.test.ts fix: return the era-parsed tool, and hide aggregate reports in paginated mode (#1909) 2026-08-15 20:08:03 -04:00
mcp-app-demo.test.ts SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624) 2026-07-15 10:44:24 -04:00
nullable-fields.test.ts fix: strip the enum null sentinel, and make the preset test cover its wiring 2026-08-15 18:28:38 -04:00
rfc6570-templates.test.ts test: pin the InspectorClient wiring, and correct a false claim about the SDK 2026-08-17 00:59:30 -04:00
test-server-protected-resource.test.ts All of the mid-session and step-up auth work 2026-07-02 16:35:31 -07:00