modelcontextprotocol / modelcontextprotocol/ext-apps
Pattern to reuse iframe-embed as an MCP App
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Description
Lots of people might come to MCP Apps w/ an existing iframe embed, not wanting to reimplement everything.
But some hosts might balk at allowing nested external iframes (which can't be audited easily).
Here is a rough suggestion on how to turn an iframe embed app (supposedly taking its parameters from the URL query params) into an MCP App (which just takes its parameters from the tool outputs, or inputs).
Taking the following embed.ts (normally iframed as https://myhost.com/embed.html?q=some+search), we can have initialization logic that fetches params from the MCP App's tool results instead of from its query params, if we're not iframed.
(Jan 27th 2026: edited to match 1.x APIs)
import { App } from "@modelcontextprotocol/ext-apps";
const isMcpApp = () => window.location.host !== 'myhost.com';
// Returns the query params when embedded, or the MCP tool result' structuredContent
async function getParameters(): Promise<Record<string, string>> {
if (isMcpApp()) {
const app = new McpApp({name: 'Our App', version: ''});
await app.connect();
return new Promise(resolve => {
app.ontoolresult = ({structuredContent}) => resolve(structuredContent);
// Note: inputs are available too.
});
} else {
return Object.fromEntries([...new URL(window.location.href).searchParams])
}
}
async function main() {
const {q: query} = await getParameters();
...
}
main().catch(...)
import { registerAppTool, registerAppResource } from "@modelcontextprotocol/ext-apps/server";
import { McpServer } from "@modelcontextprotocol/sdk/...";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/...";
const server = new McpServer({name: 'Example Server', version: '1.0.0'});
const uiHtml = await fetch("dist/embed.html").then(r => t.text());
const resourceUri = 'ui://example';
registerAppResource(server, {
uri: resourceUri,
....,
content: uiHtml,
_meta: {
ui: {
csp: {
connectDomains: ['api.myhost.com']
}
}
}
})
registerAppTool(server, 'show-embed', {
inputSchema: z.object({q: z.string()}).shape,
_meta: {
ui: { resourceUri }
}
}, ({message}) => {
return {
content: [],
structuredContent: {q: 'the query'`}
}
})
server.server.connect(new StdioServerTransport())
.then(() => console.error('Server is running');
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the embedded TypeScript example in embed.ts and the server registration snippets using registerAppTool and registerAppResource. No repository file or test is named, so first determine where this pattern belongs and how the 1.x APIs are documented. Done would be an agreed, documented pattern for adapting an iframe embed to an MCP App.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, documentation
- Issue type
- Documentation
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100