浏览器API桥接服务

基于Cloudflare Worker和Durable Objects构建的浏览器桥接服务,提供WebSocket桥接、REST API代理和MCP协议支持,将浏览器能力暴露为可调用的API服务。

  • by broxy-dev
  • JavaScript
  • 本地部署
  • 更新于
在 GitHub 查看 broxy-dev/worker

使用说明

Broxy工人

中文

基于Cloudflare Worker+Durable Objects构建的Broxy后端服务。提供WebSocket桥接、REST API代理和MCP(模型上下文协议)支持,以将浏览器功能公开为可调用的API服务。

特性

  • WebSocket桥接 -与浏览器脚本建立持久连接以进行实时通信
  • REST API代理 -通过以下方式向浏览器发送代理请求 /api/{userId}/* 支持自定义响应格式的端点
  • MCP协议支持 -实现MCP JSON-RPC 2.0协议用于AI工具调用
  • 耐用物品 -具有长期连接和请求队列的每用户连接状态管理
  • 自定义响应格式 -通过base64编码支持自定义状态代码、标头和二进制响应

建筑

graph TB
    subgraph Client["Clients"]
        MCPClient["MCP Client<br/>(AI Tool Calling)"]
        APIClient["API Consumer<br/>(HTTP Request)"]
    end

    subgraph Cloudflare["Cloudflare Worker"]
        Worker["Worker Entry<br/>src/index.js"]
        DO["Durable Object<br/>BrowserConnection"]
        MCPHandler["MCP Handler<br/>src/mcpHandler.js"]
    end

    subgraph Browser["Browser"]
        Ext["Browser Script<br/>(ext/)"]
        Handler["Tool Handlers"]
    end

    MCPClient -->|"POST /mcp/{userId}"| Worker
    APIClient -->|"GET/POST /api/{userId}/*"| Worker
    Worker -->|"Route"| DO
    DO -->|"JSON-RPC"| MCPHandler
    DO <-->|"WebSocket"| Ext
    Ext -->|"Execute"| Handler
    Handler -->|"Result"| Ext
    Ext -->|"Response"| DO
    DO -->|"Return"| Worker
    Worker -->|"Response"| MCPClient
    Worker -->|"Response"| APIClient

API终点

健康检查

GET /health

答复:

{
  "status": "ok",
  "service": "broxy",
  "endpoints": {
    "connect": "/connect?id={userId}",
    "mcp": "/mcp/{userId}",
    "api": "/api/{userId}/{route}"
  }
}

WebSocket连接

GET /connect?id={userId}
Upgrade: websocket

浏览器脚本通过此端点建立WebSocket连接。

连接成功消息:

{
  "type": "connected",
  "connectionId": "uuid-xxx",
  "message": "Browser bridge connected successfully"
}

请求消息(Worker→ 浏览器):

{
  "type": "request",
  "requestId": "uuid-xxx",
  "data": {
    "method": "GET",
    "path": "/api/route",
    "query": {},
    "headers": {},
    "body": null
  }
}

响应消息(浏览器→ 工人):

{
  "type": "response",
  "requestId": "uuid-xxx",
  "result": { "data": "response data" }
}

REST API代理

支持所有HTTP方法:

GET|POST|PUT|DELETE /api/{userId}/{route}

请求示例:

curl -X POST https://your-worker.workers.dev/api/user123/data/fetch \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/api"}'

成功响应:

{
  "data": {
    "result": "browser execution result"
  }
}

自定义响应格式

浏览器脚本可以返回带有状态代码、标题和正文的自定义响应格式:

{
  "status": 201,
  "headers": {
    "Content-Type": "text/plain",
    "X-Custom-Header": "value"
  },
  "body": "Plain text response"
}

返回二进制数据(图像、PDF等) 使用base64编码:

{
  "status": 200,
  "headers": {
    "Content-Type": "image/png"
  },
  "body": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
  "isBase64": true
}

示例用法-浏览器脚本返回图像:

// Browser script handler
async function handleRequest(request) {
  if (request.path === '/screenshot') {
    const canvas = document.createElement('canvas');
    // ... capture screenshot
    const dataUrl = canvas.toDataURL('image/png');
    const base64 = dataUrl.split(',')[1];
    
    return {
      status: 200,
      headers: { 'Content-Type': 'image/png' },
      body: base64,
      isBase64: true
    };
  }
}

错误响应(浏览器未连接):

{
  "error": "Browser not connected",
  "userId": "user123",
  "hint": "Browser script may not be running or userId is invalid"
}

错误响应(超时):

{
  "error": "timeout",
  "userId": "user123",
  "details": "Browser did not respond within 30000ms"
}

MCP JSON-RPC端点

POST /mcp/{userId}
Content-Type: application/json

初始化请求:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {},
    "clientInfo": {
      "name": "my-client",
      "version": "1.0.0"
    }
  }
}

初始化响应:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {},
      "resources": {},
      "prompts": {}
    },
    "serverInfo": {
      "name": "Broxy MCP Server",
      "version": "1.0.0"
    }
  }
}

列出工具:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

工具列表响应示例:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "fetch_page",
        "description": "Fetch page content",
        "inputSchema": {
          "type": "object",
          "properties": {
            "url": { "type": "string" }
          },
          "required": ["url"]
        }
      }
    ]
  }
}

呼叫工具:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "fetch_page",
    "arguments": {
      "url": "https://example.com"
    }
  }
}

工具调用响应:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "page content..."
      }
    ]
  }
}

本地开发

npx wrangler dev

开发服务器启动于 http://localhost:8787 默认情况下。

部署

npx wrangler deploy

成功部署后,将输出Worker URL,例如。, https://broxy.your-subdomain.workers.dev.

配置

wrangler.toml 配置:

name = "broxy"                    # Worker name
main = "src/index.js"             # Entry file
compatibility_date = "2024-01-01" # Compatibility date

# Durable Objects binding
[[durable_objects.bindings]]
name = "BROWSER_CONNECTIONS"      # Binding name (referenced in code)
class_name = "BrowserConnection"  # Durable Object class name

# Migration config (required for first deployment)
[[migrations]]
tag = "v1"
new_sqlite_classes = ["BrowserConnection"]

# Environment variables
[vars]
DEFAULT_TIMEOUT = "30000"         # Request timeout (milliseconds)

环境变量

变量 默认值 描述
DEFAULT_TIMEOUT 30000 浏览器请求超时(毫秒)

目录结构

worker/
├── src/
│   ├── index.js         # Main entry, route dispatching
│   ├── durableObject.js # Durable Object, browser connection management
│   └── mcpHandler.js    # MCP JSON-RPC protocol handler
└── wrangler.toml        # Cloudflare Worker configuration

相关项目

项目 描述
扩展 浏览器扩展名/Tampermonkey脚本
extui 扩展UI(React+TypeScript)
万维网 静态登录页面

许可证

麻省理工学院

用法示例

完整浏览器脚本示例

// Tampermonkey/extension script
const WS_URL = 'wss://your-worker.workers.dev/connect?id=your-user-id';
let ws;

function connect() {
  ws = new WebSocket(WS_URL);
  
  ws.onopen = () => console.log('Connected to Broxy');
  
  ws.onmessage = async (event) => {
    const msg = JSON.parse(event.data);
    
    if (msg.type === 'request') {
      const result = await handleRequest(msg.data);
      ws.send(JSON.stringify({
        type: 'response',
        requestId: msg.requestId,
        result
      }));
    }
  };
  
  ws.onclose = () => setTimeout(connect, 3000);
}

async function handleRequest(req) {
  // Handle different routes
  if (req.path === '/screenshot') {
    return await captureScreenshot();
  }
  
  if (req.path === '/fetch') {
    return await fetchData(req.body.url);
  }
  
  // Default: return data as-is
  return { path: req.path, method: req.method };
}

async function captureScreenshot() {
  const canvas = document.createElement('canvas');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(document.body, 0, 0);
  
  const dataUrl = canvas.toDataURL('image/png');
  const base64 = dataUrl.split(',')[1];
  
  return {
    status: 200,
    headers: { 'Content-Type': 'image/png' },
    body: base64,
    isBase64: true
  };
}

connect();

客户来电

# Get screenshot
curl https://your-worker.workers.dev/api/your-user-id/screenshot --output screenshot.png

# Fetch data
curl -X POST https://your-worker.workers.dev/api/your-user-id/fetch \
  -H "Content-Type: application/json" \
  -d '{"url": "https://api.example.com/data"}'