Skip to content

Commit 95e0b71

Browse files
authored
Implement React UI Builder and Enhance Greeting Widget (#58)
* feat(ui-builder): Implement React UI definition and HTML generation utilities - Added `defineReactUI` and `isReactUIDef` functions for defining and validating React-based UI components. - Introduced `generateHTML` and `generateEntryPoint` functions for creating self-contained HTML documents and entry points for React components. - Developed transformation utilities to convert React UI definitions to standard UIDef format. - Created comprehensive type definitions for React UI components and build configurations. - Implemented unit tests for all new functionalities to ensure reliability and correctness. - Configured TypeScript and build settings for the new package structure. * feat(minimal-example): Enhance Greeting Widget with React UI integration and update package configuration * feat: Add publishing step for ui-react-builder and update release scripts * feat(ui-react-builder): Update README with ui-react-builder package details and usage examples * feat: Enhance tool output handling by wrapping results with tool names in MCP and OpenAI adapters * feat: Enhance type safety across UI components and tools with ClientToolsFromCore integration * refactor: Simplify JSX structure in Widget component and streamline output type extraction logic * feat: Enhance UI React Builder with AST-based parsing and improved logging - Added AST-based parser for discovering defineReactUI calls using @typescript-eslint/typescript-estree. - Updated buildReactUIs function to include detailed limitations and usage notes. - Introduced custom logging options for the Vite plugin, allowing for silent or custom loggers. - Improved component path resolution with better error handling and logging. - Updated unit tests for the new AST parser, covering various import scenarios and edge cases. - Added dependencies for @typescript-eslint/typescript-estree and updated package.json accordingly.
1 parent 0ac6476 commit 95e0b71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4557
-184
lines changed

.github/workflows/publish.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,8 @@ jobs:
5656
- name: Publish @mcp-apps-kit/ui-react
5757
run: pnpm --filter @mcp-apps-kit/ui-react exec npm publish --access public --provenance
5858

59+
- name: Publish @mcp-apps-kit/ui-react-builder
60+
run: pnpm --filter @mcp-apps-kit/ui-react-builder exec npm publish --access public --provenance
61+
5962
- name: Publish @mcp-apps-kit/create-app
6063
run: pnpm --filter @mcp-apps-kit/create-app exec npm publish --access public --provenance

README.md

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[![npm @mcp-apps-kit/core](https://img.shields.io/npm/v/%40mcp-apps-kit%2Fcore?label=%40mcp-apps-kit%2Fcore&logo=npm)](https://www.npmjs.com/package/@mcp-apps-kit/core)
88
[![npm @mcp-apps-kit/ui](https://img.shields.io/npm/v/%40mcp-apps-kit%2Fui?label=%40mcp-apps-kit%2Fui&logo=npm)](https://www.npmjs.com/package/@mcp-apps-kit/ui)
99
[![npm @mcp-apps-kit/ui-react](https://img.shields.io/npm/v/%40mcp-apps-kit%2Fui-react?label=%40mcp-apps-kit%2Fui-react&logo=npm)](https://www.npmjs.com/package/@mcp-apps-kit/ui-react)
10+
[![npm @mcp-apps-kit/ui-react-builder](https://img.shields.io/npm/v/%40mcp-apps-kit%2Fui-react-builder?label=%40mcp-apps-kit%2Fui-react-builder&logo=npm)](https://www.npmjs.com/package/@mcp-apps-kit/ui-react-builder)
1011
[![npm @mcp-apps-kit/create-app](https://img.shields.io/npm/v/%40mcp-apps-kit%2Fcreate-app?label=%40mcp-apps-kit%2Fcreate-app&logo=npm)](https://www.npmjs.com/package/@mcp-apps-kit/create-app)
1112

1213
Build interactive AI apps for [MCP Apps](https://blog.modelcontextprotocol.io/posts/2025-11-21-mcp-apps/) and [ChatGPT](https://developers.openai.com/apps-sdk) from a single codebase.
@@ -21,6 +22,7 @@ MCP AppsKit is a TypeScript framework for building interactive applications with
2122
- [Compatibility](#compatibility)
2223
- [Install](#install)
2324
- [Usage](#usage)
25+
- [React Component UIs](#react-component-uis)
2426
- [Type-Safe Tool Definitions](#type-safe-tool-definitions)
2527
- [How It Works](#how-it-works)
2628
- [Deployment Options](#deployment-options)
@@ -70,12 +72,13 @@ This project may be a poor fit if you:
7072

7173
## Packages
7274

73-
| Package | Description |
74-
| -------------------------- | ---------------------------- |
75-
| `@mcp-apps-kit/core` | Server-side framework |
76-
| `@mcp-apps-kit/ui` | Client-side SDK (vanilla JS) |
77-
| `@mcp-apps-kit/ui-react` | React bindings |
78-
| `@mcp-apps-kit/create-app` | CLI scaffolding tool |
75+
| Package | Description |
76+
| -------------------------------- | ---------------------------------- |
77+
| `@mcp-apps-kit/core` | Server-side framework |
78+
| `@mcp-apps-kit/ui` | Client-side SDK (vanilla JS) |
79+
| `@mcp-apps-kit/ui-react` | React bindings |
80+
| `@mcp-apps-kit/ui-react-builder` | Build tool for React component UIs |
81+
| `@mcp-apps-kit/create-app` | CLI scaffolding tool |
7982

8083
## Compatibility
8184

@@ -107,6 +110,12 @@ Vanilla UI SDK:
107110
npm install @mcp-apps-kit/ui
108111
```
109112

113+
React UI builder (for defining UIs with React components):
114+
115+
```bash
116+
npm install @mcp-apps-kit/ui-react-builder
117+
```
118+
110119
CLI scaffolding tool:
111120

112121
```bash
@@ -180,6 +189,7 @@ import { useAppsClient, useToolResult, useHostContext } from "@mcp-apps-kit/ui-r
180189
import type { AppClientTools } from "../../server";
181190

182191
function RestaurantList() {
192+
// Type-safe hooks - tool names, inputs, and outputs are all inferred
183193
const client = useAppsClient<AppClientTools>();
184194
const result = useToolResult<AppClientTools>();
185195
const context = useHostContext();
@@ -207,6 +217,58 @@ function RestaurantList() {
207217
}
208218
```
209219

220+
### React Component UIs
221+
222+
Instead of pre-building HTML files, you can define UIs using React components directly with `@mcp-apps-kit/ui-react-builder`:
223+
224+
```typescript
225+
// server/index.ts
226+
import { createApp, defineTool } from "@mcp-apps-kit/core";
227+
import { defineReactUI } from "@mcp-apps-kit/ui-react-builder";
228+
import { RestaurantList } from "./ui/RestaurantList";
229+
import { z } from "zod";
230+
231+
const app = createApp({
232+
name: "restaurant-finder",
233+
version: "1.0.0",
234+
tools: {
235+
search_restaurants: defineTool({
236+
description: "Search for restaurants",
237+
input: z.object({ location: z.string() }),
238+
output: z.object({ restaurants: z.array(z.unknown()) }),
239+
// Define UI with React component - auto-builds to HTML
240+
ui: defineReactUI({
241+
component: RestaurantList,
242+
name: "Restaurant List",
243+
prefersBorder: true,
244+
}),
245+
handler: async (input) => {
246+
return { restaurants: await fetchRestaurants(input.location) };
247+
},
248+
}),
249+
},
250+
});
251+
```
252+
253+
Configure the Vite plugin to auto-discover and build React UIs:
254+
255+
```typescript
256+
// vite.config.ts
257+
import { defineConfig } from "vite";
258+
import { mcpReactUI } from "@mcp-apps-kit/ui-react-builder/vite";
259+
260+
export default defineConfig({
261+
plugins: [
262+
mcpReactUI({
263+
serverEntry: "./src/index.ts",
264+
outDir: "./src/ui/dist",
265+
}),
266+
],
267+
});
268+
```
269+
270+
The plugin scans your server entry for `defineReactUI` calls, bundles each component with React and `@mcp-apps-kit/ui-react`, and outputs self-contained HTML files.
271+
210272
### CLI
211273

212274
```bash
@@ -396,6 +458,7 @@ Detailed package documentation:
396458
- [packages/core/README.md](packages/core/README.md)
397459
- [packages/ui/README.md](packages/ui/README.md)
398460
- [packages/ui-react/README.md](packages/ui-react/README.md)
461+
- [packages/ui-react-builder/README.md](packages/ui-react-builder/README.md)
399462
- [packages/create-app/README.md](packages/create-app/README.md)
400463

401464
## Contributing

eslint.config.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@ export default [
1212
},
1313
// Server-side packages (Node.js environment)
1414
{
15-
files: ["packages/core/**/*.ts", "packages/create-app/**/*.ts"],
15+
files: [
16+
"packages/core/**/*.ts",
17+
"packages/create-app/**/*.ts",
18+
"packages/ui-react-builder/**/*.ts",
19+
],
1620
languageOptions: {
1721
parser: tsparser,
1822
parserOptions: {
19-
project: ["./packages/core/tsconfig.json", "./packages/create-app/tsconfig.json"],
23+
project: [
24+
"./packages/core/tsconfig.json",
25+
"./packages/create-app/tsconfig.json",
26+
"./packages/ui-react-builder/tsconfig.json",
27+
],
2028
tsconfigRootDir: import.meta.dirname,
2129
},
2230
globals: {

examples/minimal/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@mcp-apps-kit/example-minimal",
33
"version": "0.1.0",
44
"private": true,
5-
"description": "Minimal example demonstrating basic @mcp-apps-kit/core usage",
5+
"description": "Minimal example demonstrating basic @mcp-apps-kit/core usage with React UI",
66
"type": "module",
77
"scripts": {
88
"dev": "concurrently \"pnpm dev:server\" \"pnpm dev:ui\"",
@@ -16,10 +16,17 @@
1616
"dependencies": {
1717
"@mcp-apps-kit/core": "workspace:*",
1818
"@mcp-apps-kit/ui": "workspace:*",
19+
"@mcp-apps-kit/ui-react-builder": "workspace:*",
20+
"@mcp-apps-kit/ui-react": "workspace:*",
21+
"react": "^19.2.3",
22+
"react-dom": "^19.2.3",
1923
"zod": "^4.0.0"
2024
},
2125
"devDependencies": {
2226
"@types/node": "^25.0.3",
27+
"@types/react": "^19.2.7",
28+
"@types/react-dom": "^19.2.3",
29+
"@vitejs/plugin-react": "^5.1.2",
2330
"concurrently": "^9.2.1",
2431
"tsx": "^4.0.0",
2532
"typescript": "^5.0.0",

examples/minimal/src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
* - Type-safe handlers using defineTool helper (no type assertions needed!)
99
*/
1010

11-
import { createApp, defineTool, defineUI, type ClientToolsFromCore } from "@mcp-apps-kit/core";
11+
import { createApp, defineTool, type ClientToolsFromCore } from "@mcp-apps-kit/core";
12+
import { defineReactUI } from "@mcp-apps-kit/ui-react-builder";
13+
import { GreetingWidget } from "./ui/GreetingWidget";
1214
import { z } from "zod";
1315

1416
// Define schemas separately for clarity
@@ -30,11 +32,11 @@ const greetTool = defineTool({
3032
output: greetOutput,
3133
visibility: "both",
3234

33-
// Colocated UI definition - no separate ui config needed!
34-
ui: defineUI({
35+
// React component UI - the Vite plugin auto-discovers and builds this!
36+
ui: defineReactUI({
37+
component: GreetingWidget,
3538
name: "Greeting Widget",
3639
description: "Displays greeting messages",
37-
html: "./src/ui/dist/index.html",
3840
prefersBorder: true,
3941
}),
4042

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Greeting Widget Component
3+
*
4+
* A React component that displays greeting messages from the greet tool.
5+
* Uses @mcp-apps-kit/ui-react hooks for receiving tool output and theme changes.
6+
*/
7+
8+
import { useEffect } from "react";
9+
import { useToolResult, useHostContext } from "@mcp-apps-kit/ui-react";
10+
import type { AppClientTools } from "../index";
11+
12+
export function GreetingWidget() {
13+
const result = useToolResult<AppClientTools>();
14+
const { theme } = useHostContext();
15+
16+
// Extract the greet output - result contains the tool outputs keyed by tool name
17+
const greetOutput = result?.greet;
18+
19+
// Apply theme to document
20+
useEffect(() => {
21+
if (typeof document !== "undefined") {
22+
document.documentElement.className = theme;
23+
}
24+
}, [theme]);
25+
26+
if (greetOutput?.message) {
27+
const date = new Date(greetOutput.timestamp);
28+
const timeStr = date.toLocaleTimeString();
29+
30+
return (
31+
<div className="container">
32+
<div className="greeting">
33+
<h1>{greetOutput.message}</h1>
34+
<p className="timestamp">at {timeStr}</p>
35+
</div>
36+
</div>
37+
);
38+
}
39+
40+
return (
41+
<div className="container">
42+
<p className="waiting">Waiting for greeting...</p>
43+
</div>
44+
);
45+
}
46+
47+
export default GreetingWidget;

examples/minimal/src/ui/index.html

Lines changed: 0 additions & 61 deletions
This file was deleted.

examples/minimal/src/ui/main.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)