npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

v0.5.0

Published

Build tool for React-based MCP application UIs

Readme

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

npm node license

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:

// src/ui/GreetingWidget.tsx
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:

// src/index.ts
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,
        // Optional: Disable automatic size notifications (default: true)
        // autoResize: false,
      }),
      handler: async ({ name }) => ({
        message: `Hello, ${name}!`,
      }),
    }),
  },
});

Vite Plugin

The Vite plugin automatically discovers defineReactUI calls and builds them into self-contained HTML files.

Configuration

// vite.config.ts
import { defineConfig } from "vite";
import { mcpReactUI } from "@mcp-apps-kit/ui-react-builder/vite";

export default defineConfig({
  plugins: [
    mcpReactUI({
      // Server entry point to scan for defineReactUI calls
      serverEntry: "./src/index.ts",
      // Output directory for built HTML files
      outDir: "./src/ui/dist",
      // Optional: Global CSS to include in all UIs
      globalCss: "./src/ui/styles.css",

      // Optional: Standalone mode takes over the Vite build and outputs only UI HTML.
      // Use this if this Vite config exists solely to build MCP UI widgets.
      // standalone: true,
    }),
  ],
});

How it works

  1. The plugin scans your serverEntry file for defineReactUI calls using AST parsing
  2. It resolves component imports to their source files
  3. Each component is bundled with React, ReactDOM, and @mcp-apps-kit/ui-react
  4. 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"; // ✓ Works
    import { MyWidget } from "./ui"; // ✗ Barrel imports not supported
  • Use string literals for the name property:
    name: "My Widget"; // ✓ Works
    name: `My ${type}`; // ✗ Template literals not supported
  • Reference components by identifier:
    component: MyWidget; // ✓ Works
    component: widgets.MyWidget; // ✗ Property access not supported

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

| Export | Description | | --------------- | ---------------------------------------------- | | defineReactUI | Define a UI using a React component | | isReactUIDef | Type guard to check if a value is a ReactUIDef |

defineReactUI Options

| Option | Type | Default | Description | | --------------- | --------------- | ---------- | ------------------------------------------------------------------------------------------- | | 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

| Type | Description | | -------------- | --------------------------------------- | | 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

| Export | Description | | --------------- | -------------------------------- | | 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

| Export | Description | | -------------------------- | -------------------------------------- | | 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

| Export | Description | | -------------------- | -------------------------------------- | | 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";

| Option | Type | Default | Description | | ------------- | ----------------------- | -------------- | ------------------------------------------- | | 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

// Disable all logging
mcpReactUI({
  serverEntry: "./src/index.ts",
  logger: false,
});

// Custom logger
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