How to Configure MCP Apps with Inline iframe Rendering in Agent-Native
To configure MCP Apps with inline iframe rendering in Agent-Native, mount the MCP server plugin with mountMCP, expose an iframeTitle property in each action definition, and detect the embed surface on the client side using the ?embedded query flag to handle sandbox restrictions.
Agent-Native ships with a built-in MCP (Multi-Chat-Partner) runtime that allows template apps to load as embedded widgets inside external hosts like ChatGPT or Claude. This guide explains the three-step configuration process required to enable inline iframe rendering, referencing the official implementation in the BuilderIO/agent-native repository.
Mount the MCP Server Plugin
The first step to enable inline rendering is registering your app as an MCP provider. In the server initialization code, import mountMCP from @agent-native/core/mcp and invoke it with your app metadata and action definitions.
In templates/plan/server/plugins/00-mcp.ts, the Plan template registers itself as follows:
import { mountMCP } from "@agent-native/core/mcp";
import { loadActionsFromStaticRegistry } from "@agent-native/core/server";
import actionsRegistry from "../../.generated/actions-registry.js";
export default function planMcpPlugin(nitroApp: any) {
const actions = {
...loadActionsFromStaticRegistry(actionsRegistry),
// optional automation tool entries
};
mountMCP(nitroApp, {
name: "Plan",
title: "Agent-Native Plan",
appId: "plan",
description:
"Create, review, update, publish, and export Agent-Native visual plans.",
websiteUrl: "https://plan.agent-native.com",
actions,
productionActions: actions,
connectorCatalog: PLAN_CONNECTOR_CATALOG,
});
}
The mountMCP function tells the Nitro server that this template is an MCP-enabled app. The actions object defines the public surface that the host may invoke, while the title property provides the default app-level title.
Expose iframeTitle Metadata for Inline Actions
For an action to render inside an inline iframe, it must export an iframeTitle property. This metadata tells the MCP host how to label the sandboxed container when displaying your UI widget.
In templates/plan/actions/get-visual-plan.ts, the action definition includes the required field:
export const getVisualPlan = defineAction({
// tool definition and parameters
iframeTitle: "Agent-Native Plan",
// optional: iframeWidth and iframeHeight for size hints
run: async (args) => {
// Generate HTML or MDX content
return {
html: `<div>Plan content here</div>`
};
},
});
The iframeTitle string is not the URL or HTML body—it serves as the accessible title for the iframe element. The host automatically creates a sandboxed <iframe title="Agent-Native Plan"> with appropriate security attributes when this action is invoked.
Other actions follow the same pattern, including create-visual-plan.ts, list-visual-plans.ts, and templates like design/actions/show-design-questions.ts.
Detect the Embed Surface on the Client
When an MCP host loads your app inline, it appends ?embedded=1 or ?embedded=true to the URL. Your client-side code must detect this flag to handle asset restrictions imposed by the host's COEP and CORP policies.
The Calendar template provides a helper function in templates/calendar/app/lib/mcp-embed.ts:
export function isMcpEmbedSurface(): boolean {
if (typeof window === "undefined") return false;
const value = new URLSearchParams(window.location.search).get("embedded");
return value === "1" || value === "true";
}
Use this helper to switch between external and same-origin assets:
import { isMcpEmbedSurface } from "@/app/lib/mcp-embed";
export default function Page() {
const avatarUrl = isMcpEmbedSurface()
? "/assets/placeholder-avatar.png"
: user.avatarUrl;
return <img src={avatarUrl} alt="avatar" />;
}
Equivalent helpers exist in the Mail, Design, and Slides templates under templates/*/app/lib/mcp-embed.ts.
Complete Minimal Example
Here is a complete configuration that combines server registration, action definition, and client-side adaptation:
// server/plugins/mcp.ts
import { mountMCP } from "@agent-native/core/mcp";
import { loadActionsFromStaticRegistry } from "@agent-native/core/server";
import actionsRegistry from "../../.generated/actions-registry.js";
export default function myAppPlugin(app: any) {
const actions = loadActionsFromStaticRegistry(actionsRegistry);
mountMCP(app, {
name: "MyApp",
title: "My Inline MCP",
appId: "myapp",
description: "Demo MCP app with inline iframe support.",
websiteUrl: "https://myapp.example.com",
actions,
productionActions: actions,
connectorCatalog: [],
});
}
// actions/show-widget.ts
export const showWidget = defineAction({
iframeTitle: "My Inline Widget",
iframeWidth: "400",
iframeHeight: "300",
run: async () => {
return {
html: `<div>Hello from inline iframe</div>`
};
},
});
// client.tsx
import { isMcpEmbedSurface } from "@/app/lib/mcp-embed";
const imageSrc = isMcpEmbedSurface()
? "/assets/placeholder.png"
: "https://external.com/image.png";
When the MCP host invokes the show-widget tool, it receives the iframeTitle and renders the app inside a sandboxed iframe directly within the conversation UI.
Summary
- Register the MCP app using
mountMCPin a server plugin liketemplates/plan/server/plugins/00-mcp.tsto expose actions to external hosts. - Add
iframeTitleto every action definition that should support inline rendering; this property identifies the iframe for the host UI. - Detect the embed surface using
isMcpEmbedSurface()to handle strict sandbox policies and serve same-origin assets when?embedded=1is present. - Reference templates in the BuilderIO/agent-native repository (Calendar, Mail, Design, Slides) for production examples of MCP embed detection.
Frequently Asked Questions
What is the MCP runtime in Agent-Native?
The MCP runtime is a built-in server-side system in Agent-Native that implements the Multi-Chat-Partner protocol, enabling template apps to register tools and render UI widgets inside external AI hosts like ChatGPT or Claude as sandboxed iframes.
Why is the iframeTitle property required for inline rendering?
The iframeTitle property provides the accessible title attribute for the sandboxed iframe element that the host creates. Without this metadata, the host cannot properly label the embedded surface for accessibility or distinguish it from other potential inline applications.
How do I handle COEP and CORP policy restrictions in an embedded iframe?
MCP hosts enforce strict Cross-Origin Embedder Policies that block external images and assets. Use the isMcpEmbedSurface() helper to detect the ?embedded query flag and switch to same-origin placeholder assets that comply with the host's sandbox requirements.
Can I customize the iframe dimensions for my MCP app?
Yes, in addition to iframeTitle, you can export optional iframeWidth and iframeHeight properties in your action definition. These values suggest the initial dimensions to the MCP host, though the host may apply its own constraints based on available UI space.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →