@modelcontextprotocol/ext-apps - v0.0.1
    Preparing search index...

    Class App

    Main class for MCP Apps to communicate with their host.

    The App class provides a framework-agnostic way to build interactive MCP Apps that run inside host applications. It extends the MCP SDK's Protocol class and handles the connection lifecycle, initialization handshake, and bidirectional communication with the host.

    Guest UIs (Apps) act as MCP clients connecting to the host via PostMessageTransport. The host proxies requests to the actual MCP server and forwards responses back to the App.

    1. Create: Instantiate App with info and capabilities
    2. Connect: Call connect() to establish transport and perform handshake
    3. Interactive: Send requests, receive notifications, call tools
    4. Cleanup: Host sends teardown request before unmounting

    As a subclass of Protocol, App inherits key methods for handling communication:

    • setRequestHandler() - Register handlers for requests from host
    • setNotificationHandler() - Register handlers for notifications from host

    Protocol from @modelcontextprotocol/sdk for all inherited methods

    For common notifications, the App class provides convenient setter properties that simplify handler registration:

    • ontoolinput - Complete tool arguments from host
    • ontoolinputpartial - Streaming partial tool arguments
    • ontoolresult - Tool execution results
    • onhostcontextchanged - Host context changes (theme, viewport, etc.)

    These setters are convenience wrappers around setNotificationHandler(). Both patterns work; use whichever fits your coding style better.

    import {
    App,
    PostMessageTransport,
    McpUiToolInputNotificationSchema
    } from '@modelcontextprotocol/ext-apps';

    const app = new App(
    { name: "WeatherApp", version: "1.0.0" },
    {} // capabilities
    );

    // Register notification handler using setter (simpler)
    app.ontoolinput = (params) => {
    console.log("Tool arguments:", params.arguments);
    };

    // OR using inherited setNotificationHandler (more explicit)
    app.setNotificationHandler(
    McpUiToolInputNotificationSchema,
    (notification) => {
    console.log("Tool arguments:", notification.params.arguments);
    }
    );

    await app.connect(new PostMessageTransport(window.parent));
    await app.sendMessage({
    role: "user",
    content: [{ type: "text", text: "Weather updated!" }]
    });

    Hierarchy

    • Protocol<Request, Notification, Result>
      • App
    Index

    Constructors

    • Create a new MCP App instance.

      Parameters

      • _appInfo: {
            icons?: { mimeType?: string; sizes?: string[]; src: string }[];
            name: string;
            title?: string;
            version: string;
            websiteUrl?: string;
        }

        App identification (name and version)

      • _capabilities: McpUiAppCapabilities = {}

        Features and capabilities this app provides

      • options: AppOptions = ...

        Configuration options including autoResize behavior

      Returns App

      const app = new App(
      { name: "MyApp", version: "1.0.0" },
      { tools: { listChanged: true } }, // capabilities
      { autoResize: true } // options
      );

    Properties

    fallbackNotificationHandler?: (
        notification: {
            method: string;
            params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown };
        },
    ) => Promise<void>

    A handler to invoke for any notification types that do not have their own handler installed.

    fallbackRequestHandler?: (
        request: {
            id: string | number;
            jsonrpc: "2.0";
            method: string;
            params?: {
                _meta?: { progressToken?: string | number; [key: string]: unknown };
                [key: string]: unknown;
            };
        },
        extra: RequestHandlerExtra<
            {
                method: string;
                params?: {
                    _meta?: { progressToken?: string
                    | number; [key: string]: unknown };
                    [key: string]: unknown;
                };
            },
            {
                method: string;
                params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown };
            },
        >,
    ) => Promise<{ _meta?: { [key: string]: unknown }; [key: string]: unknown }>

    A handler to invoke for any request types that do not have their own handler installed.

    onclose?: () => void

    Callback for when the connection is closed for any reason.

    This is invoked when close() is called as well.

    onerror?: (error: Error) => void

    Callback for when an error occurs.

    Note that errors are not necessarily fatal; they are used for reporting any kind of exceptional condition out of band.

    Accessors

    • set oncalltool(
          callback: (
              params: {
                  _meta?: { progressToken?: string | number; [key: string]: unknown };
                  arguments?: { [key: string]: unknown };
                  name: string;
                  [key: string]: unknown;
              },
              extra: RequestHandlerExtra,
          ) => Promise<
              {
                  _meta?: { [key: string]: unknown };
                  content: (
                      | { _meta?: { [key: string]: unknown }; text: string; type: "text" }
                      | {
                          _meta?: { [key: string]: unknown };
                          data: string;
                          mimeType: string;
                          type: "image";
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          data: string;
                          mimeType: string;
                          type: "audio";
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          description?: string;
                          icons?: { mimeType?: string; sizes?: (...)[]; src: string }[];
                          mimeType?: string;
                          name: string;
                          title?: string;
                          type: "resource_link";
                          uri: string;
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          resource:
                              | {
                                  _meta?: { [key: string]: unknown };
                                  mimeType?: string;
                                  text: string;
                                  uri: string;
                              }
                              | {
                                  _meta?: { [key: string]: unknown };
                                  blob: string;
                                  mimeType?: string;
                                  uri: string;
                              };
                          type: "resource";
                      }
                  )[];
                  isError?: boolean;
                  structuredContent?: { [key: string]: unknown };
                  [key: string]: unknown;
              },
          >,
      ): void

      Convenience handler for tool call requests from the host.

      Set this property to register a handler that will be called when the host requests this app to execute a tool. This enables apps to provide their own tools that can be called by the host or LLM.

      The app must declare tool capabilities in the constructor to use this handler.

      This setter is a convenience wrapper around setRequestHandler() that automatically handles the request schema and extracts the params for you.

      Register handlers before calling connect to avoid missing requests.

      Parameters

      • callback: (
            params: {
                _meta?: { progressToken?: string | number; [key: string]: unknown };
                arguments?: { [key: string]: unknown };
                name: string;
                [key: string]: unknown;
            },
            extra: RequestHandlerExtra,
        ) => Promise<
            {
                _meta?: { [key: string]: unknown };
                content: (
                    | { _meta?: { [key: string]: unknown }; text: string; type: "text" }
                    | {
                        _meta?: { [key: string]: unknown };
                        data: string;
                        mimeType: string;
                        type: "image";
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        data: string;
                        mimeType: string;
                        type: "audio";
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        description?: string;
                        icons?: { mimeType?: string; sizes?: (...)[]; src: string }[];
                        mimeType?: string;
                        name: string;
                        title?: string;
                        type: "resource_link";
                        uri: string;
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        resource:
                            | {
                                _meta?: { [key: string]: unknown };
                                mimeType?: string;
                                text: string;
                                uri: string;
                            }
                            | {
                                _meta?: { [key: string]: unknown };
                                blob: string;
                                mimeType?: string;
                                uri: string;
                            };
                        type: "resource";
                    }
                )[];
                isError?: boolean;
                structuredContent?: { [key: string]: unknown };
                [key: string]: unknown;
            },
        >

        Async function that executes the tool and returns the result. The callback will only be invoked if the app declared tool capabilities in the constructor.

      Returns void

      app.oncalltool = async (params, extra) => {
      if (params.name === "greet") {
      const name = params.arguments?.name ?? "World";
      return { content: [{ type: "text", text: `Hello, ${name}!` }] };
      }
      throw new Error(`Unknown tool: ${params.name}`);
      };

      setRequestHandler for the underlying method

    • set onhostcontextchanged(callback: (params: McpUiHostContext) => void): void

      Convenience handler for host context changes (theme, viewport, locale, etc.).

      Set this property to register a handler that will be called when the host's context changes, such as theme switching (light/dark), viewport size changes, locale changes, or other environmental updates. Apps should respond by updating their UI accordingly.

      This setter is a convenience wrapper around setNotificationHandler() that automatically handles the notification schema and extracts the params for you.

      Register handlers before calling connect to avoid missing notifications.

      Parameters

      • callback: (params: McpUiHostContext) => void

        Function called with the updated host context

      Returns void

      app.onhostcontextchanged = (params) => {
      if (params.theme === "dark") {
      document.body.classList.add("dark-theme");
      } else {
      document.body.classList.remove("dark-theme");
      }
      };
    • set onlisttools(
          callback: (
              params:
                  | {
                      _meta?: { progressToken?: string
                      | number; [key: string]: unknown };
                      cursor?: string;
                      [key: string]: unknown;
                  }
                  | undefined,
              extra: RequestHandlerExtra,
          ) => Promise<{ tools: string[] }>,
      ): void

      Convenience handler for listing available tools.

      Set this property to register a handler that will be called when the host requests a list of tools this app provides. This enables dynamic tool discovery by the host or LLM.

      The app must declare tool capabilities in the constructor to use this handler.

      This setter is a convenience wrapper around setRequestHandler() that automatically handles the request schema and extracts the params for you.

      Register handlers before calling connect to avoid missing requests.

      Parameters

      • callback: (
            params:
                | {
                    _meta?: { progressToken?: string
                    | number; [key: string]: unknown };
                    cursor?: string;
                    [key: string]: unknown;
                }
                | undefined,
            extra: RequestHandlerExtra,
        ) => Promise<{ tools: string[] }>

        Async function that returns the list of available tools. The callback will only be invoked if the app declared tool capabilities in the constructor.

      Returns void

      app.onlisttools = async (params, extra) => {
      return {
      tools: ["calculate", "convert", "format"]
      };
      };
    • set ontoolinput(
          callback: (params: { arguments?: Record<string, unknown> }) => void,
      ): void

      Convenience handler for receiving complete tool input from the host.

      Set this property to register a handler that will be called when the host sends a tool's complete arguments. This is sent after a tool call begins and before the tool result is available.

      This setter is a convenience wrapper around setNotificationHandler() that automatically handles the notification schema and extracts the params for you.

      Register handlers before calling connect to avoid missing notifications.

      Parameters

      • callback: (params: { arguments?: Record<string, unknown> }) => void

        Function called with the tool input params

      Returns void

      // Register before connecting to ensure no notifications are missed
      app.ontoolinput = (params) => {
      console.log("Tool:", params.arguments);
      // Update your UI with the tool arguments
      };
      await app.connect(transport);
      app.setNotificationHandler(
      McpUiToolInputNotificationSchema,
      (notification) => {
      console.log("Tool:", notification.params.arguments);
      }
      );
    • set ontoolinputpartial(
          callback: (params: { arguments?: Record<string, unknown> }) => void,
      ): void

      Convenience handler for receiving streaming partial tool input from the host.

      Set this property to register a handler that will be called as the host streams partial tool arguments during tool call initialization. This enables progressive rendering of tool arguments before they're complete.

      This setter is a convenience wrapper around setNotificationHandler() that automatically handles the notification schema and extracts the params for you.

      Register handlers before calling connect to avoid missing notifications.

      Parameters

      • callback: (params: { arguments?: Record<string, unknown> }) => void

        Function called with each partial tool input update

      Returns void

      app.ontoolinputpartial = (params) => {
      console.log("Partial args:", params.arguments);
      // Update your UI progressively as arguments stream in
      };
    • set ontoolresult(
          callback: (
              params: {
                  _meta?: { [key: string]: unknown };
                  content: (
                      | { _meta?: { [key: string]: unknown }; text: string; type: "text" }
                      | {
                          _meta?: { [key: string]: unknown };
                          data: string;
                          mimeType: string;
                          type: "image";
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          data: string;
                          mimeType: string;
                          type: "audio";
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          description?: string;
                          icons?: { mimeType?: string; sizes?: string[]; src: string }[];
                          mimeType?: string;
                          name: string;
                          title?: string;
                          type: "resource_link";
                          uri: string;
                      }
                      | {
                          _meta?: { [key: string]: unknown };
                          resource:
                              | {
                                  _meta?: { [key: string]: unknown };
                                  mimeType?: string;
                                  text: string;
                                  uri: string;
                              }
                              | {
                                  _meta?: { [key: string]: unknown };
                                  blob: string;
                                  mimeType?: string;
                                  uri: string;
                              };
                          type: "resource";
                      }
                  )[];
                  isError?: boolean;
                  structuredContent?: { [key: string]: unknown };
                  [key: string]: unknown;
              },
          ) => void,
      ): void

      Convenience handler for receiving tool execution results from the host.

      Set this property to register a handler that will be called when the host sends the result of a tool execution. This is sent after the tool completes on the MCP server, allowing your app to display the results or update its state.

      This setter is a convenience wrapper around setNotificationHandler() that automatically handles the notification schema and extracts the params for you.

      Register handlers before calling connect to avoid missing notifications.

      Parameters

      • callback: (
            params: {
                _meta?: { [key: string]: unknown };
                content: (
                    | { _meta?: { [key: string]: unknown }; text: string; type: "text" }
                    | {
                        _meta?: { [key: string]: unknown };
                        data: string;
                        mimeType: string;
                        type: "image";
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        data: string;
                        mimeType: string;
                        type: "audio";
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        description?: string;
                        icons?: { mimeType?: string; sizes?: string[]; src: string }[];
                        mimeType?: string;
                        name: string;
                        title?: string;
                        type: "resource_link";
                        uri: string;
                    }
                    | {
                        _meta?: { [key: string]: unknown };
                        resource:
                            | {
                                _meta?: { [key: string]: unknown };
                                mimeType?: string;
                                text: string;
                                uri: string;
                            }
                            | {
                                _meta?: { [key: string]: unknown };
                                blob: string;
                                mimeType?: string;
                                uri: string;
                            };
                        type: "resource";
                    }
                )[];
                isError?: boolean;
                structuredContent?: { [key: string]: unknown };
                [key: string]: unknown;
            },
        ) => void

        Function called with the tool result

      Returns void

      app.ontoolresult = (params) => {
      if (params.content) {
      console.log("Tool output:", params.content);
      }
      if (params.isError) {
      console.error("Tool execution failed");
      }
      };
    • get transport(): Transport | undefined

      Returns Transport | undefined

    Methods

    • Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.

      Parameters

      • method: string

      Returns void

    • Internal

      Verify that the host supports the capability required for the given request method.

      Parameters

      • method: string

      Returns void

    • Internal

      Verify that the app supports the capability required for the given notification method.

      Parameters

      • method: string

      Returns void

    • Internal

      Verify that the app declared the capability required for the given request method.

      Parameters

      • method: string

      Returns void

    • Call a tool on the originating MCP server (proxied through the host).

      Apps can call tools to fetch fresh data or trigger server-side actions. The host proxies the request to the actual MCP server and returns the result.

      Parameters

      • params: {
            _meta?: { progressToken?: string | number; [key: string]: unknown };
            arguments?: { [key: string]: unknown };
            name: string;
            [key: string]: unknown;
        }

        Tool name and arguments

      • Optionaloptions: RequestOptions

        Request options (timeout, etc.)

      Returns Promise<
          {
              _meta?: { [key: string]: unknown };
              content: (
                  | { _meta?: { [key: string]: unknown }; text: string; type: "text" }
                  | {
                      _meta?: { [key: string]: unknown };
                      data: string;
                      mimeType: string;
                      type: "image";
                  }
                  | {
                      _meta?: { [key: string]: unknown };
                      data: string;
                      mimeType: string;
                      type: "audio";
                  }
                  | {
                      _meta?: { [key: string]: unknown };
                      description?: string;
                      icons?: { mimeType?: string; sizes?: string[]; src: string }[];
                      mimeType?: string;
                      name: string;
                      title?: string;
                      type: "resource_link";
                      uri: string;
                  }
                  | {
                      _meta?: { [key: string]: unknown };
                      resource:
                          | {
                              _meta?: { [key: string]: unknown };
                              mimeType?: string;
                              text: string;
                              uri: string;
                          }
                          | {
                              _meta?: { [key: string]: unknown };
                              blob: string;
                              mimeType?: string;
                              uri: string;
                          };
                      type: "resource";
                  }
              )[];
              isError?: boolean;
              structuredContent?: { [key: string]: unknown };
              [key: string]: unknown;
          },
      >

      Tool execution result

      If the tool does not exist on the server

      If the request times out or the connection is lost

      If the host rejects the request

      Note: Tool-level execution errors are returned in the result with isError: true rather than throwing exceptions. Always check result.isError to distinguish between transport failures (thrown) and tool execution failures (returned).

      try {
      const result = await app.callServerTool({
      name: "get_weather",
      arguments: { location: "Tokyo" }
      });
      if (result.isError) {
      console.error("Tool returned error:", result.content);
      } else {
      console.log(result.content);
      }
      } catch (error) {
      console.error("Tool call failed:", error);
      }
    • Closes the connection.

      Returns Promise<void>

    • Establish connection with the host and perform initialization handshake.

      This method performs the following steps:

      1. Connects the transport layer
      2. Sends ui/initialize request with app info and capabilities
      3. Receives host capabilities and context in response
      4. Sends ui/notifications/initialized notification
      5. Sets up auto-resize using setupSizeChangeNotifications if enabled (default)

      If initialization fails, the connection is automatically closed and an error is thrown.

      Parameters

      • transport: Transport

        Transport layer (typically PostMessageTransport)

      • Optionaloptions: RequestOptions

        Request options for the initialize request

      Returns Promise<void>

      If initialization fails or connection is lost

      const app = new App(
      { name: "MyApp", version: "1.0.0" },
      {}
      );

      try {
      await app.connect(new PostMessageTransport(window.parent));
      console.log("Connected successfully!");
      } catch (error) {
      console.error("Failed to connect:", error);
      }
    • Get the host's capabilities discovered during initialization.

      Returns the capabilities that the host advertised during the connect handshake. Returns undefined if called before connection is established.

      Returns McpUiHostCapabilities | undefined

      Host capabilities, or undefined if not yet connected

      await app.connect(transport);
      const caps = app.getHostCapabilities();
      if (caps === undefined) {
      console.error("Not connected");
      return;
      }
      if (caps.serverTools) {
      console.log("Host supports server tool calls");
      }
    • Get the host's implementation info discovered during initialization.

      Returns the host's name and version as advertised during the connect handshake. Returns undefined if called before connection is established.

      Returns
          | {
              icons?: { mimeType?: string; sizes?: string[]; src: string }[];
              name: string;
              title?: string;
              version: string;
              websiteUrl?: string;
          }
          | undefined

      Host implementation info, or undefined if not yet connected

      await app.connect(transport);
      const host = app.getHostVersion();
      if (host === undefined) {
      console.error("Not connected");
      return;
      }
      console.log(`Connected to ${host.name} v${host.version}`);

      connect for the initialization handshake

    • Emits a notification, which is a one-way message that does not expect a response.

      Parameters

      • notification: {
            method: string;
            params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown };
        }
      • Optionaloptions: NotificationOptions

      Returns Promise<void>

    • Removes the notification handler for the given method.

      Parameters

      • method: string

      Returns void

    • Removes the request handler for the given method.

      Parameters

      • method: string

      Returns void

    • Sends a request and wait for a response.

      Do not use this method to emit notifications! Use notification() instead.

      Type Parameters

      • T extends AnySchema

      Parameters

      • request: {
            method: string;
            params?: {
                _meta?: { progressToken?: string | number; [key: string]: unknown };
                [key: string]: unknown;
            };
        }
      • resultSchema: T
      • Optionaloptions: RequestOptions

      Returns Promise<SchemaOutput<T>>

    • Send log messages to the host for debugging and telemetry.

      Logs are not added to the conversation but may be recorded by the host for debugging purposes.

      Parameters

      • params: {
            _meta?: { [key: string]: unknown };
            data: unknown;
            level:
                | "error"
                | "debug"
                | "info"
                | "notice"
                | "warning"
                | "critical"
                | "alert"
                | "emergency";
            logger?: string;
            [key: string]: unknown;
        }

        Log level and message

      Returns Promise<void>

      Promise that resolves when the log notification is sent

      app.sendLog({
      level: "info",
      data: "Weather data refreshed",
      logger: "WeatherApp"
      });
    • Send a message to the host's chat interface.

      Enables the app to add messages to the conversation thread. Useful for user-initiated messages or app-to-conversation communication.

      Parameters

      • params: { content: ContentBlock[]; role: "user" }

        Message role and content

        • content: ContentBlock[]

          Message content blocks (text, image, etc.)

        • role: "user"

          Message role, currently only "user" is supported

      • Optionaloptions: RequestOptions

        Request options (timeout, etc.)

      Returns Promise<McpUiMessageResult>

      Result indicating success or error (no message content returned)

      If the host rejects the message

      try {
      await app.sendMessage({
      role: "user",
      content: [{ type: "text", text: "Show me details for item #42" }]
      });
      } catch (error) {
      console.error("Failed to send message:", error);
      // Handle error appropriately for your app
      }

      McpUiMessageRequest for request structure

    • Request the host to open an external URL in the default browser.

      The host may deny this request based on user preferences or security policy. Apps should handle rejection gracefully.

      Parameters

      • params: { url: string }

        URL to open

        • url: string

          URL to open in the host's browser

      • Optionaloptions: RequestOptions

        Request options (timeout, etc.)

      Returns Promise<McpUiOpenLinkResult>

      Result indicating success or error

      If the host denies the request (e.g., blocked domain, user cancelled)

      If the request times out or the connection is lost

      try {
      await app.sendOpenLink({ url: "https://docs.example.com" });
      } catch (error) {
      console.error("Failed to open link:", error);
      // Optionally show fallback: display URL for manual copy
      }

      McpUiOpenLinkRequest for request structure

    • Notify the host of UI size changes.

      Apps can manually report size changes to help the host adjust the container. If autoResize is enabled (default), this is called automatically.

      Parameters

      • params: { height?: number; width?: number }

        New width and height in pixels

        • Optionalheight?: number

          New height in pixels

        • Optionalwidth?: number

          New width in pixels

      Returns Promise<void>

      Promise that resolves when the notification is sent

      app.sendSizeChange({
      width: 400,
      height: 600
      });

      McpUiSizeChangeNotification for notification structure

    • Registers a handler to invoke when this protocol object receives a notification with the given method.

      Note that this will replace any previous notification handler for the same method.

      Type Parameters

      • T extends AnyObjectSchema

      Parameters

      • notificationSchema: T
      • handler: (notification: SchemaOutput<T>) => void | Promise<void>

      Returns void

    • Registers a handler to invoke when this protocol object receives a request with the given method.

      Note that this will replace any previous request handler for the same method.

      Type Parameters

      • T extends AnyObjectSchema

      Parameters

      • requestSchema: T
      • handler: (
            request: SchemaOutput<T>,
            extra: RequestHandlerExtra<
                {
                    method: string;
                    params?: {
                        _meta?: { progressToken?: string
                        | number; [key: string]: unknown };
                        [key: string]: unknown;
                    };
                },
                {
                    method: string;
                    params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown };
                },
            >,
        ) => | { _meta?: { [key: string]: unknown }; [key: string]: unknown }
        | Promise<{ _meta?: { [key: string]: unknown }; [key: string]: unknown }>

      Returns void

    • Set up automatic size change notifications using ResizeObserver.

      Observes both document.documentElement and document.body for size changes and automatically sends ui/notifications/size-change notifications to the host. The notifications are debounced using requestAnimationFrame to avoid duplicates.

      Note: This method is automatically called by connect() if the autoResize option is true (default). You typically don't need to call this manually unless you disabled autoResize and want to enable it later.

      Returns () => void

      Cleanup function to disconnect the observer

      const app = new App(appInfo, capabilities, { autoResize: false });
      await app.connect(transport);

      // Later, enable auto-resize manually
      const cleanup = app.setupSizeChangeNotifications();

      // Clean up when done
      cleanup();