Browser transport implementations for the Model Context Protocol. Each transport class implements the MCP Transport interface and handles JSON-RPC 2.0 message serialization, origin validation, and connection lifecycle.
Installation
pnpm add @mcp-b/transports
Peer dependency: @mcp-b/webmcp-ts-sdk (included transitively).
Minimal example
Which transport to use
| Scenario | Server | Client |
|---|
| Same-page communication | TabServerTransport | TabClientTransport |
| Parent page to iframe | IframeChildTransport (in iframe) | IframeParentTransport (in parent) |
| Extension to webpage | ExtensionServerTransport (in background) | ExtensionClientTransport (in sidebar/popup) |
| Extension to extension | ExtensionServerTransport (in host extension) | ExtensionExternalClientTransport (in guest extension) |
| User script to extension | UserScriptServerTransport (in background) | UserScriptClientTransport (in user script) |
Tab transports
In-page communication via window.postMessage. Both server and client run in the same browser tab.
TabServerTransport
Listens for MCP messages on the current window. Broadcasts a mcp-server-ready signal on start.
import { TabServerTransport } from "@mcp-b/transports";
const transport = new TabServerTransport({
allowedOrigins: ["https://example.com"],
});
await server.connect(transport);
TabServerTransportOptions
| Option | Type | Required | Default | Description |
|---|
allowedOrigins | string[] | Yes | — | Origins permitted to send messages. Use ["*"] to allow any origin. |
channelId | string | No | "mcp-default" | Channel identifier for message routing. Multiple transports can coexist on the same page with different channel IDs. |
Behavior
- Validates
event.origin against allowedOrigins for every incoming message.
- Tracks pending requests and sends interrupted responses during
beforeunload if the page navigates while a tool call is in progress.
- Cleans up stale requests after 5 minutes.
- Posts
mcp-server-stopped on close.
TabClientTransport
Connects to a TabServerTransport in the same window. Waits for a server-ready handshake before sending messages.
import { TabClientTransport } from "@mcp-b/transports";
const transport = new TabClientTransport({
targetOrigin: window.location.origin,
});
await client.connect(transport);
TabClientTransportOptions
| Option | Type | Required | Default | Description |
|---|
targetOrigin | string | No | "*" | Expected origin of the server window. Messages from other origins are ignored. |
channelId | string | No | "mcp-default" | Channel identifier for message routing. |
requestTimeout | number | No | 10000 | Timeout in milliseconds for requests. If the server does not respond in time, a JSON-RPC error with code -32000 is synthesized. |
Properties
| Property | Type | Description |
|---|
serverReadyPromise | Promise<void> | Resolves when the server signals readiness. send() awaits this automatically. |
Server discovery
TabClientTransport does not have a standalone discover() method. The server-ready handshake (mcp-check-ready / mcp-server-ready) is handled internally during start().
Iframe transports
Cross-origin communication between a parent page and an iframe. Uses window.postMessage with retry-based ready handshakes to handle iframe loading timing.
IframeParentTransport
Client-side transport for the parent page. Sends messages into the iframe’s contentWindow.
import { IframeParentTransport } from "@mcp-b/transports";
const iframe = document.querySelector("iframe");
const transport = new IframeParentTransport({
iframe,
targetOrigin: "https://iframe-app.com",
});
await client.connect(transport);
IframeParentTransportOptions
| Option | Type | Required | Default | Description |
|---|
iframe | HTMLIFrameElement | Yes | — | Reference to the iframe element. |
targetOrigin | string | No | "*" | Expected origin of the iframe for postMessage security. |
channelId | string | No | "mcp-iframe" | Channel identifier for message routing. |
checkReadyRetryMs | number | No | 250 | Interval in milliseconds to retry the ready handshake if the iframe is not yet ready. |
Properties
| Property | Type | Description |
|---|
serverReadyPromise | Promise<void> | Resolves when the iframe’s server signals readiness. |
IframeChildTransport
Server-side transport for code running inside an iframe. Sends messages to window.parent.
import { IframeChildTransport } from "@mcp-b/transports";
const transport = new IframeChildTransport({
allowedOrigins: ["https://parent-app.com"],
});
await server.connect(transport);
IframeChildTransportOptions
| Option | Type | Required | Default | Description |
|---|
allowedOrigins | string[] | Yes | — | Parent origins permitted to connect. Use ["*"] to allow any origin. |
channelId | string | No | "mcp-iframe" | Channel identifier for message routing. |
serverReadyRetryMs | number | No | 250 | Interval in milliseconds to retry broadcasting the ready signal to the parent. |
Extension transports
Communication between Chrome extension components using chrome.runtime.Port. Used for connecting content scripts, sidebars, and popups to the extension’s background service worker.
ExtensionServerTransport
Wraps a chrome.runtime.Port to handle a single client connection. Runs in the background service worker.
import { ExtensionServerTransport } from "@mcp-b/transports";
chrome.runtime.onConnect.addListener((port) => {
if (port.name === "mcp") {
const transport = new ExtensionServerTransport(port);
await server.connect(transport);
}
});
Constructor
new ExtensionServerTransport(port: chrome.runtime.Port, options?: ExtensionServerTransportOptions)
ExtensionServerTransportOptions
| Option | Type | Required | Default | Description |
|---|
keepAlive | boolean | No | true | Send periodic keep-alive pings to prevent service worker shutdown. |
keepAliveInterval | number | No | 1000 | Keep-alive interval in milliseconds. |
Methods
| Method | Returns | Description |
|---|
getConnectionInfo() | { connectedAt, lastMessageAt, messageCount, uptime, isConnected } | Connection statistics. |
ExtensionClientTransport
Connects to the extension’s background service worker via chrome.runtime.connect. Supports automatic reconnection with exponential backoff.
import { ExtensionClientTransport } from "@mcp-b/transports";
const transport = new ExtensionClientTransport({
portName: "mcp",
});
await client.connect(transport);
ExtensionClientTransportOptions
| Option | Type | Required | Default | Description |
|---|
extensionId | string | No | — | Extension ID to connect to. Omit for same-extension connections. When provided, uses chrome.runtime.connect(extensionId, ...) for cross-extension communication. |
portName | string | No | "mcp" | Port name for the connection. |
autoReconnect | boolean | No | true | Reconnect automatically on disconnect. |
maxReconnectAttempts | number | No | 10 | Maximum reconnection attempts before giving up. |
reconnectDelay | number | No | 1000 | Initial reconnection delay in milliseconds. |
maxReconnectDelay | number | No | 30000 | Maximum reconnection delay in milliseconds. |
reconnectBackoffMultiplier | number | No | 1.5 | Multiplier applied to the delay after each failed attempt. |
User script transports
Communication between Chrome MV3 user scripts and the extension’s background service worker. Connections arrive via chrome.runtime.onUserScriptConnect on the extension side.
UserScriptServerTransport
Identical in API to ExtensionServerTransport. Wraps a chrome.runtime.Port received from a user script connection.
import { UserScriptServerTransport } from "@mcp-b/transports";
chrome.runtime.onUserScriptConnect.addListener((port) => {
const transport = new UserScriptServerTransport(port);
await server.connect(transport);
});
UserScriptServerTransportOptions
| Option | Type | Required | Default | Description |
|---|
keepAlive | boolean | No | true | Send periodic keep-alive pings. |
keepAliveInterval | number | No | 1000 | Keep-alive interval in milliseconds. |
UserScriptClientTransport
Identical in API to ExtensionClientTransport. Connects from a user script context to the extension background.
UserScriptClientTransportOptions
| Option | Type | Required | Default | Description |
|---|
extensionId | string | No | — | Extension ID to connect to. |
portName | string | No | "mcp" | Port name for the connection. |
autoReconnect | boolean | No | true | Reconnect automatically on disconnect. |
maxReconnectAttempts | number | No | 10 | Maximum reconnection attempts. |
reconnectDelay | number | No | 1000 | Initial reconnection delay in milliseconds. |
maxReconnectDelay | number | No | 30000 | Maximum reconnection delay in milliseconds. |
reconnectBackoffMultiplier | number | No | 1.5 | Backoff multiplier per failed attempt. |
Security
All transports enforce origin or connection validation:
| Transport type | Security mechanism |
|---|
| Tab | allowedOrigins on server, targetOrigin on client. Messages from non-matching origins are silently dropped. |
| Iframe | Same as tab transports. Cross-origin postMessage with origin checks on both sides. |
| Extension | Chrome’s port-based messaging API. Only code within the extension (or extensions listed in externally_connectable) can connect. |
| Extension External | Requires externally_connectable in manifest.json. Server should validate port.sender.id before accepting connections. |
| User Script | Chrome’s onUserScriptConnect API. Only registered user scripts can initiate connections. |
Setting allowedOrigins to ["*"] or targetOrigin to "*" disables origin validation. Use specific origins in production.
Common transport interface
Every transport class implements the MCP Transport interface:
| Member | Type | Description |
|---|
start() | Promise<void> | Begin listening for messages. |
send(message) | Promise<void> | Send a JSON-RPC 2.0 message. |
close() | Promise<void> | Stop listening and clean up resources. |
onmessage | (message: JSONRPCMessage) => void | Callback for incoming messages. |
onerror | (error: Error) => void | Callback for errors. |
onclose | () => void | Callback when the transport closes. |