UI over MCP. Create next-gen UI experiences with the protocol and SDK!
5,146
stars
334
commits
TypeScript
primary language
Jul 8, 2026
updated
What's mcp-ui? β’ Core Concepts β’ Installation β’ Getting Started β’ Walkthrough β’ Examples β’ Supported Hosts β’ Security β’ Roadmap β’ Contributing β’ License
mcp-ui pioneered the concept of interactive UI over MCP, enabling rich web interfaces for AI tools. Alongside Apps SDK, the patterns developed here directly influenced the MCP Apps specification, which standardized UI delivery over the protocol.
The @mcp-ui/* packages implement the MCP Apps standard. @mcp-ui/client is the recommended SDK for MCP Apps Hosts.
The @mcp-ui/ packages are fully compliant with the MCP Apps specification and ready for production use.*
mcp-ui?mcp-ui is an SDK implementing the MCP Apps standard for UI over MCP. It provides:
@mcp-ui/server (TypeScript): Create UI resources with createUIResource. Works with registerAppTool and registerAppResource from @modelcontextprotocol/ext-apps/server.@mcp-ui/client (TypeScript): Render tool UIs with AppRenderer (MCP Apps) or UIResourceRenderer (legacy MCP-UI hosts).mcp_ui_server (Ruby): Create UI resources in Ruby.mcp-ui-server (Python): Create UI resources in Python.The MCP Apps pattern links tools to their UIs via _meta.ui.resourceUri. Hosts fetch and render the UI alongside tool results.
The MCP Apps standard links tools to their UIs via _meta.ui.resourceUri:
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
import { createUIResource } from '@mcp-ui/server';
// 1. Create UI resource
const widgetUI = await createUIResource({
uri: 'ui://my-server/widget',
content: { type: 'rawHtml', htmlString: '<h1>Widget</h1>' },
encoding: 'text',
});
// 2. Register resource handler
registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
contents: [widgetUI.resource]
}));
// 3. Register tool with _meta linking
registerAppTool(server, 'show_widget', {
description: 'Show widget',
inputSchema: { query: z.string() },
_meta: { ui: { resourceUri: widgetUI.resource.uri } } // Links tool β UI
}, async ({ query }) => {
return { content: [{ type: 'text', text: `Query: ${query}` }] };
});
Hosts detect _meta.ui.resourceUri, fetch the UI via resources/read, and render it with AppRenderer.
The underlying payload for UI content:
interface UIResource {
type: 'resource';
resource: {
uri: string; // e.g., ui://component/id
mimeType: 'text/html;profile=mcp-app';
text?: string; // HTML content
blob?: string; // Base64-encoded HTML content
};
}
uri: Unique identifier using ui:// schememimeType: text/html;profile=mcp-app β the MCP Apps standard MIME typetext vs. blob: Plain text or Base64-encoded contentFor MCP Apps hosts, use AppRenderer to render tool UIs:
import { AppRenderer } from '@mcp-ui/client';
function ToolUI({ client, toolName, toolInput, toolResult }) {
return (
<AppRenderer
client={client}
toolName={toolName}
sandbox={{ url: sandboxUrl }}
toolInput={toolInput}
toolResult={toolResult}
onOpenLink={async ({ url }) => window.open(url)}
onMessage={async (params) => console.log('Message:', params)}
/>
);
}
Key props:
client: Optional MCP client for automatic resource fetchingtoolName: Tool name to render UI forsandbox: Sandbox configuration with proxy URLtoolInput / toolResult: Tool arguments and resultsonOpenLink / onMessage: Handlers for UI requestsFor legacy hosts that embed resources in tool responses:
import { UIResourceRenderer } from '@mcp-ui/client';
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={(action) => console.log('Action:', action)}
/>
Props:
resource: Resource object with uri, mimeType, and content (text/blob)onUIAction: Callback for handling tool, prompt, link, notify, and intent actionsAlso available as a Web Component:
<ui-resource-renderer
resource='{ "mimeType": "text/html", "text": "<h2>Hello!</h2>" }'
></ui-resource-renderer>
text/html;profile=mcp-app)Rendered using the internal <HTMLResourceRenderer /> component, which displays content inside an <iframe>. This is suitable for self-contained HTML.
mimeType: text/html;profile=mcp-app (MCP Apps standard)UI snippets must be able to interact with the agent. In mcp-ui, this is done by hooking into events sent from the UI snippet and reacting to them in the host (see onUIAction prop). For example, an HTML may trigger a tool call when a button is clicked by sending an event which will be caught handled by the client.
MCP-UI SDKs includes adapter support for host-specific implementations, enabling your open MCP-UI widgets to work seamlessly regardless of host. Adapters automatically translate between MCP-UI's postMessage protocol and host-specific APIs. Over time, as hosts become compatible with the open spec, these adapters wouldn't be needed.
For Apps SDK environments (e.g., ChatGPT), this adapter translates MCP-UI protocol to Apps SDK API calls (e.g., window.openai).
How it Works:
postMessage calls from your widgetsUsage:
import { createUIResource } from '@mcp-ui/server';
const htmlResource = await createUIResource({
uri: 'ui://greeting/1',
content: {
type: 'rawHtml',
htmlString: `
<button onclick="window.parent.postMessage({ type: 'tool', payload: { toolName: 'myTool', params: {} } }, '*')">
Call Tool
</button>
`
},
encoding: 'text',
});
# using npm
npm install @mcp-ui/server @mcp-ui/client
# or pnpm
pnpm add @mcp-ui/server @mcp-ui/client
# or yarn
yarn add @mcp-ui/server @mcp-ui/client
gem install mcp_ui_server
# using pip
pip install mcp-ui-server
# or uv
uv add mcp-ui-server
You can use GitMCP to give your IDE access to mcp-ui's latest documentation!
Server-side: Create a tool with UI using _meta.ui.resourceUri
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
import { createUIResource } from '@mcp-ui/server';
import { z } from 'zod';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// Create UI resource
const widgetUI = await createUIResource({
uri: 'ui://my-server/widget',
content: { type: 'rawHtml', htmlString: '<h1>Interactive Widget</h1>' },
encoding: 'text',
});
// Register resource handler
registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
contents: [widgetUI.resource]
}));
// Register tool with _meta linking
registerAppTool(server, 'show_widget', {
description: 'Show widget',
inputSchema: { query: z.string() },
_meta: { ui: { resourceUri: widgetUI.resource.uri } }
}, async ({ query }) => {
return { content: [{ type: 'text', text: `Query: ${query}` }] };
});
Client-side: Render tool UIs with AppRenderer
import { AppRenderer } from '@mcp-ui/client';
function ToolUI({ client, toolName, toolInput, toolResult }) {
return (
<AppRenderer
client={client}
toolName={toolName}
sandbox={{ url: sandboxUrl }}
toolInput={toolInput}
toolResult={toolResult}
onOpenLink={async ({ url }) => window.open(url)}
onMessage={async (params) => console.log('Message:', params)}
/>
);
}
For hosts that don't support MCP Apps yet:
import { UIResourceRenderer } from '@mcp-ui/client';
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={(action) => console.log('Action:', action)}
/>
Server-side: Build your UI resources
from mcp_ui_server import create_ui_resource
# Inline HTML
html_resource = create_ui_resource({
"uri": "ui://greeting/1",
"content": { "type": "rawHtml", "htmlString": "<p>Hello, from Python!</p>" },
"encoding": "text",
})
# External URL
external_url_resource = create_ui_resource({
"uri": "ui://greeting/2",
"content": { "type": "externalUrl", "iframeUrl": "https://example.com" },
"encoding": "text",
})
Server-side: Build your UI resources
require 'mcp_ui_server'
# Inline HTML
html_resource = McpUiServer.create_ui_resource(
uri: 'ui://greeting/1',
content: { type: :raw_html, htmlString: '<p>Hello, from Ruby!</p>' },
encoding: :text
)
# External URL
external_url_resource = McpUiServer.create_ui_resource(
uri: 'ui://greeting/2',
content: { type: :external_url, iframeUrl: 'https://example.com' },
encoding: :text
)
# remote-dom
remote_dom_resource = McpUiServer.create_ui_resource(
uri: 'ui://remote-component/action-button',
content: {
type: :remote_dom,
script: "
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me from Ruby!');
button.addEventListener('press', () => {
window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'ruby-remote-dom' } } }, '*');
});
root.appendChild(button);
",
framework: :react,
},
encoding: :text
)
For a detailed, simple, step-by-step guide on how to integrate mcp-ui into your own server, check out the full server walkthroughs on the mcp-ui documentation site:
These guides will show you how to add a mcp-ui endpoint to an existing server, create tools that return UI resources, and test your setup with the ui-inspector!
Client Examples
mcp-ui.mcp-ui.mcp-ui-enabled servers.mcp-ui client. Check out the hosted version!examples/remote-dom-demo) - local demo app to test RemoteDOM resourcesexamples/wc-demo) - local demo app to test the Web Component integration in hostsServer Examples
typescript-server-demo: A simple Typescript server that demonstrates how to generate UI resources.https://remote-mcp-server-authless.idosalomon.workers.dev/mcphttps://remote-mcp-server-authless.idosalomon.workers.dev/ssemcp_ui_server and mcp gems together.mcp-ui-server Python package.mcp-ui starter example.Drop those URLs into any MCP-compatible host to see mcp-ui in action. For a supported local inspector, see the ui-inspector.
The @mcp-ui/* packages work with both MCP Apps hosts and legacy MCP-UI hosts.
These hosts implement the MCP Apps specification and support tools with _meta.ui.resourceUri:
These hosts expect UI resources embedded directly in tool responses:
| Host | Rendering | UI Actions | Notes |
|---|---|---|---|
| Nanobot | β | β | |
| MCPJam | β | β | |
| Postman | β | β οΈ | |
| Goose | β | β οΈ | |
| LibreChat | β | β οΈ | |
| Smithery | β | β | |
| fast-agent | β | β |
Legend: β Supported Β· β οΈ Partial Β· β Not yet supported
Host and user security is one of mcp-ui's primary concerns. In all content types, the remote code is executed in a sandboxed iframe.
mcp-ui is a project by Ido Salomon, in collaboration with Liad Yosef.
Contributions, ideas, and bug reports are welcome! See the contribution guidelines to get started.
Apache License 2.0 Β© The MCP-UI Authors
This project is provided "as is", without warranty of any kind. The mcp-ui authors and contributors shall not be held liable for any damages, losses, or issues arising from the use of this software. Use at your own risk.
TypeScript
70.3%
Python
19.1%
Ruby
6.2%
HTML
2.3%
JavaScript
2.0%
UI over MCP. Create next-gen UI experiences with the protocol and SDK!
5,146
stars
334
commits
TypeScript
primary language
Jul 8, 2026
updated
What's mcp-ui? β’ Core Concepts β’ Installation β’ Getting Started β’ Walkthrough β’ Examples β’ Supported Hosts β’ Security β’ Roadmap β’ Contributing β’ License
mcp-ui pioneered the concept of interactive UI over MCP, enabling rich web interfaces for AI tools. Alongside Apps SDK, the patterns developed here directly influenced the MCP Apps specification, which standardized UI delivery over the protocol.
The @mcp-ui/* packages implement the MCP Apps standard. @mcp-ui/client is the recommended SDK for MCP Apps Hosts.
The @mcp-ui/ packages are fully compliant with the MCP Apps specification and ready for production use.*
mcp-ui?mcp-ui is an SDK implementing the MCP Apps standard for UI over MCP. It provides:
@mcp-ui/server (TypeScript): Create UI resources with createUIResource. Works with registerAppTool and registerAppResource from @modelcontextprotocol/ext-apps/server.@mcp-ui/client (TypeScript): Render tool UIs with AppRenderer (MCP Apps) or UIResourceRenderer (legacy MCP-UI hosts).mcp_ui_server (Ruby): Create UI resources in Ruby.mcp-ui-server (Python): Create UI resources in Python.The MCP Apps pattern links tools to their UIs via _meta.ui.resourceUri. Hosts fetch and render the UI alongside tool results.
The MCP Apps standard links tools to their UIs via _meta.ui.resourceUri:
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
import { createUIResource } from '@mcp-ui/server';
// 1. Create UI resource
const widgetUI = await createUIResource({
uri: 'ui://my-server/widget',
content: { type: 'rawHtml', htmlString: '<h1>Widget</h1>' },
encoding: 'text',
});
// 2. Register resource handler
registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
contents: [widgetUI.resource]
}));
// 3. Register tool with _meta linking
registerAppTool(server, 'show_widget', {
description: 'Show widget',
inputSchema: { query: z.string() },
_meta: { ui: { resourceUri: widgetUI.resource.uri } } // Links tool β UI
}, async ({ query }) => {
return { content: [{ type: 'text', text: `Query: ${query}` }] };
});
Hosts detect _meta.ui.resourceUri, fetch the UI via resources/read, and render it with AppRenderer.
The underlying payload for UI content:
interface UIResource {
type: 'resource';
resource: {
uri: string; // e.g., ui://component/id
mimeType: 'text/html;profile=mcp-app';
text?: string; // HTML content
blob?: string; // Base64-encoded HTML content
};
}
uri: Unique identifier using ui:// schememimeType: text/html;profile=mcp-app β the MCP Apps standard MIME typetext vs. blob: Plain text or Base64-encoded contentFor MCP Apps hosts, use AppRenderer to render tool UIs:
import { AppRenderer } from '@mcp-ui/client';
function ToolUI({ client, toolName, toolInput, toolResult }) {
return (
<AppRenderer
client={client}
toolName={toolName}
sandbox={{ url: sandboxUrl }}
toolInput={toolInput}
toolResult={toolResult}
onOpenLink={async ({ url }) => window.open(url)}
onMessage={async (params) => console.log('Message:', params)}
/>
);
}
Key props:
client: Optional MCP client for automatic resource fetchingtoolName: Tool name to render UI forsandbox: Sandbox configuration with proxy URLtoolInput / toolResult: Tool arguments and resultsonOpenLink / onMessage: Handlers for UI requestsFor legacy hosts that embed resources in tool responses:
import { UIResourceRenderer } from '@mcp-ui/client';
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={(action) => console.log('Action:', action)}
/>
Props:
resource: Resource object with uri, mimeType, and content (text/blob)onUIAction: Callback for handling tool, prompt, link, notify, and intent actionsAlso available as a Web Component:
<ui-resource-renderer
resource='{ "mimeType": "text/html", "text": "<h2>Hello!</h2>" }'
></ui-resource-renderer>
text/html;profile=mcp-app)Rendered using the internal <HTMLResourceRenderer /> component, which displays content inside an <iframe>. This is suitable for self-contained HTML.
mimeType: text/html;profile=mcp-app (MCP Apps standard)UI snippets must be able to interact with the agent. In mcp-ui, this is done by hooking into events sent from the UI snippet and reacting to them in the host (see onUIAction prop). For example, an HTML may trigger a tool call when a button is clicked by sending an event which will be caught handled by the client.
MCP-UI SDKs includes adapter support for host-specific implementations, enabling your open MCP-UI widgets to work seamlessly regardless of host. Adapters automatically translate between MCP-UI's postMessage protocol and host-specific APIs. Over time, as hosts become compatible with the open spec, these adapters wouldn't be needed.
For Apps SDK environments (e.g., ChatGPT), this adapter translates MCP-UI protocol to Apps SDK API calls (e.g., window.openai).
How it Works:
postMessage calls from your widgetsUsage:
import { createUIResource } from '@mcp-ui/server';
const htmlResource = await createUIResource({
uri: 'ui://greeting/1',
content: {
type: 'rawHtml',
htmlString: `
<button onclick="window.parent.postMessage({ type: 'tool', payload: { toolName: 'myTool', params: {} } }, '*')">
Call Tool
</button>
`
},
encoding: 'text',
});
# using npm
npm install @mcp-ui/server @mcp-ui/client
# or pnpm
pnpm add @mcp-ui/server @mcp-ui/client
# or yarn
yarn add @mcp-ui/server @mcp-ui/client
gem install mcp_ui_server
# using pip
pip install mcp-ui-server
# or uv
uv add mcp-ui-server
You can use GitMCP to give your IDE access to mcp-ui's latest documentation!
Server-side: Create a tool with UI using _meta.ui.resourceUri
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerAppTool, registerAppResource } from '@modelcontextprotocol/ext-apps/server';
import { createUIResource } from '@mcp-ui/server';
import { z } from 'zod';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// Create UI resource
const widgetUI = await createUIResource({
uri: 'ui://my-server/widget',
content: { type: 'rawHtml', htmlString: '<h1>Interactive Widget</h1>' },
encoding: 'text',
});
// Register resource handler
registerAppResource(server, 'widget_ui', widgetUI.resource.uri, {}, async () => ({
contents: [widgetUI.resource]
}));
// Register tool with _meta linking
registerAppTool(server, 'show_widget', {
description: 'Show widget',
inputSchema: { query: z.string() },
_meta: { ui: { resourceUri: widgetUI.resource.uri } }
}, async ({ query }) => {
return { content: [{ type: 'text', text: `Query: ${query}` }] };
});
Client-side: Render tool UIs with AppRenderer
import { AppRenderer } from '@mcp-ui/client';
function ToolUI({ client, toolName, toolInput, toolResult }) {
return (
<AppRenderer
client={client}
toolName={toolName}
sandbox={{ url: sandboxUrl }}
toolInput={toolInput}
toolResult={toolResult}
onOpenLink={async ({ url }) => window.open(url)}
onMessage={async (params) => console.log('Message:', params)}
/>
);
}
For hosts that don't support MCP Apps yet:
import { UIResourceRenderer } from '@mcp-ui/client';
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={(action) => console.log('Action:', action)}
/>
Server-side: Build your UI resources
from mcp_ui_server import create_ui_resource
# Inline HTML
html_resource = create_ui_resource({
"uri": "ui://greeting/1",
"content": { "type": "rawHtml", "htmlString": "<p>Hello, from Python!</p>" },
"encoding": "text",
})
# External URL
external_url_resource = create_ui_resource({
"uri": "ui://greeting/2",
"content": { "type": "externalUrl", "iframeUrl": "https://example.com" },
"encoding": "text",
})
Server-side: Build your UI resources
require 'mcp_ui_server'
# Inline HTML
html_resource = McpUiServer.create_ui_resource(
uri: 'ui://greeting/1',
content: { type: :raw_html, htmlString: '<p>Hello, from Ruby!</p>' },
encoding: :text
)
# External URL
external_url_resource = McpUiServer.create_ui_resource(
uri: 'ui://greeting/2',
content: { type: :external_url, iframeUrl: 'https://example.com' },
encoding: :text
)
# remote-dom
remote_dom_resource = McpUiServer.create_ui_resource(
uri: 'ui://remote-component/action-button',
content: {
type: :remote_dom,
script: "
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me from Ruby!');
button.addEventListener('press', () => {
window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'ruby-remote-dom' } } }, '*');
});
root.appendChild(button);
",
framework: :react,
},
encoding: :text
)
For a detailed, simple, step-by-step guide on how to integrate mcp-ui into your own server, check out the full server walkthroughs on the mcp-ui documentation site:
These guides will show you how to add a mcp-ui endpoint to an existing server, create tools that return UI resources, and test your setup with the ui-inspector!
Client Examples
mcp-ui.mcp-ui.mcp-ui-enabled servers.mcp-ui client. Check out the hosted version!examples/remote-dom-demo) - local demo app to test RemoteDOM resourcesexamples/wc-demo) - local demo app to test the Web Component integration in hostsServer Examples
typescript-server-demo: A simple Typescript server that demonstrates how to generate UI resources.https://remote-mcp-server-authless.idosalomon.workers.dev/mcphttps://remote-mcp-server-authless.idosalomon.workers.dev/ssemcp_ui_server and mcp gems together.mcp-ui-server Python package.mcp-ui starter example.Drop those URLs into any MCP-compatible host to see mcp-ui in action. For a supported local inspector, see the ui-inspector.
The @mcp-ui/* packages work with both MCP Apps hosts and legacy MCP-UI hosts.
These hosts implement the MCP Apps specification and support tools with _meta.ui.resourceUri:
These hosts expect UI resources embedded directly in tool responses:
| Host | Rendering | UI Actions | Notes |
|---|---|---|---|
| Nanobot | β | β | |
| MCPJam | β | β | |
| Postman | β | β οΈ | |
| Goose | β | β οΈ | |
| LibreChat | β | β οΈ | |
| Smithery | β | β | |
| fast-agent | β | β |
Legend: β Supported Β· β οΈ Partial Β· β Not yet supported
Host and user security is one of mcp-ui's primary concerns. In all content types, the remote code is executed in a sandboxed iframe.
mcp-ui is a project by Ido Salomon, in collaboration with Liad Yosef.
Contributions, ideas, and bug reports are welcome! See the contribution guidelines to get started.
Apache License 2.0 Β© The MCP-UI Authors
This project is provided "as is", without warranty of any kind. The mcp-ui authors and contributors shall not be held liable for any damages, losses, or issues arising from the use of this software. Use at your own risk.
TypeScript
70.3%
Python
19.1%
Ruby
6.2%
HTML
2.3%
JavaScript
2.0%