modelcontextprotocol / modelcontextprotocol/ext-apps

Pattern to reuse iframe-embed as an MCP App

Open
#34 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.