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

* 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
This commit is contained in:
Olivier Chafik 2026-04-21 11:45:14 +01:00 • committed by GitHub
commit 94c94216d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,20 +1,25 @@
import { useEffect, useState } from "react";
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
import { Client } from "@modelcontextprotocol/sdk/client";
import { App, McpUiAppCapabilities, PostMessageTransport } from "../app";
import {
App,
AppOptions,
McpUiAppCapabilities,
PostMessageTransport,
} from "../app";
export * from "../app";
/**
* Options for configuring the {@link useApp `useApp`} hook.
*
* Note: This interface does NOT expose {@link App `App`} options like `autoResize`.
* The hook creates the `App` with default options (`autoResize: true`). If you
* need custom `App` options, create the `App` manually instead of using this hook.
* The `strict` option is forwarded to the underlying {@link App `App`}
* instance. For other {@link AppOptions `AppOptions`}, create the `App`
* manually instead of using this hook.
*
* @see {@link useApp `useApp`} for the hook that uses these options
* @see {@link useAutoResize `useAutoResize`} for manual auto-resize control with custom `App` options
*/
export interface UseAppOptions {
export interface UseAppOptions extends Pick<AppOptions, "strict"> {
/** App identification (name and version) */
appInfo: Implementation;
/**
@ -121,6 +126,7 @@ export function useApp({
appInfo,
capabilities,
onAppCreated,
strict,
}: UseAppOptions): AppState {
const [app, setApp] = useState<App | null>(null);
const [isConnected, setIsConnected] = useState(false);
@ -135,7 +141,10 @@ export function useApp({
window.parent,
window.parent,
);
const app = new App(appInfo, capabilities);
const app = new App(appInfo, capabilities, {
autoResize: true,
strict,
});
// Register handlers BEFORE connecting
onAppCreated?.(app);