> ## 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.

# @mcp-b/mcp-iframe reference

> Reference for the iframe bridge custom element and its event contract.

A Web Component that connects to an MCP server inside an iframe and republishes
its tools on the parent page's `document.modelContext`. With MCP-B extensions on
the parent, it also republishes resources and prompts. Tool and prompt names
include the element's `id`; resource URIs use an `mcp-iframe:` wrapper URI.

## Installation

```bash title="Install mcp-iframe" theme={null}
pnpm add @mcp-b/mcp-iframe
```

## Child server requirement

The iframe must run an MCP server connected to `IframeChildTransport`. Merely
installing a `document.modelContext` implementation in the child does not expose
an MCP endpoint to the parent element.

`@mcp-b/global` supplies the server and selects `IframeChildTransport`
automatically when it runs in an iframe. Configure the parent origins before
importing it:

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

  await import('@mcp-b/global');
</script>
```

`IframeChildTransport` requires a non-empty `allowedOrigins` list. The global
runtime defaults it to `['*']` when omitted; use exact parent origins in
production. A manually created MCP server can instead connect to
`IframeChildTransport` directly.

<Note>
  Native cross-document WebMCP does not require this package. It uses iframe `allow="tools"` and
  parent-side `getTools({fromOrigins})`, both enforced by the browser and independent of the MCP
  transport's `target-origin` and `allowedOrigins`.
</Note>

## Minimal example

```html title="Bridge an iframe" theme={null}
<mcp-iframe src="./child-app.html" id="my-app"></mcp-iframe>

<script type="module">
  import '@mcp-b/mcp-iframe';

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

## Entry points

| Entry point | Behavior |
| - | - |
| `@mcp-b/mcp-iframe` | Exports the element and event types, then auto-registers `<mcp-iframe>`. |
| `@mcp-b/mcp-iframe/element` | Exports the element, event types, and `registerMCPIframeElement()` with no registration side effect. |

***

## Attributes

| Attribute | Type | Default | Description |
| - | - | - | - |
| `src` | `string` | -- | URL of the iframe page. |
| `id` | `string` | `"iframe"` | Used as the tool and prompt name prefix. Sanitized to match `[a-zA-Z0-9_.-]`. |
| `target-origin` | `string` | Inferred from `src` | Override the `postMessage` target origin. Opaque iframe origins require `"*"`. |
| `channel` | `string` | `"mcp-iframe"` | Channel ID for the underlying `IframeParentTransport`. |
| `call-timeout` | `number` | `30000` | Positive integer timeout in milliseconds for tool calls, resource reads, and prompt gets. Invalid values use the default. |
| `prefix-separator` | `string` | `"_"` | Separator between the prefix and the item name. Must contain only `[a-zA-Z0-9_.-]` characters. |

Standard iframe attributes (`sandbox`, `allow`, `width`, `height`, `loading`, `referrerpolicy`, `name`, `srcdoc`, `allowfullscreen`, `credentialless`) are mirrored to the internal `<iframe>` element.

For cross-origin children that use native WebMCP, set `allow="tools"` to
delegate that browser feature. The attribute does not establish or authorize
the MCP-B bridge. Under the polyfill or `@mcp-b/global`, a cross-origin child
cannot register tools at all: the polyfill's cross-origin permission handshake
identifies a child by its index in the parent's `window.frames`, the iframe
inside the element's shadow root has no index there, and `registerTool()`
rejects with `NotAllowedError` even with `allow="tools"`. Native WebMCP checks
the permission in the browser and does not have this limitation.

Sandboxed iframes without `allow-same-origin`, and sources whose URL origin is `null`, have opaque origins. Set `target-origin="*"` for those sources. Other sources use the explicit attribute or the origin inferred from `src`.

***

## Scoping tools to the parent

A child narrows an individual tool to named embedder origins by passing
`exposedTo` to `registerTool`:

```js "Restrict a tool to one embedder" theme={null}
await document.modelContext.registerTool(
  { name: 'checkout', description: 'Place the order', execute: placeOrder },
  { exposedTo: ['https://shop.example'] }
);
```

Embedded anywhere else, that tool never reaches the parent's `exposedTools` and
cannot be called through the element. Tools registered without `exposedTo` stay
visible to whichever parent the child transport already allows.

Two limits apply:

* `exposedTo` only narrows. The child transport's `allowedOrigins` still decides
  who may connect at all, and no allowlist widens past it.
* Enforcement is the child's own JavaScript, not the browser. Native WebMCP has
  the user agent enforce `exposedTo`, so a compromised child cannot opt out.
  Treat the bridge's version as scoping rather than a boundary against the child
  itself. See [Security and human in the loop](/explanation/design/security-and-human-in-the-loop).

## Events

| Event | Detail type | Fired when |
| - | - | - |
| `mcp-iframe-ready` | `{ tools: string[], resources: string[], prompts: string[] }` | The element connects to the iframe's MCP server and registers all items on the parent. |
| `mcp-iframe-error` | `{ error: unknown }` | The connection or a registration refresh fails. |
| `mcp-iframe-items-changed` | `{ tools: string[], resources: string[], prompts: string[] }` | The child reports a supported list change, or `refresh()` completes. |

The `tools` and `prompts` arrays contain parent-side names. The `resources` array contains parent-side wrapper URIs for static resources and resource templates.

***

## Name prefixing

Tools and prompts from the iframe are registered on the parent as `{id}{separator}{name}`:

| Iframe tool name | Element `id` | Separator | Parent tool name |
| - | - | - | - |
| `calculate` | `my-app` | `_` (default) | `my-app_calculate` |
| `get_data` | `billing` | `_` | `billing_get_data` |
| `help` | `assistant` | `-` | `assistant-help` |

The parent model context performs final tool-name validation. A rejected registration emits `mcp-iframe-error` and disconnects the element without leaving a partial registration set.

Bridged tools retain their `title` and WebMCP `readOnlyHint` annotation.

When the parent runs `@mcp-b/global`, its server also lists a same-origin
child's tools under their own names, so each such tool appears twice over MCP:
once unprefixed through frame mirroring and once with the element prefix.
Resources and prompts appear only with the prefix.

## Resource URI wrapping

Resource URIs use the `mcp-iframe:` scheme instead of name concatenation. A resource `config://settings` in an iframe with `id="my-app"` becomes `mcp-iframe:?source=my-app_&uri=config%3A%2F%2Fsettings` on the parent.

Resource templates use the same wrapper. Template expressions remain expressions, so `config://users/{userId}` can be listed and resolved through the parent.

***

## Properties

| Property | Type | Description |
| - | - | - |
| `iframe` | `HTMLIFrameElement \| null` | The wrapped iframe element. |
| `ready` | `boolean` | Whether the element is connected to the iframe's MCP server. |
| `exposedTools` | `string[]` | Prefixed tool names registered on the parent. |
| `exposedResources` | `string[]` | Wrapper resource URIs registered on the parent. |
| `exposedPrompts` | `string[]` | Prefixed prompt names registered on the parent. |
| `itemPrefix` | `string` | The computed prefix string (`{id}{separator}`). |

***

## Methods

| Method | Returns | Description |
| - | - | - |
| `refresh()` | `Promise<void>` | Re-fetches all tools, resources, resource templates, and prompts from the iframe, replaces the parent registrations, and fires `mcp-iframe-items-changed`. |

If fetching or parent registration fails, `refresh()` rejects after emitting `mcp-iframe-error` and disconnecting the element. It also rejects if the iframe connection changes before the refresh completes.

***

## Programmatic registration

To register the custom element with a different tag name:

```typescript title="Register a custom element name" theme={null}
import { registerMCPIframeElement } from '@mcp-b/mcp-iframe/element';

registerMCPIframeElement('my-mcp-frame');
```

The `/element` entry point does not register `<mcp-iframe>`. The package root does.

***

## Lifecycle

1. On `connectedCallback`, the element creates an internal `<iframe>` and mirrors attributes.
2. When the iframe fires its `load` event, the element creates an `IframeParentTransport` and an MCP `Client`, then connects.
3. The element subscribes to each `list_changed` notification advertised by the child.
4. Tools, resources, resource templates, and prompts are fetched without using cached list responses.
5. Each item is registered on the parent's `document.modelContext`. Tool and prompt names use the element prefix, while resources use wrapper URIs.
6. The `mcp-iframe-ready` event fires. Later child list changes replace the parent registrations in order and fire `mcp-iframe-items-changed`.
7. On `disconnectedCallback`, notification handlers and registrations are removed, and the client and transport are closed.

Changing `src`, `srcdoc`, `target-origin`, or `channel` while connected triggers a reconnection cycle.

***

## Related

* [Bridge tools across iframes](/how-to/bridge-tools-across-iframes) for a step-by-step guide
* [@mcp-b/transports](/packages/transports/reference) for the underlying `IframeParentTransport` and `IframeChildTransport`
* [@mcp-b/global](/packages/global/reference) for child transport configuration
* [WebMCP API sources](/reference/webmcp/standard-api) for native cross-document discovery
* [Transports and bridges](/explanation/architecture/transports-and-bridges) for the architectural context


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