Skip to main content
Transports are the communication layer that routes MCP messages between different browser contexts. Think of them as the plumbing that lets your tools talk to agents, whether they’re in the same tab, different tabs, or extension pages.

Prerequisites

  • @modelcontextprotocol/sdk - Official MCP SDK
  • Modern browser with ES2020+ support
  • TypeScript 5.0+ (recommended for type safety)
  • Chrome Extension Manifest V3 (for extension transports)
  • Understanding of async/await and Promises

Installation

npm install @mcp-b/transports @modelcontextprotocol/sdk

Transport Types

Tab Transports (In-Page Communication)

Use TabServerTransport and TabClientTransport when your MCP server and client are running in the same browser tab. The transport uses window.postMessage for secure communication with origin validation.

Iframe Transports (Parent-Child Communication)

Use IframeParentTransport and IframeChildTransport for cross-origin communication between a parent page and an iframe. These transports are specifically designed for iframe scenarios and support cross-origin messaging.

Extension Transports (Cross-Context Communication)

Use ExtensionClientTransport and ExtensionServerTransport for communication between browser extension components (sidebar, popup, background) and web pages with MCP servers.

Tab Transport Examples

Server Setup (Web Page)

Create an MCP server in your web page and expose it via TabServerTransport:
import { TabServerTransport } from "@mcp-b/transports";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// Create MCP server with tools
const server = new McpServer(
  {
    name: "TODO-APP",
    version: "1.0.0",
  },
  {
    instructions:
      "You are a helpful assistant that can create, update, and delete todos.",
  }
);

// Register a tool
server.tool(
  "createTodo",
  "Creates a new todo item for the current user",
  {
    todoText: z.string().describe("The content of the todo item."),
  },
  async (args) => {
    // Implementation here
    return {
      content: [
        {
          type: "text",
          text: `Todo created: "${args.todoText}"`,
        },
      ],
    };
  }
);

// Connect to transport with CORS configuration
const transport = new TabServerTransport({
  allowedOrigins: ["*"], // Configure based on your security needs
});
await server.connect(transport);

Client Setup (Same Page)

Connect to the server from within the same page or from an extension content script:
import { TabClientTransport } from "@mcp-b/transports";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// Create transport with target origin
const transport = new TabClientTransport({
  targetOrigin: window.location.origin,
});

// Discover available servers
const availableServers = await transport.discover();
if (availableServers.length > 0) {
  console.log(`Found server: ${availableServers[0].implementation.name}`);
}

// Create and connect client
const client = new Client({
  name: "ExtensionProxyClient",
  version: "1.0.0",
});

await client.connect(transport);

// Use the client
const result = await client.callTool({
  name: "createTodo",
  arguments: { todoText: "Buy groceries" },
});

Iframe Transport Examples

Server Setup (Inside Iframe)

Create an MCP server inside an iframe that can be accessed by the parent page:
import { IframeChildTransport } from "@mcp-b/transports";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { z } from "zod";

// Create MCP server
const server = new Server(
  {
    name: "IframeApp",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Register tools
server.setRequestHandler('tools/list', async () => {
  return {
    tools: [
      {
        name: "getIframeData",
        description: "Get data from the iframe application",
        inputSchema: {
          type: "object",
          properties: {
            key: { type: "string", description: "Data key to retrieve" }
          },
          required: ["key"]
        }
      }
    ]
  };
});

server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === "getIframeData") {
    const { key } = request.params.arguments;
    return {
      content: [
        {
          type: "text",
          text: `Retrieved: ${key}`,
        },
      ],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

// Connect to iframe transport
const transport = new IframeChildTransport({
  allowedOrigins: ["https://parent-app.com"], // Parent page origin
  // or use ['*'] to allow any origin (less secure)
});

await server.connect(transport);

Client Setup (Parent Page)

Connect from the parent page to the iframe’s MCP server:
import { IframeParentTransport } from "@mcp-b/transports";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// Get reference to iframe element
const iframe = document.querySelector('iframe');

// Wait for iframe to load
iframe.addEventListener('load', async () => {
  // Create transport targeting the iframe
  const transport = new IframeParentTransport({
    iframe: iframe,
    targetOrigin: 'https://iframe-app.com', // Iframe page origin
  });

  // Create MCP client
  const client = new Client(
    {
      name: "ParentPage",
      version: "1.0.0",
    },
    {
      capabilities: {}
    }
  );

  // Connect and use
  await client.connect(transport);

  // List available tools from iframe
  const tools = await client.listTools();
  console.log("Tools from iframe:", tools.tools);

  // Call a tool from the iframe
  const result = await client.callTool({
    name: "getIframeData",
    arguments: { key: "user-preferences" },
  });
});

Extension Transport Examples

Background Script Setup

The extension background script acts as a hub, aggregating tools from multiple tabs:
import { ExtensionServerTransport } from "@mcp-b/transports";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

class McpHub {
  private server: McpServer;

  constructor() {
    this.server = new McpServer({
      name: "Extension-Hub",
      version: "1.0.0",
    });

    this.setupConnections();
  }

  private setupConnections() {
    chrome.runtime.onConnect.addListener((port) => {
      if (port.name === "mcp") {
        this.handleUiConnection(port);
      } else if (port.name === "mcp-content-script-proxy") {
        this.handleContentScriptConnection(port);
      }
    });
  }

  private async handleUiConnection(port: chrome.runtime.Port) {
    const transport = new ExtensionServerTransport(port, {
      keepAlive: true,
      keepAliveInterval: 25_000,
    });
    await this.server.connect(transport);
  }
}

Content Script Bridge

Content scripts act as a bridge between the page’s MCP server and the extension:
import { TabClientTransport } from "@mcp-b/transports";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// Connect to the page's MCP server
const transport = new TabClientTransport({
  targetOrigin: window.location.origin,
});

const client = new Client({
  name: "ExtensionProxyClient",
  version: "1.0.0",
});

// Connect to extension background
const backgroundPort = chrome.runtime.connect({
  name: "mcp-content-script-proxy",
});

// Discover and connect to page server
await client.connect(transport);
const pageTools = await client.listTools();

// Register tools with background hub
backgroundPort.postMessage({
  type: "register-tools",
  tools: pageTools.tools,
});

// Handle tool execution requests from background
backgroundPort.onMessage.addListener(async (message) => {
  if (message.type === "execute-tool") {
    const result = await client.callTool({
      name: message.toolName,
      arguments: message.args || {},
    });

    backgroundPort.postMessage({
      type: "tool-result",
      requestId: message.requestId,
      data: { success: true, payload: result },
    });
  }
});

Extension UI Client

Connect from the extension’s sidebar or popup to use tools from all connected pages:
import { ExtensionClientTransport } from "@mcp-b/transports";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

// Create transport - connects to the extension's background script
const transport = new ExtensionClientTransport({
  portName: "mcp",
});

// Create MCP client
const client = new Client({
  name: "Extension Sidepanel",
  version: "1.0.0",
});

// Connect and use
await client.connect(transport);

// List all available tools from all connected tabs
const tools = await client.listTools();

// Call a tool from a specific website
const result = await client.callTool({
  name: "website_tool_example_com_createTodo",
  arguments: { todoText: "Review PR" },
});

Configuration Options

ExtensionServerTransport Options

OptionTypeRequiredDescription
portchrome.runtime.PortYesChrome runtime port object
keepAlivebooleanNoEnable keep-alive ping/pong (default: false)
keepAliveIntervalnumberNoKeep-alive interval in ms (default: 30000)

ExtensionClientTransport Options

OptionTypeRequiredDescription
portNamestringNoPort name for connection (default: ‘mcp’)
autoReconnectbooleanNoAuto-reconnect on disconnect (default: false)
extensionIdstringNoTarget extension ID (for cross-extension)

TabServerTransport Options

OptionTypeRequiredDescription
allowedOriginsstring[]YesArray of allowed origins or ['*'] for all

TabClientTransport Options

OptionTypeRequiredDescription
targetOriginstringYesOrigin of the target window

IframeParentTransport Options

OptionTypeRequiredDescription
iframeHTMLIFrameElementYesReference to the iframe element
targetOriginstringYesExpected origin of the iframe (for security)
channelIdstringNoChannel identifier (default: ‘mcp-iframe’)
checkReadyRetryMsnumberNoRetry interval for ready handshake in ms (default: 250)

IframeChildTransport Options

OptionTypeRequiredDescription
allowedOriginsstring[]YesWhitelist of parent origins allowed to connect
channelIdstringNoChannel identifier (default: ‘mcp-iframe’)
serverReadyRetryMsnumberNoRetry interval for broadcasting ready signal in ms (default: 250)

Key Features

  • Automatic Server Discovery: Tab clients can discover available servers
  • Cross-Origin Support: Configure CORS for tab and iframe transports
  • Parent-Child Communication: Iframe transports enable secure cross-origin iframe communication
  • Ready Handshake Protocol: Iframe transports handle iframe loading timing issues automatically
  • Cross-Extension Communication: Extensions can expose APIs to other extensions
  • Tool Namespacing: Extension hub prefixes tools to avoid conflicts
  • Connection Management: Automatic cleanup when tabs close
  • Keep-Alive Support: Maintain persistent connections
  • Type Safety: Full TypeScript support with proper typing

Security Considerations

  • Tab transports respect origin restrictions
  • Iframe transports validate origins on both parent and child sides
    • Always specify explicit targetOrigin (never use '*' in production)
    • Configure allowedOrigins to whitelist only trusted parent domains
    • Use postMessage API for secure cross-origin communication
  • Extension transports use Chrome’s secure message passing
  • External extension transports require externally_connectable manifest configuration
  • Server extensions should validate incoming connections from other extensions
  • Configure allowedOrigins appropriately for your use case
  • Tools execute in their original context (web page, iframe, or extension)
See also:

External Resources