@mcp-apps-kit/ui-react-builder

Build tool for React-based MCP application UIs.
@mcp-apps-kit/ui-react-builder allows you to define UI resources using React components instead of pre-built HTML files. The framework handles bundling React, ReactDOM, and @mcp-apps-kit/ui-react into self-contained HTML that works with both MCP Apps and ChatGPT.
Table of Contents
Background
Building interactive UI widgets for MCP applications traditionally requires manually bundling React components into self-contained HTML files. This package automates that process, letting you define UIs with React components directly in your tool definitions.
Features
defineReactUI() helper for type-safe React component definitions
- Vite plugin for automatic discovery and building of React UIs
- AST-based parsing using
@typescript-eslint/typescript-estree for reliable detection
- esbuild-powered bundling to self-contained HTML
- Auto-generated HTML paths from component names (kebab-case)
- Global CSS injection support
- Configurable logging for plugin output
- Full compatibility with
defineTool() from @mcp-apps-kit/core
Compatibility
- Node.js:
>= 18
- Peer dependencies:
@mcp-apps-kit/core ^0.2.0
@mcp-apps-kit/ui-react ^0.2.0
react and react-dom ^18 || ^19
vite ^5 || ^6 || ^7 (optional, for Vite plugin)
Install
npm install @mcp-apps-kit/ui-react-builder
Usage
Quick start
Define your React component:
import { useToolResult, useHostContext } from "@mcp-apps-kit/ui-react";
export function GreetingWidget() {
const result = useToolResult();
const { theme } = useHostContext();
return (
<div data-theme={theme}>
<h1>{result?.greet?.message}</h1>
</div>
);
}
Use defineReactUI in your tool definition:
import { createApp, defineTool } from "@mcp-apps-kit/core";
import { defineReactUI } from "@mcp-apps-kit/ui-react-builder";
import { GreetingWidget } from "./ui/GreetingWidget";
import { z } from "zod";
const app = createApp({
name: "my-app",
version: "1.0.0",
tools: {
greet: defineTool({
description: "Greet someone",
input: z.object({ name: z.string() }),
output: z.object({ message: z.string() }),
ui: defineReactUI({
component: GreetingWidget,
name: "Greeting Widget",
prefersBorder: true,
}),
handler: async ({ name }) => ({
message: `Hello, ${name}!`,
}),
}),
},
});
Vite Plugin
The Vite plugin automatically discovers defineReactUI calls and builds them into self-contained HTML files.
Configuration
import { defineConfig } from "vite";
import { mcpReactUI } from "@mcp-apps-kit/ui-react-builder/vite";
export default defineConfig({
plugins: [
mcpReactUI({
serverEntry: "./src/index.ts",
outDir: "./src/ui/dist",
globalCss: "./src/ui/styles.css",
}),
],
});
How it works
- The plugin scans your
serverEntry file for defineReactUI calls using AST parsing
- It resolves component imports to their source files
- Each component is bundled with React, ReactDOM, and
@mcp-apps-kit/ui-react
- Self-contained HTML files are written to
outDir
The plugin uses @typescript-eslint/typescript-estree for reliable AST-based detection of imports and defineReactUI calls. This is more robust than regex-based parsing and correctly handles:
- Nested
defineReactUI calls (e.g., inside defineTool)
- Comments around definitions
- Various import styles (named, default, aliased)
- Complex code structures (conditionals, arrays, objects)
Supported patterns
The plugin discovers defineReactUI calls using static analysis. For reliable detection:
- Import components directly from their source files:
import { MyWidget } from "./ui/MyWidget";
import { MyWidget } from "./ui";
- Use string literals for the
name property:
name: "My Widget";
name: `My ${type}`;
- Reference components by identifier:
component: MyWidget;
component: widgets.MyWidget;
If you need patterns not supported by auto-discovery, use defineUI({ html: "..." }) with manual Vite bundling.
Build commands
{
"scripts": {
"dev": "concurrently \"pnpm dev:server\" \"pnpm dev:ui\"",
"dev:server": "tsx watch src/index.ts",
"dev:ui": "vite build --watch",
"build": "pnpm build:ui && tsc",
"build:ui": "vite build"
}
}
API
Definition Helpers
defineReactUI | Define a UI using a React component |
isReactUIDef | Type guard to check if a value is a ReactUIDef |
defineReactUI Options
component | ComponentType | (required) | React component to render |
name | string | (required) | Display name for the UI |
description | string | - | Description of the UI widget |
prefersBorder | boolean | - | Hint to the host whether a border should be drawn |
autoResize | boolean | true | Enable automatic size change notifications. Only supported in MCP Apps; ignored in ChatGPT. |
csp | CSPConfig | - | Content Security Policy configuration (ChatGPT only) |
Types
ReactUIInput | Input type for defineReactUI() |
ReactUIDef | Output type (extends UIDef from core) |
BuildOptions | Options for the build process |
BuildResult | Result of building React UIs |
PluginLogger | Logger interface for Vite plugin |
Build Functions
buildReactUIs | Build multiple React UIs to HTML |
buildReactUI | Build a single React UI to HTML |
Note: The programmatic build functions (buildReactUIs, buildReactUI) serialize components using .toString(), which has limitations:
- No external imports (components cannot import other modules)
- No closures (components that capture external variables won't work)
- Simple components only (best for self-contained components)
For production use, prefer the Vite plugin which uses file paths for proper import resolution.
Transform Utilities
transformToCoreDefs | Convert ReactUIDefs to standard UIDefs |
transformSingleToCoreDef | Convert a single ReactUIDef to UIDef |
extractReactUIs | Separate React UIs from standard UIs |
buildAndTransform | Build and transform in one step |
HTML Utilities
generateHTML | Generate HTML document from bundled JS |
generateEntryPoint | Generate React entry point code |
Vite Plugin
import { mcpReactUI } from "@mcp-apps-kit/ui-react-builder/vite";
serverEntry | string | (required) | Server entry point to scan |
outDir | string | "./dist/ui" | Output directory for HTML files |
minify | boolean | true in prod | Minify output JavaScript |
globalCss | string | - | Path to global CSS file |
logger | PluginLogger | false | console | Custom logger or false to disable logging |
standalone | boolean | false | Take over Vite build (emit only UI HTML) |
Custom logging
mcpReactUI({
serverEntry: "./src/index.ts",
logger: false,
});
mcpReactUI({
serverEntry: "./src/index.ts",
logger: {
info: (msg) => myLogger.info(msg),
warn: (msg) => myLogger.warn(msg),
error: (msg) => myLogger.error(msg),
},
});
Examples
../../examples/minimal - Simple hello world with React UI
Contributing
See ../../CONTRIBUTING.md for development setup and guidelines. Issues and pull requests are welcome.
License
MIT