Skip to content

fix(react): StrictMode cleanup + relax late-handler guard for re-reg#631

Merged
ochafik merged 2 commits intomainfrom
fix/react-strictmode-and-handler-rereg
Apr 21, 2026
Merged

fix(react): StrictMode cleanup + relax late-handler guard for re-reg#631
ochafik merged 2 commits intomainfrom
fix/react-strictmode-and-handler-rereg

Conversation

@ochafik
Copy link
Copy Markdown
Contributor

@ochafik ochafik commented Apr 21, 2026

Summary

Three React-friendliness fixes following #629/#623:

  • useApp cleanup closes the App. Previously the effect cleanup only set mounted = false, so under React StrictMode's dev double-invoke the abandoned first App's PostMessageTransport kept its window message listener and received every host postMessage alongside the second instance (JSONRPC ID collisions, double-fired notifications). Now appInstance?.close() runs in cleanup and after a post-unmount connect resolution. Protocol.close() is idempotent and rejects pending requests with ConnectionClosed, so the abandoned instance's ui/initialize typically isn't even sent. May help with ontoolinput is not consistently called #476 reports from dev environments.
  • Late-handler guard only flags the first registration per event. feat(app): warn when one-shot handlers are registered after connect() #629 warned (or threw under strict) on every one-shot handler registration after connect(), which false-positives on the React useEffect([app, dep]) re-registration pattern even when a handler was correctly registered via onAppCreated first. Now a _everHadListener set tracks per-event whether any handler ever existed; re-registration is silent. Only the genuinely-risky "first handler for this event is post-connect" case warns/throws.
  • AppBridge warns on a second ui/initialize. Diagnostic only — behavior unchanged (still responds, latest appInfo/appCapabilities win). Surfaces View double-mounting to host implementers.

ONE_SHOT_EVENTS and _everHadListener remain private — not part of the public API.

Test plan

  • npm test — 332/332 pass (4 new in late handler registration, 1 close() stops further notification delivery, 1 AppBridge warns on second ui/initialize)
  • npx tsc --noEmit — 0 errors
  • Re-registration after pre-connect handler: silent, including under strict: true
  • First late registration: warns once, subsequent silent; per-event independent
  • app.close() stops toolresult delivery
  • AppBridge double-init warning fires
  • e2e: basic-server-react renders correctly (covers useApp in a real browser)

… re-registration

useApp's effect cleanup now closes the App (and its transport) instead
of only flipping a `mounted` flag. Under React StrictMode dev
double-invoke, the abandoned first instance's PostMessageTransport
otherwise keeps a window `message` listener and receives every host
postMessage alongside the second instance.

The late-handler guard from #629 now tracks `_everHadListener` per
event and only flags the *first* registration of a one-shot event
post-handshake. Re-registration (React useEffect cleanup + re-add on
dep change) of an event that had a pre-connect handler is silent,
including under `strict: true`.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Preview

Preview deployments for this PR have been cleaned up.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@631

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@631

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@631

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@631

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@631

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@631

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@631

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@631

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@631

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@631

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@631

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@631

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@631

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@631

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@631

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@631

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@631

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@631

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@631

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@631

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@631

commit: 35f4dc8

If a View double-mounts (e.g. React StrictMode in dev) without closing
the previous App, AppBridge now logs a console.warn when it receives a
second ui/initialize. Behavior is unchanged — it still responds and the
latest appInfo/appCapabilities replace the previous values — this is
purely a diagnostic for host implementers.
@ochafik ochafik merged commit cc02e17 into main Apr 21, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant