ext-appss / src /react /useApp.tsx
AbdulElahGwaith's picture
Upload folder using huggingface_hub
e1cc3bc verified
import { useEffect, useState } from "react";
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
import { Client } from "@modelcontextprotocol/sdk/client";
import { App, McpUiAppCapabilities, PostMessageTransport } from "../app";
export * from "../app";
/**
* Options for configuring the {@link useApp} hook.
*
* Note: This interface does NOT expose {@link 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.
*
* @see {@link useApp} for the hook that uses these options
* @see {@link useAutoResize} for manual auto-resize control with custom `App` options
*/
export interface UseAppOptions {
/** App identification (name and version) */
appInfo: Implementation;
/**
* Declares what features this app supports.
*/
capabilities: McpUiAppCapabilities;
/**
* Called after {@link App} is created but before connection.
*
* Use this to register request/notification handlers that need to be in place
* before the initialization handshake completes.
*
* @param app - The newly created `App` instance
*
* @example Register a notification handler
* ```typescript
* import { McpUiToolInputNotificationSchema } from '@modelcontextprotocol/ext-apps/react';
*
* onAppCreated: (app) => {
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool input:", notification.params.arguments);
* }
* );
* }
* ```
*/
onAppCreated?: (app: App) => void;
}
/**
* State returned by the {@link useApp} hook.
*/
export interface AppState {
/** The connected {@link App} instance, null during initialization */
app: App | null;
/** Whether initialization completed successfully */
isConnected: boolean;
/** Connection error if initialization failed, null otherwise */
error: Error | null;
}
/**
* React hook to create and connect an MCP App.
*
* This hook manages {@link App} creation and connection. It automatically
* creates a {@link PostMessageTransport} to window.parent and handles
* initialization.
*
* This hook is part of the optional React integration. The core SDK (`App`,
* `PostMessageTransport`) is framework-agnostic and can be used with any UI
* framework or vanilla JavaScript.
*
* **Important**: The hook intentionally does NOT re-run when options change
* to avoid reconnection loops. Options are only used during the initial mount.
* Furthermore, the `App` instance is NOT closed on unmount. This avoids cleanup
* issues during React Strict Mode's double-mount cycle. If you need to
* explicitly close the `App`, call {@link App.close} manually.
*
* @param options - Configuration for the app
* @returns Current connection state and app instance. If connection fails during
* initialization, the `error` field will contain the error (typically connection
* timeouts, initialization handshake failures, or transport errors).
*
* @example Basic usage
* ```typescript
* import { useApp, McpUiToolInputNotificationSchema } from '@modelcontextprotocol/ext-apps/react';
*
* function MyApp() {
* const { app, isConnected, error } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* onAppCreated: (app) => {
* // Register handlers before connection
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool input:", notification.params.arguments);
* }
* );
* },
* });
*
* if (error) return <div>Error: {error.message}</div>;
* if (!isConnected) return <div>Connecting...</div>;
* return <div>Connected!</div>;
* }
* ```
*
* @see {@link App.connect} for the underlying connection method
* @see {@link useAutoResize} for manual auto-resize control when using custom App options
*/
export function useApp({
appInfo,
capabilities,
onAppCreated,
}: UseAppOptions): AppState {
const [app, setApp] = useState<App | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let mounted = true;
async function connect() {
try {
const transport = new PostMessageTransport(
window.parent,
window.parent,
);
const app = new App(appInfo, capabilities);
// Register handlers BEFORE connecting
onAppCreated?.(app);
await app.connect(transport);
if (mounted) {
setApp(app);
setIsConnected(true);
setError(null);
}
} catch (error) {
if (mounted) {
setApp(null);
setIsConnected(false);
setError(
error instanceof Error ? error : new Error("Failed to connect"),
);
}
}
}
connect();
return () => {
mounted = false;
};
}, []); // Intentionally not including options to avoid reconnection
return { app, isConnected, error };
}