Skip to content

Pattern to reuse iframe-embed as an MCP App #34

@ochafik

Description

@ochafik

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.

import { App, PostMessageTransport } 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(new PostMessageTransport());
    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 { 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';

server.registerResource({
  uri: resourceUri,
  ....,
  content: uiHtml,
  _meta: {
    ui: {
      csp: {
        connectDomains: ['api.myhost.com']
      }
    }
  }
})
server.registerTool('show-embed', {
  inputSchema: z.object({q: z.string()}).shape,
  _meta: {
    'ui/resourceUri': resourceUri,
  }
}, ({message}) => {
   return {
     content: [],
     structuredContent: {q: 'the query'`}
   }
})
  
server.server.connect(new StdioServerTransport())
.then(() => console.error('Server is running');

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions