Skip to content

chore: v2.14.0 rollup — integration proof for eight PRs (do not merge) - #2858

Closed
cliffhall wants to merge 31 commits into
v2/mainfrom
v2/chore/v2.14.0-rollup
Closed

cliffhall wants to merge 31 commits into
v2/mainfrom
v2/chore/v2.14.0-rollup

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Refs #2847, #2843, #2834, #2835, #2836, #2837, #2826, #2810, #2825, #2824, #2823, #2827, #2822, #2814, #2813, #2811

Integration proof only — this PR will be closed, not merged. It merges the eight v2.14.0 PRs together on top of v2/main to show they apply without conflicts and work together. Each one is then merged individually.

PR Issues Area
#2852 #2822, #2814, #2813, #2811 Node engines floor, vitest 4.1.12, happy-dom 21.0.4, ext-apps 2.0.4, ip-address
#2853 #2824 Reap the backend session and its stdio child when a web tab closes or reloads
#2848 #2825 Docker HEALTHCHECK over HTTPS, probing /healthz
#2849 #2823 mcpdo connections/list and servers/show report an over-limit socket path
#2850 #2826, #2810 TUI list rows truncate, the selected row stays visible; Windows-safe test paths
#2856 #2827 Nine small web, TUI and catalog defects
#2854 #2847, #2843 The OAuth callback listener checks state; its non-callback pages no longer say sign-in finished
#2855 #2834, #2835, #2836, #2837 --load: interrupt summary, circuit breaker, success-only latency, rate shortfall

Smoke results on this branch are posted as a comment.

🤖 Generated with Claude Code

cliffhall and others added 29 commits October 28, 2026 23:37
…socket path (#2823)

`connections/list` and `servers/show` with no name read a
`daemon_unreachable` error as "nothing is connected". When the daemon
socket path is over the platform's sun_path limit the daemon can never
bind, so that read was wrong: the list printed `Connections (0)` and
exited 0, and MRU inference said there was no most-recently-used
connection, while `connect` refused with the real cause.

Both now run the same `assertSocketPathWithinLimit()` check that
`ensureDaemon()` runs before treating an unreachable daemon as empty,
so they fail with connect's error and exit code. Neither starts the
daemon or creates its directory, and an absent daemon on a valid path
still reports an empty list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
)

The image's HEALTHCHECK always built an http:// URL, so with both
INSPECTOR_TLS_KEY_PATH and INSPECTOR_TLS_CERT_PATH set the probe failed
every time and a correctly running TLS container was reported unhealthy
forever. The probe now follows the server's TLS rule (both paths set, a
blank value counting as unset) and uses https without verifying the
certificate, since it only reaches the container's own listener where a
self-signed or private-CA certificate is the normal case. It uses
node:http/https rather than fetch because fetch has no per-request way
to skip verification without undici, which the copied script cannot
resolve.

It also probes the unauthenticated /healthz route (#2438) instead of /,
which read and token-injected index.html on every probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
saveResultToFile resolves its target with node:path resolve(cwd, path),
so on Windows the mocked writeFile receives D:\d\a.json while the test
asserted POSIX literals. Build the expected paths the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…2826)

Tools, Resources and Prompts rows now render with wrap="truncate-end",
so a long name no longer wraps into extra rows that the item-counting
scroll window cannot see. The window is also re-clamped every render, so
a terminal resize cannot leave the selected row outside it. The Tools
detail header wraps its [Enter to Test] hint onto its own line when it
does not fit beside the full tool name, instead of overlapping it, and a
row's schema-portability marker sits outside the truncated text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…eases (#2822)

engines.node goes from >=22.19.0 to ^22.23.4 || ^24.22.1 || >=26.12.1, the
patched release on each supported line, so npm warns (EBADENGINE) on an
unpatched runtime. A per-line range is what keeps an unpatched 24.x or 26.x
from satisfying it.

Every doc that states the floor is updated. The code comments that reasoned
from the 22.19 floor were re-read rather than replaced:

- NODE_USE_ENV_PROXY was "unsupported at our floor"; it was added in 22.21.0,
  so it exists at 22.23.4. It is still no substitute: opt-in at process start
  and Stability 1.1. The comments now say that.
- The short-GCM-tag note said only Node 22 accepts a truncated tag. The floor
  now spans 22 and 24, and 24 only runtime-deprecates it (DEP0182); Node 26
  is where it is end-of-life.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Lockfile-only: ^2.0.0 already admits 2.0.4. Upstream calls it a patch of
example and dependency changes with no library API change; its exports map
and dependency set are identical to 2.0.3, so the bundler externals need no
change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
The four-edit vitest bump: the exact pins on vitest and @vitest/coverage-v8
(root) and @vitest/browser-playwright (web), plus web's vitest overrides
entry. Clears GHSA-qxg6-89mg-rqv8 and GHSA-xp26-3454-mjgp, and the
@storybook/addon-vitest finding that was only inherited from vitest.

ip-address (transitive, via server-legacy -> express-rate-limit ^10.2.0) is
an in-range npm update, so no overrides entry. fast-uri was already fixed at
3.1.9 by #2820.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
happy-dom 21 turns iframe page loading off by default. The web unit project
already stopped child-frame navigation, but with loading off every sandbox
iframe now logs a NotSupportedError and gets no contentWindow. Turning
enableIframePageLoading back on restores the previous behavior; the
navigation flag still stops the frame before it fetches anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
, #2843)

The loopback listener used by the CLI, TUI and mcpdo accepted the first
request to its callback path whatever its `state`, so any local process or
web page able to hit the fixed port could end an in-progress sign-in with a
forged `?error=` and choose the message the runner reported.

- `OAuthCallbackServer.start()` takes `expectedState` (a promise, since the
  listener is bound before the authorize URL exists). A callback whose
  `state` does not match -- success or error response -- gets a short 400
  and the listener keeps waiting; one that arrives before the state is known
  is held until it is, or answered 503 if the listener stops first.
- `runRunnerInteractiveOAuth` hands over the state from the authorize URL
  (the step-up URL before navigating, or the one `authenticate()` returns).
- Authorization-server error text is labeled as such ("The authorization
  server said: ...") in both the page and the runner's error.
- #2843: the non-GET, unparseable-URL and wrong-path branches (and the new
  mismatch, 409 and 503 answers) no longer send the "OAuth complete" page.
- Responses send `Connection: close`, so `stop()` is not held open by an
  idle kept-alive socket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Closing or reloading a connected web tab dropped /api/mcp/events and sent
nothing else, so the backend RemoteSession and its spawned stdio child lived
until the Inspector exited, queueing every later server message without
bound.

- Backend: arm a detach grace timer (default 60s, `sessionDetachGraceMs`)
  whenever a session has no events consumer — after connect and when its
  stream drops. A reattach (the OAuth redirect's attachToSession) cancels
  it; otherwise the transport is closed and the session dropped. A stream
  that was already replaced no longer clears the live consumer.
- Cap RemoteSession's event queue at 1000, dropping the oldest and telling
  the next consumer how many went.
- Web: on pagehide (not bfcache, not an OAuth redirect that will reattach to
  this session) send a keepalive fetch to /api/mcp/disconnect. Not
  sendBeacon: it cannot carry the x-mcp-remote-auth header every /api route
  requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
… split success latency, report dropped rate slots (#2834, #2835, #2836, #2837)

- CLI: SIGINT/SIGTERM abort the run instead of the process; the partial
  summary prints with stoppedEarly.reason "interrupted", then exit 130.
  A second signal exits at once. Load flag refusals use load_flag_conflict.
- Engine: circuit breaker after 50 consecutive thrown calls (or 1s of only
  thrown calls), reported as stoppedEarly consecutive_errors; mcpdo names a
  dropped connection connection_lost.
- latencyMs is success-only, errorLatencyMs times failures, okThroughput
  added, --load-max-p99 judges successes; isError text is tallied in
  toolErrorMessages.
- --load-rate runs report plannedStarts/droppedSlots/achievedRate and warn
  on stderr under 90% of target; a duration-bound run is measured to the
  end of its window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
)

A reload cancels the page's events stream before pagehide fires, which marks
the transport closed while the backend session is still alive. Key the
release on the backend session id alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
1. Self-host the Fredoka and Roboto Mono woff2 files (clients/web/src/theme/fonts)
   instead of loading them from fonts.googleapis.com at runtime, in the app and
   in Storybook.
2. The sandbox listener answers HEAD, ignores a query string on /sandbox, and
   applies the main server's Host allow-list (plus the host of
   MCP_SANDBOX_FULL_ADDRESS).
3. The prod SPA fallback excludes only /api and /api/..., so /apixyz gets the SPA.
4. One ad-hoc server naming rule (adHocServerName in core/mcp/serverList.ts):
   URL host or command basename, used by web and by the shared CLI/TUI/mcpdo
   resolution, which used to call it "default".
5. The default seed scopes filesystem-server-default to os.tmpdir(), passed in
   by the Node backend (defaultSeedConfig(tempDir)) since serverList.ts is
   browser-consumed.
6. Every web screen has a visually hidden h1; connected-screen panel headings
   move to h2/h3 (visual sizes unchanged) so axe heading-order stays clean.
7. The TUI lists tools alphabetically, as the web sidebar does since #2782,
   via a shared core/mcp/toolOrder.ts.
8. TUI test modals no longer print their title twice; the roots-editor comment
   in App.tsx reflects #2432.
9. A custom MCP_INSPECTOR_API_TOKEN shorter than 32 characters prints a startup
   warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
… text (#2847)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…tener

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…l in tests (#2847)

The runner told our own exchange failures from redirect errors by the
`error` string, which a redirect can set to anything (callback_error
included). The listener now reports `source: "redirect" | "callback"`
and the runner labels every redirect error.

The integration tests waited 20ms for a request to reach the listener;
they now wait on the http server's own "request" event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…led font licenses (#2827)

- TUI: memoize the sorted tools on the store snapshot so an unrelated update
  (a stderr line) no longer resets the Tools selection; regression test.
- Web: the Apps 'Messages from app' heading is an h2, valid with the sidebar
  hidden while maximized; test.
- Ship the full upstream OFL 1.1 texts for Fredoka and Roboto Mono in
  public/fonts (copied to dist/fonts, so they are in the tarball); test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…p, breaker only while starts remain, uncapped planned slots, exact shortfall check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…request (#2847)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…r the stop, whichever is first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…e figures count only calls made

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Oct 29, 2026
@cliffhall

Copy link
Copy Markdown
Member Author

Rollup smoke, round 1 (a4da719d, fresh npm install + npm run build)

All eight branches merged with no conflicts. Git combined the changes in the shared files on its own: core/mcp/remote/node/server.ts (#2852, #2853, #2856), clients/cli/README.md (#2852, #2854, #2855) and clients/mcpdo/src/connection/mcp.ts (#2849, #2855).

PR Check Result
#2848 TLS web launch: the probe exits 0. Plain HTTP launch: the probe exits 0. HTTP probe against the TLS server: exits 1 ✅
#2849 Over-long MCP_INSPECTOR_DAEMON_DIR: connections/list and servers/show both exit 1 with the socket-path error, and the dir is not created. Short dir: empty list, exit 0 ✅
#2850 Real pty at 60×18: rows stay one line and truncate, the selected last tool stays visible, [Enter to Test] drops below the name ✅
#2852 vitest 4.1.12, happy-dom 21.0.4, ext-apps 2.0.4, ip-address 10.7.5, fast-uri 3.1.9. engines is ^22.23.4 || ^24.22.1 || >=26.12.1. npm audit reports 0 in root and web ✅
#2853 Playwright, connect then close tab: stdio children go 1 → 0 within 1s. Connect then reload: 1 → 0 within 1s (re-run after #2856 merged) ✅
#2854 Wrong path gets 404 and POST gets 405, both with a "not the OAuth callback" page. Forged ?error= with no state, and wrong state, both get 400 "ignored" while the CLI keeps waiting. The genuine approval completes and the CLI prints tools/list ✅
#2856 Weak-token warning ✅. Sandbox HEAD 200, ?x=1 200, rebind Host 403 ✅. /apixyz gets the SPA, /api/nope gets 401 ✅. No googleapis refs ✅. Seed uses os.tmpdir() ✅. The TUI ad-hoc server is named node, tools are A→Z, and "Test Tool:" appears once ✅ ✅
#2855 #2835 breaker trips about 1.9s after the server dies, with stoppedEarly.consecutive_errors ✅. #2836 toolErrorMessages counts rate limited ×50, latencyMs is null, --load-max-p99 exits 10 ✅. #2837: 500 planned, 407 dropped, 18.6/s, warning printed; a 1/s run reports 1.0/s over 5000 ms ✅. load_flag_conflict ✅. #2834 ❌: a real SIGINT/SIGTERM exits 130 with no partial summary. when-exit re-raises the signal and the handler takes it for a second Ctrl+C ❌, sent back to #2855

cliffhall and others added 2 commits October 29, 2026 01:56
…ts as the second Ctrl+C (#2834)

when-exit (via atomically) re-raises SIGINT/SIGTERM with process.kill after
its cleanups, so the CLI's listener saw two signals for one press and
exited 130 before the partial summary was written. A repeat of the same
signal within 500ms of the first is now treated as that re-raise. Adds a
spawn test that delivers real signals to the built CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
This was referenced Oct 29, 2026
@cliffhall

Copy link
Copy Markdown
Member Author

Closing unmerged, as planned. This rollup did its job: all eight v2.14.0 PRs (#2852, #2853, #2854, #2855, #2856, #2848, #2849, #2850) merged together on v2/main with no conflicts. Each contribution passed a hand smoke test on the combined build. One defect turned up here: the #2834 partial summary was lost to a dependency's signal re-raise. It was fixed on #2855 (f5c2de58) and re-verified. CI on the combined branch passed (build, coverage, DCO, CodeQL). The eight PRs were then merged individually, and the resulting v2/main tree is byte-identical to this branch's tested tree (git diff is empty).

@cliffhall cliffhall closed this Oct 29, 2026
@cliffhall
cliffhall deleted the v2/chore/v2.14.0-rollup branch October 29, 2026 06:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant