> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-b.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge tools across iframes

> Connect a child-frame MCP server to its parent with the mcp-iframe custom element.

This guide uses `<mcp-iframe>` to connect a parent-side MCP client to an MCP
server in a child frame. The element republishes the child's tools on the
parent's `document.modelContext` and, when the parent provides MCP-B extensions,
also republishes prompts and resources.

<Note>
  Native cross-document discovery is a separate browser mechanism. The parent delegates the
  `tools` feature with `<iframe allow="tools">`, the child registers a tool with
  `exposedTo`, and the parent discovers it with `getTools({ fromOrigins })`. That path does not use
  MCP transports, add MCP-B name prefixes, or bridge prompts and resources.
</Note>

## Prerequisites

* You control the parent and child applications.
* The parent exposes `document.modelContext`. Use `@mcp-b/global` when you also
  need prompts and resources.
* The child runs an MCP server connected to `IframeChildTransport`.
  `@mcp-b/global` supplies both pieces automatically when it runs inside an
  iframe. A standalone `document.modelContext` implementation is not enough for
  the MCP-B bridge.

## Install the packages

Install `@mcp-b/global` in the child application. Install both packages in the
parent application:

```bash title="Install iframe bridge packages" theme={null}
npm install @mcp-b/global @mcp-b/mcp-iframe
```

<Steps>
  <Step title="Configure the child MCP server">
    Configure the parent origin before importing `@mcp-b/global` in the child page:

    ```html title="child-app.html" theme={null}
    <script type="module">
      window.__webModelContextOptions = {
        transport: {
          iframeServer: {
            allowedOrigins: ['https://parent.example'],
          },
        },
      };

      await import('@mcp-b/global');
      if (!document.modelContext) throw new Error('WebMCP is unavailable');

      await document.modelContext.registerTool({
        name: 'calculate',
        description: 'Add two numbers',
        inputSchema: {
          type: 'object',
          properties: {
            a: { type: 'number' },
            b: { type: 'number' },
          },
          required: ['a', 'b'],
        },
        execute: ({ a, b }) => ({
          content: [{ type: 'text', text: String(a + b) }],
        }),
      });
    </script>
    ```

    Inside an iframe, `@mcp-b/global` creates an MCP server, connects it through
    `IframeChildTransport`, and continues to let application code use
    `document.modelContext` normally. Set `allowedOrigins` to the exact parent
    origins. Its convenience default is `['*']`, which accepts every parent origin.
  </Step>

  <Step title="Add the bridge to the parent">
    Use the same child origin for `src` and `target-origin`. The `id` becomes the
    parent-side item prefix:

    ```html title="parent.html" theme={null}
    <mcp-iframe
      src="https://child.example/app"
      id="calculator"
      target-origin="https://child.example"
      allow="tools"
    ></mcp-iframe>

    <script type="module">
      window.__webModelContextOptions = {
        transport: {
          tabServer: { allowedOrigins: [window.location.origin] },
        },
      };

      await import('@mcp-b/global');
      await import('@mcp-b/mcp-iframe');

      const frame = document.querySelector('mcp-iframe');
      frame.addEventListener('mcp-iframe-ready', (event) => {
        console.log('Bridged tools:', event.detail.tools);
      });
    </script>
    ```

    The child tool `calculate` is registered on the parent as
    `calculator_calculate`. `target-origin` restricts messages sent by the parent;
    the child's `allowedOrigins` independently restricts messages it accepts.

    The `allow="tools"` attribute delegates the native WebMCP feature to a
    cross-origin child. It does not create or authorize the MCP-B transport.
    Under the polyfill, the delegation does not reach a child inside `<mcp-iframe>`:
    the polyfill cannot find frames in the element's shadow root, so the
    cross-origin child's `registerTool()` rejects with `NotAllowedError`. A
    cross-origin child needs native WebMCP; a same-origin child works with either
    runtime.
  </Step>

  <Step title="Verify the parent tool">
    After `mcp-iframe-ready` fires, inspect the parent context:

    ```javascript title="List parent tools" theme={null}
    const tools = await document.modelContext?.getTools();
    console.log(tools?.find((tool) => tool.name === 'calculator_calculate'));
    ```

    Use a different `id` for every `<mcp-iframe>` so bridged names remain unique.
    The element refreshes its registrations when the child advertises supported
    `list_changed` notifications.
  </Step>
</Steps>

<Warning>
  Opaque iframe origins require `target-origin="*"`. That disables parent-side origin validation, so
  prefer a non-opaque iframe and exact origins. Do not use a wildcard merely to work around an
  origin mismatch.
</Warning>

## Related pages

* [mcp-iframe reference](/packages/mcp-iframe/reference) for attributes, events, lifecycle, and programmatic registration
* [Transports reference](/packages/transports/reference) for `IframeParentTransport` and `IframeChildTransport`
* [WebMCP API sources](/reference/webmcp/standard-api) for native `exposedTo` and `fromOrigins`
* [Transports and bridges](/explanation/architecture/transports-and-bridges) for the architectural boundary


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.