Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Browser context transport #206

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

AdirD
Copy link

@AdirD AdirD commented Mar 18, 2025

Browser-based MCP Transport Implementation

Added a browser-based MCP transport implementation to enable client-server communication entirely within the browser environment.

#219

Motivation and Context

  • There's increasing demand for applications that can run entirely in the browser
  • Current transport implementations (stdio, SSE) require separate processes or HTTP endpoints, which isn't suitable for browser based applications, like browser extensions, static web apps, or embedded components.
  • The new BrowserContextTransport allows for MCP servers to run directly in the browser alongside clients, enabling new application architectures.

How Has This Been Tested?

  • Tested in browser environment with basic client-server communication
  • Tested with Web Workers scenario for isolated server execution
  • Tested with iframes for cross-domain scenarios

Breaking Changes

None. This is a new addition that doesn't affect existing functionality.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The implementation uses the browser's MessageChannel API to create paired channels for bidirectional communication between MCP client and MCP server.

Resources

Quick Start Examples

Creating a Transport Pair

Most basic use case - both ends in same context:

// Import the necessary components
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { BrowserContextTransport } from '@modelcontextprotocol/sdk/browser-context-transport.js';

async function setupMcpInBrowser() {
  // Create a connected pair of transports
  const [clientTransport, serverTransport] = BrowserContextTransport.createChannelPair();
  
  // Create and set up the client
  const client = new Client(
    { name: "browser-client", version: "1.0.0" },
    { capabilities: { tools: {}, resources: {}, prompts: {} } }
  );
  
  // Create and set up the server
  const server = new Server(
    { name: "browser-server", version: "1.0.0" },
    { capabilities: { tools: {}, resources: {}, prompts: {} } }
  );
  
  // Connect the transports to their respective endpoints
  await server.connect(serverTransport);
  await client.connect(clientTransport);
  
  // Initialize the client
  await client.initialize();
  
  return { client, server };
}

With Web Workers

For cases where you want to run the server in a Web Worker:

// In your main application
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { BrowserContextTransport } from '@modelcontextprotocol/sdk/browser-context-transport.js';

async function createWorkerServerClient() {
  // Create a worker
  const worker = new Worker('server-worker.js');
  
  // Create a MessageChannel to communicate with the worker
  const channel = new MessageChannel();
  
  // Pass port2 to the worker
  worker.postMessage({ type: 'INIT_PORT' }, [channel.port2]);
  
  // Create a client with port1
  const client = new Client(
    { name: "browser-client", version: "1.0.0" },
    { capabilities: { tools: {}, resources: {}, prompts: {} } }
  );
  
  const transport = new BrowserContextTransport(channel.port1);
  await client.connect(transport);
  await client.initialize();
  
  return client;
}

// In server-worker.js
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { BrowserContextTransport } from '@modelcontextprotocol/sdk/browser-context-transport.js';

let serverTransport;
let server;

// Listen for the port to be transferred
self.onmessage = async (event) => {
  if (event.data && event.data.type === 'INIT_PORT') {
    const port = event.ports[0];
    
    // Create server
    server = new Server(
      { name: "worker-server", version: "1.0.0" },
      { capabilities: { tools: {}, resources: {}, prompts: {} } }
    );
    
    // Create transport with the received port
    serverTransport = new BrowserContextTransport(port);
    
    // Connect the server
    await server.connect(serverTransport);
    
    // Remove this general message handler since we'll now use the transport
    self.onmessage = null;
  }
};

With Iframes

For cross-domain scenarios, you might want to use iframes:

// In the parent window
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { BrowserContextTransport } from '@modelcontextprotocol/sdk/browser-context-transport.js';

async function createIframeServerClient() {
  // Create an iframe
  const iframe = document.createElement('iframe');
  iframe.src = 'https://example.com/server-frame.html';
  document.body.appendChild(iframe);
  
  // Create a MessageChannel
  const channel = new MessageChannel();
  
  // Wait for iframe to load
  await new Promise(resolve => {
    iframe.onload = resolve;
  });
  
  // Transfer port2 to the iframe
  iframe.contentWindow.postMessage({ type: 'INIT_PORT' }, '*', [channel.port2]);
  
  // Create a client with port1
  const client = new Client(
    { name: "browser-client", version: "1.0.0" },
    { capabilities: { tools: {}, resources: {}, prompts: {} } }
  );
  
  const transport = new BrowserContextTransport(channel.port1);
  await client.connect(transport);
  await client.initialize();
  
  return client;
}

// In the iframe (server-frame.html)
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { BrowserContextTransport } from '@modelcontextprotocol/sdk/browser-context-transport.js';

window.addEventListener('message', async (event) => {
  if (event.data && event.data.type === 'INIT_PORT') {
    const port = event.ports[0];
    
    // Create server
    const server = new Server(
      { name: "iframe-server", version: "1.0.0" },
      { capabilities: { tools: {}, resources: {}, prompts: {} } }
    );
    
    // Create transport with the received port
    const serverTransport = new BrowserContextTransport(port);
    
    // Connect the server
    await server.connect(serverTransport);
  }
}, { once: true });

Comment on lines 54 to 62
private static generateSessionId(): string {
// Current timestamp as prefix (in base 36 for shorter string)
const timePrefix = Date.now().toString(36);

// Random suffix
const randomSuffix = Math.random().toString(36).substring(2, 10);

return `${timePrefix}-${randomSuffix}`;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the sse transport the crypto UUID is used. there is a browser compatible implementation that can be used

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants