-
Notifications
You must be signed in to change notification settings - Fork 8
feat: Add uiResource API with React hooks for interactive widgets #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add uiResource() method to McpServer for registering interactive UI widgets - Support three widget types: externalUrl, rawHtml, and remoteDom - Create @mcp-lite/react package with useWidget hook and OpenAI Apps SDK compatibility - Add comprehensive example with weather widget demonstrating React integration - Include manual testing guide and automated test script - Auto-register widgets as both tools (with params) and resources (static + dynamic) - Props flow via URL query params and window.openai.toolInput injection
|
|
@laulauland Could you please review this PR? 🙏 This adds UI widget support with a new Key features:
Let me know if you have any questions! |
- Fix tsconfig.json to use proper bundler module resolution - Add skipLibCheck to avoid bun-types conflicts - Fix optional chaining in test-manual.ts script
|
Thanks for the detailed PR and examples! I appreciate the thought that went into this, and I understand the pain of having to wrangle all the boilerplate to wire up UI resources with MCP tools. Making this easier is a good idea. My main concern is adding a significant API surface area to
These are host-specific extensions rather than core MCP protocol features. One of mcp-lite's core features is staying lightweight and focused exclusively on the official MCP specification in the core package, then using auxiliary packages to augment functionality. This keeps the core lean and makes it easier to maintain as the spec evolves. I think we can achieve the same developer experience while keeping this separation. Option 1: Separate Widget PackageCreate a new import { McpServer } from 'mcp-lite';
import { registerOpenAIWidget } from '@mcp-lite/widgets-openai';
const server = new McpServer({ name: "app", version: "1.0.0" });
// All the widget registration logic lives in the separate package
registerOpenAIWidget(server, {
type: 'externalUrl',
name: 'weather-widget',
url: 'http://localhost:5173',
inputSchema: { ... }
});
Option 2: Just show examplesAlternatively, keep Ideally btw the example should demonstrate bundling and serving the React app assets directly from the MCP server, rather than requiring a separate deployment. This gives developers a much better experience—everything lives in one application. For example: // examples/openai-widgets/src/index.ts
import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';
import { McpServer } from 'mcp-lite';
import { registerOpenAIWidget } from './widget-helpers.js';
const app = new Hono();
const server = new McpServer({ name: "app", version: "1.0.0" });
// Serve built React assets from ./dist/widgets
app.use('/widgets/*', serveStatic({ root: './dist' }));
// Register widget pointing to locally-served assets
registerOpenAIWidget(server, {
type: 'externalUrl',
name: 'weather-widget',
url: 'http://localhost:3000/widgets/weather/index.html', // Served by same server
inputSchema: { ... }
});
// MCP endpoint
app.all('/mcp', /* ... */); |
Overview
This PR introduces UI widget support to mcp-lite, enabling developers to create interactive widgets similar to mcp-use. The implementation adds a new
uiResource()API that automatically registers widgets as both tools (accepting parameters) and resources (serving the UI).What's New
Core Changes
uiResource()method inMcpServerclass for registering interactive widgetsexternalUrl: Wraps external URLs in an iframe with props injectionrawHtml: Serves inline HTML directly (useful for simple widgets)remoteDom: Executes scripts with a dedicated root elementui://widget/...)window.openai.toolInputinjectionNew Package:
@mcp-lite/reactuseWidget<TProps, TState>(): Main hook for widget developmentuseWidgetProps<TProps>(): Direct access to tool input propsuseWidgetTheme(): Theme support (light/dark)useWidgetState<TState>(): Persistent widget state managementuseSyncExternalStoreExample Implementation
examples/react-widget/How to Test Manually
1. Start the MCP Server
cd examples/react-widget bun install bun run devThe server will start on http://localhost:3002/mcp
2. Start the Widget App (for externalUrl testing)
cd examples/react-widget/widget-app bun install bun run devThe widget app will start on http://localhost:5173/
3. Run Automated Tests
cd examples/react-widget bun run test:manualThis will test:
4. Manual Testing Steps
Test Tool Call
Expected: Returns a resource link like
ui://widget/weather-widget-{uuid}.html?props=...Test Resource Retrieval
# Replace {uuid} with actual UUID from tool call response curl http://localhost:3002/mcp/resource/ui%3A%2F%2Fwidget%2Fweather-widget-{uuid}.html%3Fprops%3D...Expected: Returns HTML with embedded iframe and props injection
Test in Browser
5. Verify React Integration
Open
examples/react-widget/widget-app/src/WeatherWidget.tsxto see:useWidget()hook usagecallTool()andsendFollowUpMessage()examplesTesting Documentation
See
examples/react-widget/TESTING.mdfor:Breaking Changes
None. This is a new experimental feature that doesn't affect existing APIs.
Notes
uiResource()API is marked as experimentalChecklist
Co-authored-by: @Rodriguespn