@mcp-apps-kit/ui-react
v0.5.0
Published
React bindings for MCP applications
Downloads
46
Maintainers
Readme
@mcp-apps-kit/ui-react
React bindings for MCP applications.
@mcp-apps-kit/ui-react builds on @mcp-apps-kit/ui to provide React context and hooks for tool calls, tool results, and host context.
Table of Contents
Background
React widgets often need host-aware APIs for tool calls and UI state. This package provides a React-first wrapper around the vanilla UI SDK so you can use hooks instead of manual subscriptions.
Features
AppsProvidercontext wrapper- Hooks for tools, host context, and widget state
- Typed tool calls with generics
- Optional debug logger hook
- Host capabilities - Query what the host supports (theming, display modes, file upload, etc.)
- Size notifications - Automatic resize observer integration
- Partial tool input - React to streaming tool inputs
Compatibility
- Hosts: MCP Apps and ChatGPT (OpenAI Apps SDK)
- Node.js:
>= 18for tooling/builds (browser runtime) - Peer dependencies:
reactandreact-dom^18 || ^19
Install
npm install @mcp-apps-kit/ui-reactUsage
Quick start
import { AppsProvider, useAppsClient, useToolResult, useHostContext } from "@mcp-apps-kit/ui-react";
function Widget() {
const client = useAppsClient();
const result = useToolResult();
const host = useHostContext();
return (
<div data-theme={host.theme}>
<button onClick={() => client.callTool("greet", { name: "Alice" })}>Greet</button>
<pre>{JSON.stringify(result, null, 2)}</pre>
</div>
);
}
export function App() {
return (
<AppsProvider>
<Widget />
</AppsProvider>
);
}Typed tools and results
Use ClientToolsFromCore from @mcp-apps-kit/core for end-to-end type safety:
import { AppsProvider, useAppsClient, useToolResult } from "@mcp-apps-kit/ui-react";
import type { AppClientTools } from "../server"; // Exported from your server
function Widget() {
// Typed client - callTool arguments and return types are inferred
const client = useAppsClient<AppClientTools>();
// Typed results - result?.greet?.message is typed as string | undefined
const result = useToolResult<AppClientTools>();
const handleGreet = async () => {
// Option 1: Using callTool with tool name string
await client.callTool("greet", { name: "Alice" });
// Option 2: Using the typed tools proxy (recommended)
await client.tools.callGreet({ name: "Alice" });
};
if (result?.greet) {
return (
<div>
<p>{result.greet.message}</p>
<p>at {result.greet.timestamp}</p>
</div>
);
}
return <button onClick={handleGreet}>Greet</button>;
}
export function App() {
return (
<AppsProvider>
<Widget />
</AppsProvider>
);
}Automatic size notifications
By default, the MCP adapter automatically reports UI size changes to the host using a ResizeObserver. This feature is only supported in MCP Apps and is silently ignored in ChatGPT.
To disable automatic resizing:
export function App() {
return (
<AppsProvider autoResize={false}>
<Widget />
</AppsProvider>
);
}Note: The autoResize option is only applied during initial mount. Changing it at runtime has no effect.
The AppClientTools type is generated in your server code:
// server/index.ts
import { createApp, defineTool, type ClientToolsFromCore } from "@mcp-apps-kit/core";
const app = createApp({
tools: {
greet: defineTool({
input: z.object({ name: z.string() }),
output: z.object({ message: z.string(), timestamp: z.string() }),
handler: async (input) => ({ ... }),
}),
},
});
// Export for UI code
export type AppClientTools = ClientToolsFromCore<typeof app.tools>;Examples
API
Provider
AppsProvider- Context wrapper for all hooksclient?- Pre-initialized client instance (optional)forceAdapter?- Force a specific adapter ("mcp" | "openai" | "mock")autoResize?- Enable/disable automatic size change notifications (default:true). Only supported in MCP Apps; ignored in ChatGPT. Note: changing this prop after initial mount has no effect.fallback?- Component to show while client initializeserrorFallback?- Component to show on initialization error
Core Hooks
| Hook | Description |
| ---------------- | ----------------------------------------- |
| useAppsClient | Client instance for tool calls |
| useToolResult | Current tool result data |
| useToolInput | Tool input parameters |
| useHostContext | Host info (theme, viewport, locale, etc.) |
| useWidgetState | Persisted state across reloads |
| useDisplayMode | Fullscreen/panel mode control |
| useDebugLogger | Debug logging configuration |
Typed Tools Proxy
The client returned by useAppsClient includes a tools property with typed method wrappers:
import { useAppsClient } from "@mcp-apps-kit/ui-react";
import type { AppClientTools } from "../server";
function Widget() {
const client = useAppsClient<AppClientTools>();
const handleClick = async () => {
// Tool name "greet" becomes method "callGreet"
const result = await client.tools.callGreet({ name: "Alice" });
console.log(result.message);
};
return <button onClick={handleClick}>Greet</button>;
}Method names are generated by prepending "call" and capitalizing the first letter of the tool name (e.g., greet → callGreet, searchRestaurants → callSearchRestaurants).
Host Capabilities & Version
import { useHostCapabilities, useHostVersion } from "@mcp-apps-kit/ui-react";
function Widget() {
const capabilities = useHostCapabilities();
const version = useHostVersion();
// Common capabilities (both platforms)
const themes = capabilities?.theming?.themes; // ["light", "dark"]
const modes = capabilities?.displayModes?.modes; // ["inline", "fullscreen", "pip"]
// MCP Apps specific
const hasPartialInput = !!capabilities?.partialToolInput;
// ChatGPT specific
const hasFileUpload = !!capabilities?.fileUpload;
// Host version (MCP Apps only)
// { name: "MCP Host", version: "1.0.0" }
return <div>Host: {version?.name}</div>;
}Size Notifications (MCP Apps)
import { useSizeChangedNotifications } from "@mcp-apps-kit/ui-react";
function Widget() {
// Attach to container to auto-report size changes
const containerRef = useSizeChangedNotifications();
return <div ref={containerRef}>Content that may resize</div>;
}Partial Tool Input (MCP Apps)
import { useOnToolInputPartial } from "@mcp-apps-kit/ui-react";
function Widget() {
useOnToolInputPartial((input) => {
// React to streaming partial input from the model
console.log("Partial input:", input);
});
return <div>Streaming input widget</div>;
}Theme & Style Hooks
| Hook | Description |
| ----------------------- | --------------------------------- |
| useHostStyleVariables | Apply host-provided CSS variables |
| useDocumentTheme | Sync document theme with host |
| useSafeAreaInsets | Safe area insets (ChatGPT) |
Lifecycle Hooks
| Hook | Description |
| -------------------- | -------------------------------------- |
| useOnToolCancelled | Callback when tool is cancelled |
| useOnTeardown | Cleanup callback before widget removal |
File Operations (ChatGPT)
| Hook | Description |
| ----------------- | ---------------------- |
| useFileUpload | Upload files to host |
| useFileDownload | Get file download URLs |
Layout (ChatGPT)
| Hook | Description |
| -------------------- | --------------------------- |
| useIntrinsicHeight | Set widget intrinsic height |
| useView | View management |
Modals (ChatGPT)
| Hook | Description |
| ---------- | ----------------------- |
| useModal | Modal dialog management |
Contributing
See ../../CONTRIBUTING.md for development setup and guidelines. Issues and pull requests are welcome.
License
MIT
