Skip to content

feat(app,app-bridge): guard requests sent before handshake completes - #623

Merged
ochafik merged 1 commit into
mainfrom
ochafik/pre-handshake-guard
Apr 21, 2026
Merged

feat(app,app-bridge): guard requests sent before handshake completes#623
ochafik merged 1 commit into
mainfrom
ochafik/pre-handshake-guard

Conversation

@ochafik

Copy link
Copy Markdown
Contributor

Re-land of #620 against main#620 was squash-merged into ochafik/build-hook-fixes, but #621 had already squash-merged to main from an earlier tip, so #620's commit never reached main.

See #620 for full description and discussion. #622 stacks on this.

…620)
* feat(app,app-bridge): guard requests sent before handshake completes
Calling host-bound methods before the ui/initialize →
ui/notifications/initialized handshake completes can race the handshake
on strict hosts and leave the iframe permanently hidden.
App (View side):
- All 8 host-bound methods (callServerTool, readServerResource,
listServerResources, sendMessage, updateModelContext, openLink,
downloadFile, requestDisplayMode) now check that connect() has sent
the initialized notification.
- New AppOptions.strict (default false): console.error when false,
throw when true. AppOptions is now exported.
- useApp() forwards strict to the App constructor.
- Flag resets on reconnect.
AppBridge (Host side):
- replaceRequestHandler is overridden to wrap every host-bound handler
with a console.warn if the request arrives before
ui/notifications/initialized. Never throws. ui/initialize and ping
use setRequestHandler directly and are exempt; notifications are
unaffected.
- Catches Views that hand-roll postMessage without the SDK.
- Flag resets on reconnect.
Refs anthropics/claude-ai-mcp#61, anthropics/claude-ai-mcp#149.
* fix(docs): drop AppOptions from intentionallyNotExported now that it is exported
@github-actions

github-actionsBot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Preview

Preview deployments for this PR have been cleaned up.

@ochafik
ochafik merged commit 94c9421 into mainApr 21, 2026
21 checks passed
ochafik added a commit that referenced this pull request Apr 21, 2026
Conflict resolution:
- src/app.ts: kept both _registeredTools (#72) and
_initializedSent/_assertInitialized (#623) private fields.
- typedoc.config.mjs: dropped AppOptions (now exported per #623),
kept RequestHandlerExtra (#72) → ["MethodSchema", "RequestHandlerExtra"].
- .husky/pre-commit: took main's --diff-filter=d (both sides had
equivalent fixes for staged-deletion re-add failure).
Guard application to #72's new App methods:
- sendToolListChanged: added _assertInitialized guard.
- registerTool's notify() helper: gated on _initializedSent instead of
transport so enable/disable/update during the handshake window
doesn't fire a premature list_changed notification.
- registerTool itself is intentionally NOT guarded — it's a pre-connect
setup method (analogous to setting ontoolresult).
AppBridge: #72 adds no setRequestHandler/replaceRequestHandler calls;
the existing override auto-covers any new replaceRequestHandler users.
ochafik added a commit that referenced this pull request Apr 21, 2026
…#629)
Mirrors the outbound-side guard from #623: registering a toolinput /
toolinputpartial / toolresult / toolcancelled handler after the
ui/initialize handshake has completed now logs a console.warn (or
throws under { strict: true }), since the host may have already fired
the notification by then. hostcontextchanged is exempt — late
listeners for repeating events are legitimate.
Covers both the on* setters and addEventListener.
Sign up for freeto 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

@ochafik