Skip to content

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

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

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

Conversation

@ochafik
Copy link
Copy Markdown
Contributor

@ochafik ochafik commented Apr 21, 2026

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
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Preview

Preview deployments for this PR have been cleaned up.

@ochafik ochafik merged commit 94c9421 into main Apr 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 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