Replies: 2 comments
-
See https://mcpui.dev/ for the most developed work on this front I've seen to date There is also a #ui-wg channel in the MCP Contributor Discord |
Beta Was this translation helpful? Give feedback.
-
Hi MCP UI team! We’ve been independently working on very similar ideas (we call our version Fractal https://www.fractalmcp.com) and we are so excited that other teams are also thinking about delivering UI via MCP! We’d love for a single standard to emerge so we can all make our client libraries interoperable (so whether a company uses MCP UI, Fractal, or someone else’s server library, it all works on the consumption side). Below we review (a) your existing protocol spec (and suggest other teams align to it), (b) suggest an expansion focused on making the components themselves act like MCP servers to enable interactivity with agents. Here’s what we see right now as far as protocol specs go: UI tool responsesWe love what MCP UI has done with the interface UIResource {
type: 'resource';
resource: {
uri: string; // ui://component/id
mimeType: 'text/html' | 'text/uri-list' | 'application/vnd.mcp-ui.remote-dom';
// text/html: inline HTML
// text/uri-list: URL(s)
// application/vnd.mcp-ui.remote-dom: JS runtime for remote DOM
_metatada: {[key:string]: string},
text?: string; // Inline text (e.g., HTML or URL)
blob?: string; // Base64-encoded payload (e.g., HTML bundle or URL list)
};
} Communication protocol between iframe and parentWe are aligned with MCP UI that there should be standardization on the communication protocol between the UI components inside the iframe and the parent component that appears in a chat. We think this is important because it will allow client libraries to support key features like resizing, user intents, and beyond. We would like to propose that communication should be two-way, flowing between the iframed component and the parent rendering it. Current format (iframe to parent)Message IDs correlate requests with their responses. type IframeToParent =
| { type: 'tool'; payload: { toolName: string; params: Record<string, unknown> }; messageId?: string }
| { type: 'intent'; payload: { intent: string; params: Record<string, unknown> }; messageId?: string }
| { type: 'prompt'; payload: { prompt: string }; messageId?: string }
| { type: 'notify'; payload: { message: string }; messageId?: string }
| { type: 'link'; payload: { url: string }; messageId?: string }; Currently these response events are handled inside the iframe:
Proposed extension: parent to iframe RPCMotivationWe propose extending the spec so an agent can query a minimal DOM representation inside the iframe and drive interactions through a lightweight RPC layer. Components can declare whether they support this mode. Since most actions from parent to iframe will be done by the AI agent, we propose exposing functionality just like an MCP server would: via introspection and tool calls. Use cases
Approach
1) Standardize the iframe message response formatWe propose standardizing the response envelope as follows: type UIMessageResponse<T = unknown> = {
messageId: string;
response?: T;
error?: string;
}; This pattern is hinted at in the MCP UI documentation but not spelled out explicitly. 2) Extend messaging protocol to go both waysAllow the parent to initiate a narrow set of built-in declarative actions. Use the same type QueryDomResult = string; // minimal HTML snapshot
type ClickResult = { success: boolean };
type EnterTextResult = { success: boolean };
type ParentToIframe =
| { type: 'queryDom'; payload: {}; messageId: string }
| { type: 'click'; payload: { elementId: string }; messageId: string }
| { type: 'enterText'; payload: { elementId: string; text: string }; messageId: string };
type ParentToIframeResponse =
| UIMessageResponse<QueryDomResult>
| UIMessageResponse<ClickResult>
| UIMessageResponse<EnterTextResult>; These primitives should be enough to enable any interaction you might want your agent to take. We believe there’s a benefit of taking this a step further:3) Components as MCP serversWe believe that allowing components themselves to behave as MCP servers is the simplest and most flexible way to enable agent-component interactions. To this end, we propose adding support for the MCP-standard Motivation
Example sketch // We borrow some types from MCP!
import type { Tool, ToolsListResult, CallToolResult, Content } from '@modelcontextprotocol/sdk';
// Extend the ParentToIframe union with MCP tools support
type ParentToIframe =
| { type: 'queryDom'; payload: {}; messageId: string }
| { type: 'click'; payload: { elementId: string }; messageId: string }
| { type: 'enterText'; payload: { elementId: string; text: string }; messageId: string }
| { type: 'tools/list'; payload: { cursor?: string }; messageId: string }
| { type: 'tools/call'; payload: { name: string; arguments?: Record<string, unknown> }; messageId: string };
// ParentToIframeResponse stays consistent
type ParentToIframeResponse =
| UIMessageResponse<QueryDomResult>
| UIMessageResponse<ClickResult>
| UIMessageResponse<EnterTextResult>
| UIMessageResponse<ToolsListResult>
| UIMessageResponse<CallToolResult>; Putting it all togetherWhat exists today
What we propose to add or change
Final consolidated TypeScript sketchimport type {
ToolsListResult,
CallToolResult,
} from '@modelcontextprotocol/sdk';
// ---------- UIResource ----------
interface UIResource {
type: 'resource';
resource: {
uri: string; // ui://component/id
mimeType: 'text/html' | 'application/vnd.mcp-ui.remote-dom' | 'text/uri-list';
metadata?: { [key: string]: string };
text?: string;
blob?: string; // base64-encoded bundle; preferred
};
}
// ---------- UI Message Envelope ----------
interface UIMessage<TType extends string, TPayload> {
type: TType;
payload: TPayload;
messageId?: string; // required if a response is expected
}
type UIMessageResponse<T = unknown> = {
messageId: string;
response?: T;
error?: string;
};
// ---------- Iframe to Parent ----------
type IframeToParent =
| UIMessage<'tool', { toolName: string; params: Record<string, unknown> }>
| UIMessage<'intent', { intent: string; params: Record<string, unknown> }>
| UIMessage<'prompt', { prompt: string }>
| UIMessage<'notify', { message: string }>
| UIMessage<'link', { url: string }>;
// ---------- Parent to Iframe (RPC) ----------
type QueryDomResult = string;
type ClickResult = { success: boolean };
type EnterTextResult = { success: boolean };
type ParentToIframe =
| UIMessage<'queryDom', {}>
| UIMessage<'click', { elementId: string }>
| UIMessage<'enterText', { elementId: string; text: string }>
| UIMessage<'tools/list', { cursor?: string }>
| UIMessage<'tools/call', { name: string; arguments?: Record<string, unknown> }>;
type ParentToIframeResponse =
| UIMessageResponse<QueryDomResult>
| UIMessageResponse<ClickResult>
| UIMessageResponse<EnterTextResult>
| UIMessageResponse<ToolsListResult>
| UIMessageResponse<CallToolResult>; |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Pre-submission Checklist
Your Idea
Current MCP definitions focus on tools and semantics, but lack a standard way to define dynamic, agent-driven UIs or workflows. We need a UI protocol for composable rendering and user interaction. Introduce a UI-oriented extension to MCP called MCP.UI, where a tool not only returns data or performs actions, but can also declare how to render a UI, bind interactions, and wire actions.
It’s like giving agents a way to speak React (or JSX) — declarative, composable, and reactive — but abstracted through schema.
Use Cases:
Agent returns a form to create a CRM record, pre-filled from context, with on-submit invoking an MCP tool.
Agent renders a dynamic dashboard UI, with filters tied to ui:bind and click actions invoking a read_data tool.
Agent sends a confirmation modal with Approve/Reject buttons wired to specific tools.
Declarative step-by-step wizards, where each step's UI is returned by the agent, contextually.
e.g.
{
"tool": "create_contact_form",
"ui:type": "form",
"ui:elements": [
{ "type": "text", "label": "Name", "bind": "contact.name" },
{ "type": "email", "label": "Email", "bind": "contact.email" },
{ "type": "submit", "label": "Save", "action": "create_contact" }
],
"ui:bind": {
"contact.name": "{{ user.name }}",
"contact.email": "{{ user.email }}"
},
"ui:actions": {
"create_contact": {
"tool": "create_contact",
"inputs": {
"name": "{{ contact.name }}",
"email": "{{ contact.email }}"
}
}
}
}
Scope
Beta Was this translation helpful? Give feedback.
All reactions