MCPNEW
McpAppFrame
MCP Apps (SEP-1865) host renderer: sandboxed iframe + JSON-RPC bridge.
Available in
reactangularvuePreview
Live previewreal component
Install
TerminalBash
npx mcp-elements add mcp-app-frameUsage
mcp-app-frame-example.tsxTSX
import { McpAppFrame } from '@mcp-elements/react'
import { APP_RESOURCE_MIME_TYPE } from '@mcp-elements/core'
// 1. Your MCP client reads the tool's declared ui:// resource
const { contents } = await client.readResource({ uri: toolMeta.ui.resourceUri })
const resource = contents[0] // mimeType === APP_RESOURCE_MIME_TYPE
// 2. Render it — the frame answers ui/initialize, injects the spec CSP,
// proxies tools/call back to your client, and streams tool input/results
export function Example({ toolArgs, result }) {
return (
<McpAppFrame
html={resource.text}
resourceMeta={resource._meta?.ui}
hostContext={{ theme: 'dark' }}
callTool={(name, args) => client.callTool({ name, arguments: args })}
toolInput={toolArgs}
toolResult={result}
openLink={(url) => window.open(url, '_blank', 'noopener')}
/>
)
}mcp-app-frame.component.tsTS
// Note: the Angular adapter currently ships the pre-SEP envelope bridge.
// For full SEP-1865 hosting (JSON-RPC handshake, tool proxying, CSP), wire
// createAppHost from @mcp-elements/core directly — the React adapter shows the shape.
import { Component } from '@angular/core'
import { McpeMcpAppFrameComponent } from '@mcp-elements/angular'
import type { AppMessageEnvelope } from '@mcp-elements/core'
@Component({
selector: 'app-app-frame-example',
standalone: true,
imports: [McpeMcpAppFrameComponent],
template: `<mcpe-mcp-app-frame src="https://my-mcp-app.example.com" [height]="600" (onMessage)="onMessage($event)" />`,
})
export class AppFrameExampleComponent {
onMessage(env: AppMessageEnvelope) {
console.log('app message', env)
}
}mcp-app-frame.vueVue
<script setup lang="ts">
// Note: the Vue adapter currently ships the pre-SEP envelope bridge.
// For full SEP-1865 hosting (JSON-RPC handshake, tool proxying, CSP), wire
// createAppHost from @mcp-elements/core directly — the React adapter shows the shape.
import { McpeMcpAppFrame } from '@mcp-elements/vue'
import type { AppMessageEnvelope } from '@mcp-elements/core'
function onMessage(env: AppMessageEnvelope) {
console.log('app message', env)
}
</script>
<template>
<McpeMcpAppFrame src="https://my-mcp-app.example.com" :height="600" @message="onMessage" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
html | string | — | Raw HTML of the ui:// resource (from resources/read). Rendered via srcdoc with the spec CSP injected |
src | string | — | URL to load instead of raw HTML (dedicated-origin or dev setups) |
resourceMeta | UiResourceMeta | — | The resource's _meta.ui — drives CSP injection, iframe permissions, and border preference |
callTool | (name, args) => Promise<CallToolResult> | — | Proxies tools/call requests from the app to your MCP client |
readResource | (uri: string) => Promise<unknown> | — | Proxies resources/read requests from the app to your MCP client |
openLink | (url: string) => void | Promise<void> | — | Handles ui/open-link requests from the app |
toolInput | Record<string, unknown> | — | Complete tool arguments; delivered as ui/notifications/tool-input after the handshake |
toolResult | CallToolResult | — | Tool result; delivered as ui/notifications/tool-result after the handshake |
hostContext | HostContext | — | UI context shared during ui/initialize (theme, containerDimensions, locale...) |
onInitialized | (appCapabilities?) => void | — | Called when the ui/initialize handshake completes |
autoResize | boolean | true | Grow the iframe to match ui/notifications/size-changed from the app |
height | number | 480 | Initial/fallback height in pixels |
sandbox | string | 'allow-scripts allow-forms' | iframe sandbox flags (keep allow-same-origin out for srcdoc content) |
className | string | — | Additional CSS classes |