MCP Apps
MCP Apps are interactive HTML panels that the AI agent can display inside a host application as part of a conversation. Instead of responding with plain text, a tool call can return a live UI — a chart, a form, a workspace builder, a preview panel — that you interact with directly inside the chat or in a dedicated workspace window.
Interactive AI Responses
MCP Apps turn a tool result into a live UI component, not just a text answer.
When an AI assistant needs to show you something you can act on — a workspace layout to review, a form to fill in, a data panel to explore — MCP Apps give it a surface to do that inside the same conversation.
Which Package Does What
Three packages in io.Intelligence each play a distinct role in making MCP Apps work end-to-end. Understanding the split makes the rest of this page easier to follow.
| Package | Role in MCP Apps |
|---|---|
@interopio/mcp-core | Server side. Marks tools as MCP App tools by attaching a _meta.ui.resourceUri to the tool definition. Ships the built-in Workspace Widget as a system tool. Registers UI tools only for clients that advertise the required capability, so non-UI clients are unaffected. |
@interopio/ai-web | Client / host side. Acts as the MCP host in the browser. Detects MCP App tools in a run stream, manages the full app lifecycle (create, display, state persistence, close), and exposes the mcpApps API. Any custom chat UI built on AI Web can host MCP Apps. |
@interopio/io-assist-ng | Ready-made UI layer. Built on AI Web. Renders MCP Apps inline in the chat thread and ships the sandboxed proxy page required for that mode. Displays a modal when an existing app is detected in workspace mode (replace / new instance choice). Closes apps automatically when the user switches away from a thread. Workspace window creation and lifecycle are handled by AI Web underneath. |
The sections below describe the protocol and behaviour shared across all three.
How MCP Apps Work
A standard MCP tool returns text. An MCP App tool goes one step further: it also points to an HTML resource that the host application loads inside a sandboxed frame. That resource is the app.
The host and the app communicate in real time using a bidirectional message protocol. The app can:
- receive the tool call inputs and results from the host
- call other MCP tools on the user's behalf
- send a chat message back into the conversation
- signal to the host that it is finished and should be closed
- save and restore its own state across thread switches
No JavaScript library is required in the app itself. Communication happens over a standard window.postMessage interface (inline) or io.Connect interop (workspace mode).
App Lifecycle
- AI calls a tool — the assistant decides to use a tool that has a UI resource attached.
- App appears — the host creates the sandboxed app. In inline mode it renders inside the chat; in workspace mode a new window opens.
- Inputs and results are delivered — the host sends the tool call arguments and result to the app so it can populate its own UI.
- User interacts — the app is fully interactive throughout. It can call other MCP tools, read resources, and post messages back to the conversation.
- Response lock — while the AI is generating a reply, the app receives a notification and can temporarily disable interactive controls to prevent conflicts.
- App finishes — when the task is complete, the app posts a final chat message and signals the host to close it. In workspace mode the window is removed; inline apps stay visible in the chat history.
Thread Switching
When you switch to a different conversation thread, all currently open MCP Apps are closed. When you switch back to a thread that had active apps, those apps are automatically restored to their last saved state.
How Apps Appear
Depending on the environment and host configuration, an MCP App renders in one of two ways:
- Inline — the app appears inside the chat thread, below the tool-call message. Active when io.Connect workspaces are not available or the host is configured for inline-only rendering.
- Workspace — the app opens in a dedicated io.Connect window alongside the chat. Active automatically when the host is running inside an io.Connect workspace. Multiple apps can be open simultaneously as separate windows.
Duplicate App Handling
If the AI calls the same tool again while an instance is already open (workspace mode), the host can present a choice:
| Option | Result |
|---|---|
| Replace Oldest | Closes the oldest running instance and opens the new one in its window |
| Replace All | Closes all instances of that tool and opens a single new one |
| New Instance | Keeps all existing instances open and opens an additional window |
Chat UI Support
io.Intelligence implements the MCP Apps standard. For a full description of the standard, client and server SDKs, and the broader ecosystem, visit mcpui.dev.
Any MCP-compatible chat application can host MCP Apps as long as it:
- advertises the
io.modelcontextprotocol/uicapability withtext/html;profile=mcp-appMIME type - serves a sandbox proxy page that creates an isolated inner frame for the app HTML
- routes messages bidirectionally between the host and the app
io.Assist: Full MCP Apps Experience
io.Assist implements the standard and additionally ships an extended notification layer on top of it to enable richer host–app interactions out of the box.
This additional layer, which any chat UI can implement independently, provides:
- Response-generation status — apps know when the AI is actively generating and can disable controls accordingly
- State save and restore — apps can persist their state and have it automatically reloaded after a thread switch
- Programmatic close — apps can signal to the host that they are finished without the user manually closing the window
The extended notifications are fully documented and require no proprietary SDK. See Custom Notifications below for the specification.
The standard MCP App protocol (ui/initialize, ui/call-tool, ui/message, and the standard notifications) works the same in io.Assist and in any other compliant host. The extended notifications listed here are additive — apps that do not use them continue to work normally.
Custom Notifications
The io.Intelligence MCP Apps layer defines a small set of notifications on top of the standard MCP UI protocol. They are sent between the host application and the embedded app HTML.
Host → App Notifications
| Method | Params | When | Purpose |
|---|---|---|---|
ui/notifications/response-generation-status | { isPending: boolean } | Every time the AI starts or stops generating | App can disable interactive controls while the AI is working |
ui/notifications/load-state | { state: any } | After the app initializes, if a saved state exists | App restores a previously persisted UI state |
ui/notifications/save-state-response | { success: boolean, message?: string } | After the host processes a save-state request | Confirms whether the write succeeded |
App → Host Notifications
| Method | Params | When | Purpose |
|---|---|---|---|
ui/notifications/save-state | { state: any, toolCallId: string } | Whenever the app wants to checkpoint its state | Host writes to io Preferences |
ui/notifications/close-ws-app | {} | App has finished its job | Host marks job done and, in workspace mode, closes the window |
Implementing Custom Notifications in a Third-Party Chat
// Inside your MCP App HTML
window.addEventListener('message', (event) => {
const msg = event.data;
if (!msg?.jsonrpc) return;
if (!msg.method) return;
switch (msg.method) {
case 'ui/notifications/response-generation-status':
// Disable or enable controls based on msg.params.isPending
document.getElementById('myButton').disabled = msg.params.isPending;
break;
case 'ui/notifications/load-state':
// Restore UI from msg.params.state
if (msg.params?.state) restoreMyState(msg.params.state);
break;
case 'ui/notifications/save-state-response':
if (!msg.params?.success) console.error('State save failed');
break;
}
});
// To save state:
function saveMyState() {
window.parent.postMessage({
jsonrpc: '2.0',
method: 'ui/notifications/save-state',
params: { state: captureMyState(), toolCallId: myToolCallId },
}, '*');
}
// To close the app when done:
function finish() {
window.parent.postMessage({
jsonrpc: '2.0',
method: 'ui/notifications/close-ws-app',
params: {},
}, '*');
}
The standard MCP ui/initialize, ui/call-tool, ui/message, ui/notifications/tool-input, and ui/notifications/tool-result messages work the same way regardless of which chat UI hosts the app. The custom notifications above are additive — apps that do not need them simply ignore them.
Built-In MCP App: Workspace Widget
io.Intelligence ships one built-in MCP App as a system tool: the Workspace Widget.
The Workspace Widget is a full-featured interactive component that lets users visualize and build io.Connect workspace layouts through conversation. It supports two modes:
- Previewer — displays an existing workspace layout in a read-only view before the user restores or launches it
- Builder — provides an interactive drag-and-drop canvas for creating a new workspace from scratch
→ See Workspace Widget for a complete explanation of both modes, the interaction workflow, and all supported notifications.
Setup for Developers
To enable MCP Apps in a custom host built on @interopio/ai-web, pass the mcpApps config block and advertise the UI extension capability:
const aiWeb = await IoAiWebFactory(io, {
agentServer: { baseUrl: 'http://localhost:4111' },
mcp: {
clientsConfig: {
capabilities: {
extensions: {
'io.modelcontextprotocol/ui': {
mimeTypes: ['text/html;profile=mcp-app'],
},
},
},
},
mcpApps: {
sandboxProxyUrl: '/sandbox-proxy.html',
displayMode: 'workspace', // or 'inline', or omit for auto-detection
},
},
});
The sandboxProxyUrl must point to the static proxy page that ships with @interopio/io-assist-ng or your own equivalent. The proxy creates the inner sandboxed iframe and routes messages between host and app.
For the complete API reference, see AI Web Configuration and AI Web API Reference.
For a detailed explanation of what the sandbox proxy must implement — including the workspace transport, the startup handshake, and a ready-made proxy you can use directly — see The Sandbox Proxy.
If your custom chat is React-based, the AppRenderer component from @mcp-ui/client handles MCP App resource fetching and render lifecycle for you. You still need to supply the sandbox proxy URL. See The Sandbox Proxy for the ready-made proxy and the custom-build guide.
io.Connect Desktop: Enabling the In-Memory App Store
When running in workspace mode on io.Connect Desktop, MCP Apps open as first-class io.Connect windows alongside the chat. For this to work, io.Connect Desktop must be able to register the app at runtime — MCP Apps are declared dynamically by the MCP server, not pre-configured in a static app store.
To support this, enable the in-memory app store in your io.Connect Desktop system.json:
{
"appStores": [
{
"type": "in-memory",
"details": {
"allowedApps": ["my-mcp-app", "my-other-mcp-app"]
}
}
]
}
| Property | Type | Description |
|---|---|---|
type | string | Must be "in-memory". |
details.allowedApps | string[] | App names allowed to register themselves via the in-memory store. Only apps whose names appear in this list can be dynamically registered at runtime. |
MCP Apps require the in-memory app store, which was introduced in io.Connect Desktop 10.0. Versions below 10.0 are not supported. To use MCP Apps on an unsupported version you must explicitly configure displayMode: 'inline' in the mcpApps config — workspace mode will not work.
Next Steps
- Workspace Widget — modes, workflow, and notification reference
- MCP Capabilities — broader picture of what io.Intelligence MCP provides
- io.Assist Capabilities — how MCP Apps appear in the ready-made Angular chat UI
- AI Web Configuration —
mcpAppsconfig type reference - Tool Types Reference — server-side tool definition and workspace widget configuration