browser-tools-mcp Chrome扩展架构:background.js与devtools面板通信机制

browser-tools-mcp Chrome扩展架构:background.js与devtools面板通信机制

【免费下载链接】browser-tools-mcp Monitor browser logs directly from Cursor and other MCP compatible IDEs. 【免费下载链接】browser-tools-mcp 项目地址: https://gitcode.com/gh_mirrors/br/browser-tools-mcp

引言:浏览器日志监控的架构挑战

在现代Web开发中,开发者需要实时监控浏览器日志并与IDE(集成开发环境)进行高效交互。browser-tools-mcp项目通过Chrome扩展实现了这一功能,允许开发者直接从Cursor和其他MCP兼容IDE监控浏览器日志。本文将深入剖析该扩展的核心通信机制,重点解析background.js与devtools面板之间的数据流转路径与技术实现。

Chrome扩展架构概览

browser-tools-mcp的Chrome扩展采用典型的多组件架构,主要包含以下核心模块:

mermaid

扩展各组件间的通信通过Chrome提供的多种API实现,形成了一个完整的数据采集、传输和展示链路。

background.js核心功能解析

background.js作为扩展的后台页面,负责管理长期运行的任务和跨组件通信。其主要功能包括:

1. 消息监听与路由

background.js通过chrome.runtime.onMessage.addListener建立消息监听中枢,处理来自devtools面板和其他组件的各类请求:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // 处理获取当前标签页URL的请求
  if (message.type === "GET_CURRENT_URL" && message.tabId) {
    getCurrentTabUrl(message.tabId)
      .then((url) => sendResponse({ success: true, url: url }))
      .catch((error) => sendResponse({ success: false, error: error.message }));
    return true; // 保持消息通道开放以支持异步响应
  }
  
  // 处理服务器URL更新请求
  if (message.type === "UPDATE_SERVER_URL" && message.tabId && message.url) {
    updateServerWithUrl(message.tabId, message.url)
      .then(() => sendResponse({ success: true }))
      .catch((error) => sendResponse({ success: false, error: error.message }));
    return true;
  }
  
  // 处理截图请求
  if (message.type === "CAPTURE_SCREENSHOT" && message.tabId) {
    // 实现截图逻辑...
    return true;
  }
});

2. 标签页URL跟踪与管理

为了准确监控当前活动标签页的URL变化,background.js维护了一个URL缓存机制并监听标签页事件:

// 使用Map存储每个标签页的URL
const tabUrls = new Map();

// 监听标签页更新事件
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  // 跟踪URL变化
  if (changeInfo.url) {
    console.log(`URL changed in tab ${tabId} to ${changeInfo.url}`);
    tabUrls.set(tabId, changeInfo.url);
    updateServerWithUrl(tabId, changeInfo.url, "tab_url_change");
  }
  
  // 处理页面刷新
  if (changeInfo.status === "complete") {
    if (tab.url) {
      tabUrls.set(tabId, tab.url);
      updateServerWithUrl(tabId, tab.url, "page_complete");
    }
    retestConnectionOnRefresh(tabId);
  }
});

// 监听标签页激活事件
chrome.tabs.onActivated.addListener((activeInfo) => {
  const tabId = activeInfo.tabId;
  chrome.tabs.get(tabId, (tab) => {
    if (tab && tab.url) {
      tabUrls.set(tabId, tab.url);
      updateServerWithUrl(tabId, tab.url, "tab_activated");
    }
  });
});

3. 服务器通信与身份验证

background.js负责与MCP服务器建立通信,并验证服务器身份以确保安全性:

async function validateServerIdentity(host, port) {
  try {
    const response = await fetch(`http://${host}:${port}/.identity`, {
      signal: AbortSignal.timeout(3000), // 3秒超时
    });
    
    if (!response.ok) return false;
    
    const identity = await response.json();
    // 验证服务器签名
    return identity.signature === "mcp-browser-connector-24x7";
  } catch (error) {
    console.error("Error validating server identity:", error);
    return false;
  }
}

// 更新服务器URL的实现
async function updateServerWithUrl(tabId, url, source = "background_update") {
  if (!url) return;
  
  chrome.storage.local.get(["browserConnectorSettings"], async (result) => {
    const settings = result.browserConnectorSettings || {
      serverHost: "localhost",
      serverPort: 3025,
    };
    
    // 带重试机制的服务器通信
    const maxRetries = 3;
    let retryCount = 0;
    let success = false;
    
    while (retryCount < maxRetries && !success) {
      try {
        const response = await fetch(
          `http://${settings.serverHost}:${settings.serverPort}/current-url`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ url, tabId, timestamp: Date.now(), source }),
            signal: AbortSignal.timeout(5000),
          }
        );
        
        if (response.ok) success = true;
        else {
          retryCount++;
          await new Promise(resolve => setTimeout(resolve, 500));
        }
      } catch (error) {
        retryCount++;
        await new Promise(resolve => setTimeout(resolve, 500));
      }
    }
  });
}

devtools.js通信实现

devtools.js作为开发者工具面板的核心脚本,负责与background.js通信并处理调试协议:

1. 调试器附着与控制台日志捕获

devtools.js通过Chrome调试协议(Chrome DevTools Protocol)捕获控制台日志和网络请求:

// 调试器附着逻辑
async function attachDebugger() {
  chrome.debugger.getTargets((targets) => {
    const isAlreadyAttached = targets.some(
      (target) => target.tabId === currentTabId && target.attached
    );

    if (isAlreadyAttached) {
      chrome.debugger.detach({ tabId: currentTabId }, () => {
        performAttach(); // 先分离再重新附着确保干净状态
      });
    } else {
      performAttach();
    }
  });
}

function performAttach() {
  chrome.debugger.attach({ tabId: currentTabId }, "1.3", () => {
    if (chrome.runtime.lastError) {
      console.error("Failed to attach debugger:", chrome.runtime.lastError);
      return;
    }
    
    isDebuggerAttached = true;
    chrome.debugger.onEvent.addListener(consoleMessageListener);
    
    // 启用Runtime域以捕获控制台消息
    chrome.debugger.sendCommand(
      { tabId: currentTabId },
      "Runtime.enable",
      {},
      () => {
        if (chrome.runtime.lastError) {
          console.error("Failed to enable runtime:", chrome.runtime.lastError);
          return;
        }
      }
    );
  });
}

// 控制台消息监听器
const consoleMessageListener = (source, method, params) => {
  if (source.tabId !== currentTabId) return;
  
  // 处理异常抛出事件
  if (method === "Runtime.exceptionThrown") {
    const entry = {
      type: "console-error",
      message: params.exceptionDetails.exception?.description || 
               JSON.stringify(params.exceptionDetails),
      level: "error",
      timestamp: Date.now(),
    };
    sendToBrowserConnector(entry);
  }
  
  // 处理控制台API调用
  if (method === "Runtime.consoleAPICalled") {
    // 格式化控制台消息
    let formattedMessage = params.args
      .map((arg) => {
        if (arg.type === "string") return arg.value;
        if (arg.type === "object" && arg.preview) return JSON.stringify(arg.preview);
        return arg.description || JSON.stringify(arg);
      })
      .join(" ");
    
    const entry = {
      type: params.type === "error" ? "console-error" : "console-log",
      level: params.type,
      message: formattedMessage,
      timestamp: Date.now(),
    };
    sendToBrowserConnector(entry);
  }
};

2. WebSocket连接管理

为了实现实时双向通信,devtools.js建立并维护与MCP服务器的WebSocket连接:

let ws = null;
let wsReconnectTimeout = null;
let heartbeatInterval = null;
const WS_RECONNECT_DELAY = 5000; // 5秒重连延迟
const HEARTBEAT_INTERVAL = 30000; // 30秒心跳间隔

async function setupWebSocket() {
  // 清除现有连接和定时器
  if (wsReconnectTimeout) clearTimeout(wsReconnectTimeout);
  if (heartbeatInterval) clearInterval(heartbeatInterval);
  if (ws) {
    ws.close();
    ws = null;
  }
  
  // 验证服务器身份
  const isValid = await validateServerIdentity();
  if (!isValid) {
    wsReconnectTimeout = setTimeout(setupWebSocket, WS_RECONNECT_DELAY);
    return;
  }
  
  // 建立WebSocket连接
  const wsUrl = `ws://${settings.serverHost}:${settings.serverPort}/extension-ws`;
  ws = new WebSocket(wsUrl);
  
  ws.onopen = () => {
    console.log(`WebSocket connected to ${wsUrl}`);
    // 启动心跳机制
    heartbeatInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL);
  };
  
  ws.onclose = (event) => {
    if (heartbeatInterval) clearInterval(heartbeatInterval);
    
    // 非正常关闭时自动重连
    if (!(event.code === 1000 || event.code === 1001)) {
      wsReconnectTimeout = setTimeout(setupWebSocket, WS_RECONNECT_DELAY);
    }
  };
  
  ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    // 处理服务器消息...
  };
}

// 心跳发送函数
function sendHeartbeat() {
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: "heartbeat" }));
  }
}

3. 日志处理与服务器通信

devtools.js实现了复杂的日志处理逻辑,包括数据截断、格式转换和服务器发送:

// 发送日志到浏览器连接器
async function sendToBrowserConnector(logData) {
  if (!logData) return;
  
  // 验证服务器连接
  if (!(await validateServerIdentity())) return;
  
  // 处理日志数据
  const processedData = { ...logData };
  
  // 根据日志类型处理不同字段
  if (logData.type === "network-request") {
    // 处理请求和响应体
    if (processedData.requestBody) {
      processedData.requestBody = processJsonString(
        processedData.requestBody, 
        settings.stringSizeLimit
      );
    }
    if (processedData.responseBody) {
      processedData.responseBody = processJsonString(
        processedData.responseBody, 
        settings.stringSizeLimit
      );
    }
  } else if (["console-log", "console-error"].includes(logData.type)) {
    // 处理控制台消息
    if (processedData.message) {
      processedData.message = processJsonString(
        processedData.message, 
        settings.stringSizeLimit
      );
    }
  }
  
  // 发送到服务器
  const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/extension-log`;
  fetch(serverUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      data: { ...processedData, timestamp: Date.now() },
      settings: {
        logLimit: settings.logLimit,
        queryLimit: settings.queryLimit,
        showRequestHeaders: settings.showRequestHeaders,
        showResponseHeaders: settings.showResponseHeaders,
      },
    }),
  })
  .then(response => response.json())
  .then(data => console.log("Log sent successfully:", data))
  .catch(error => console.error("Error sending log:", error));
}

// 处理JSON字符串的辅助函数
function processJsonString(jsonString, maxLength) {
  try {
    let parsed = JSON.parse(jsonString);
    
    // 如果是数组,应用大小限制处理
    if (Array.isArray(parsed)) {
      return JSON.stringify(processArrayWithSizeLimit(
        parsed, 
        settings.maxLogSize, 
        item => truncateStringsInData(item, maxLength)
      ));
    }
    
    // 截断长字符串
    return JSON.stringify(truncateStringsInData(parsed, maxLength));
  } catch (e) {
    // 非JSON字符串直接截断
    return jsonString.substring(0, maxLength) + "... (truncated)";
  }
}

组件间通信流程

background.js与devtools.js之间的通信是扩展功能实现的关键,主要通过以下几种机制完成:

1. 消息传递机制

Chrome扩展提供了chrome.runtime.sendMessagechrome.runtime.onMessageAPI用于组件间通信。这种机制支持一次性请求-响应模式,适用于需要即时响应的场景:

mermaid

2. 长期数据共享

对于需要持久化或跨会话共享的数据,扩展使用chrome.storage.localAPI:

// 在devtools.js中保存设置
chrome.storage.local.set({
  browserConnectorSettings: settings
}, () => {
  console.log("Settings saved successfully");
});

// 在background.js中加载设置
chrome.storage.local.get(["browserConnectorSettings"], (result) => {
  if (result.browserConnectorSettings) {
    settings = { ...defaultSettings, ...result.browserConnectorSettings };
  }
});

3. 调试协议通信

通过Chrome调试协议,devtools.js能够直接与目标页面通信,捕获控制台日志和网络请求:

mermaid

数据流转完整流程

结合上述所有组件和通信机制,browser-tools-mcp扩展实现了从浏览器到IDE的完整日志监控流程:

mermaid

错误处理与健壮性设计

为确保扩展在各种网络环境和使用场景下的稳定性,实现了多层次的错误处理机制:

1. 连接重试机制

无论是HTTP请求还是WebSocket连接,都实现了带指数退避策略的重试逻辑:

// HTTP请求重试逻辑
async function withRetry(operation, maxRetries = 3, delayMs = 500) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, delayMs * (i + 1)));
      }
    }
  }
  
  throw lastError;
}

2. 数据大小限制

为防止过大的数据传输影响性能,实现了多层级的数据大小控制:

// 处理数组大小限制的函数
function processArrayWithSizeLimit(array, maxTotalSize, processFunc) {
  let currentSize = 0;
  const result = [];
  
  for (const item of array) {
    const processedItem = processFunc(item);
    const itemSize = calculateObjectSize(processedItem);
    
    if (currentSize + itemSize > maxTotalSize) break;
    
    result.push(processedItem);
    currentSize += itemSize;
  }
  
  return result;
}

3. 服务器身份验证

为防止连接到恶意服务器,实现了严格的服务器身份验证:

async function validateServerIdentity() {
  try {
    const response = await fetch(
      `http://${settings.serverHost}:${settings.serverPort}/.identity`,
      { signal: AbortSignal.timeout(3000) }
    );
    
    if (!response.ok) return false;
    
    const identity = await response.json();
    return identity.signature === "mcp-browser-connector-24x7";
  } catch (error) {
    return false;
  }
}

部署与使用指南

要使用browser-tools-mcp扩展,需按照以下步骤进行部署和配置:

1. 获取源码

git clone https://gitcode.com/gh_mirrors/br/browser-tools-mcp
cd browser-tools-mcp

2. 安装依赖并构建

# 安装服务器依赖
cd browser-tools-server
npm install
npm run build

# 安装MCP服务器依赖
cd ../browser-tools-mcp
npm install
npm run build

3. 启动服务器

# 启动浏览器连接器服务器
cd browser-tools-server
npm start

# 启动MCP服务器
cd ../browser-tools-mcp
npm start

4. 安装Chrome扩展

  1. 打开Chrome浏览器,访问chrome://extensions
  2. 启用"开发者模式"
  3. 点击"加载已解压的扩展程序"
  4. 选择项目中的chrome-extension目录

5. 配置IDE集成

在支持MCP协议的IDE(如Cursor)中,配置连接到本地MCP服务器(localhost:3025),即可开始监控浏览器日志。

总结与扩展方向

browser-tools-mcp Chrome扩展通过精心设计的background.js与devtools面板通信机制,实现了浏览器日志与IDE的无缝集成。其核心优势在于:

  1. 实时性:WebSocket连接和调试协议确保日志实时传输
  2. 可靠性:多层重试机制和错误处理保证系统稳定性
  3. 安全性:服务器身份验证防止恶意连接
  4. 可扩展性:模块化设计便于添加新功能

未来可以从以下方向进一步扩展:

  1. 增强数据过滤:实现更精细的日志过滤和搜索功能
  2. 性能优化:减少大型页面的内存占用
  3. 扩展协议支持:添加对更多调试协议命令的支持
  4. 用户体验提升:增强可视化界面和交互功能

通过理解这一架构设计和通信机制,开发者不仅可以更好地使用browser-tools-mcp,还能为类似的浏览器-IDE集成工具提供参考实现。

【免费下载链接】browser-tools-mcp Monitor browser logs directly from Cursor and other MCP compatible IDEs. 【免费下载链接】browser-tools-mcp 项目地址: https://gitcode.com/gh_mirrors/br/browser-tools-mcp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值