运行在 Amazon Bedrock AgentCore 中的 AI agent 拥有云端的计算能力,但用户真正需要它操作的工具和文件,往往仍然在自己的笔记本电脑上。MCP bridge 解决的正是这个边界问题:让云端 agent 通过浏览器扩展和 Chrome Native Messaging,调用本地 MCP server,同时不要求用户开放端口或连接 VPN。
这类架构的关键不是简单地“把本地服务暴露到公网”,而是复用 agent 与浏览器之间已经存在的 WebSocket 连接,通过签名消息传递 MCP 请求和响应。
端到端连接是怎样建立的
整个链路可以拆成四个角色:
- AgentCore-hosted agent:运行在 AWS 云端,负责规划任务并决定调用哪个 MCP tool。
- WebSocket bridge:维护云端 agent 与用户浏览器之间的长连接,转发经过签名的请求和响应。
- 浏览器扩展:接收云端请求,并通过 Chrome Native Messaging 与本机进程通信。
- 本地 MCP server:访问用户授权的文件、命令行工具或其他桌面资源。
一次调用大致经过以下路径:
AgentCore agent
-> signed MCP request over WebSocket
Browser extension
-> Chrome Native Messaging
Local MCP bridge process
-> MCP protocol
Local MCP server
-> tool result
Local MCP bridge process
-> Chrome Native Messaging
Browser extension
-> signed response over WebSocket
AgentCore agent
本地 MCP server 不需要监听公网端口。Chrome Native Messaging 使用操作系统进程间通信机制启动本地 host,浏览器扩展负责把 WebSocket 消息和本地标准输入输出连接起来。
为什么使用现有 WebSocket 连接
如果让云端 agent 直接访问笔记本,通常需要处理公网地址、端口转发、防火墙、NAT、VPN 或反向代理。这些组件不仅增加部署成本,也会扩大本地工具的攻击面。
通过浏览器建立的 WebSocket 连接则可以承担“反向通道”的角色:
- 本地设备主动建立连接,不需要开放入站端口。
- 云端请求必须通过已认证的会话到达浏览器。
- MCP 请求可以附带用户、会话和请求标识,便于做授权与审计。
- 断线后可以重新连接,而不必重新配置网络入口。
需要注意的是,WebSocket 只解决传输问题,不自动解决身份验证和授权问题。bridge 仍然需要验证消息来源、限制可调用的工具,并防止旧消息重放。
用签名消息保护请求
一个实用的消息封装可以包含版本、会话 ID、请求 ID、时间戳、过期时间和请求体。下面是一个可以改造的 Python 示例,展示如何使用 HMAC 对 MCP bridge 消息签名。生产环境应将密钥放在操作系统安全存储或其他合适的密钥管理系统中,而不是硬编码在代码里。
运行前设置环境变量:
export MCP_BRIDGE_SECRET='replace-with-a-random-secret'
python sign_bridge_message.py
示例代码:
# sign_bridge_message.py
import hashlib
import hmac
import json
import os
import time
import uuid
secret = os.environ["MCP_BRIDGE_SECRET"].encode("utf-8")
message = {
"version": 1,
"type": "mcp.request",
"session_id": "session-123",
"request_id": str(uuid.uuid4()),
"issued_at": int(time.time()),
"expires_at": int(time.time()) + 30,
"payload": {
"method": "tools/call",
"params": {
"name": "read_local_file",
"arguments": {"path": "/Users/example/Documents/report.txt"},
},
},
}
canonical = json.dumps(
message, separators=(",", ":"), sort_keys=True
).encode("utf-8")
signature = hmac.new(secret, canonical, hashlib.sha256).hexdigest()
wire_message = {
"message": message,
"signature": signature,
}
print(json.dumps(wire_message, indent=2))
接收端需要执行对应的校验:
- 重新生成规范化 JSON,避免字段顺序差异导致签名不一致。
- 使用常量时间比较函数验证签名。
- 检查
session_id是否属于当前浏览器连接。 - 检查
expires_at,并记录已经处理过的request_id,防止重放。 - 在执行 MCP tool 前再次检查工具名称和参数是否符合策略。
对于跨组件部署,也可以使用非对称签名。这样浏览器端只保存公钥,签名私钥保留在受信任的服务端,降低本地密钥泄露后的影响范围。
Chrome Native Messaging 的本地边界
Chrome Native Messaging host 通常是一个本地可执行程序。扩展通过标准输入发送消息,通过标准输出接收消息。协议实现时要特别注意消息边界:不能假设一次 read 就能读到完整 JSON,也不能把日志写入标准输出,否则会污染协议流。
一个极简的本地 host 原型如下:
# native_host.py
import json
import struct
import sys
def read_message():
header = sys.stdin.buffer.read(4)
if len(header) != 4:
return None
size = struct.unpack("<I", header)[0]
data = sys.stdin.buffer.read(size)
if len(data) != size:
raise RuntimeError("incomplete native message")
return json.loads(data.decode("utf-8"))
def write_message(value):
data = json.dumps(value, separators=(",", ":")).encode("utf-8")
sys.stdout.buffer.write(struct.pack("<I", len(data)))
sys.stdout.buffer.write(data)
sys.stdout.buffer.flush()
while True:
request = read_message()
if request is None:
break
# 实际实现中,这里应调用本地 MCP client,而不是直接执行任意命令。
response = {
"request_id": request.get("request_id"),
"ok": True,
"result": {"status": "forward-to-local-mcp-server"},
}
write_message(response)
这个示例只演示消息 framing,不应直接用于生产环境。生产版本还需要:
- 只允许扩展启动经过注册的 host。
- 对文件路径做目录白名单和规范化检查。
- 禁止把用户输入直接拼接成 shell 命令。
- 限制单条消息大小、调用时长和并发数。
- 为每次 tool 调用记录审计日志。
- 对高风险操作增加用户确认,例如删除文件或执行外部程序。
MCP tool 的权限模型
本地工具的能力通常远大于普通 HTTP API。一个 read_file 工具可能读取用户隐私数据,一个 run_command 工具甚至可以改变整台机器的状态。因此,bridge 不应只检查“消息是否签名”,还需要实现能力级授权。
可以把工具策略写成配置文件,并在本地 bridge 执行前进行匹配:
allowed_tools:
- name: read_local_file
roots:
- /Users/example/Documents/project
max_bytes: 1048576
- name: list_directory
roots:
- /Users/example/Documents/project
blocked_tools:
- run_command
- delete_file
require_user_confirmation:
- write_local_file
- open_external_url
这里的路径只是示例,实际部署时应替换成用户明确授权的目录。路径检查不能只依赖字符串前缀,还应先进行规范化,并处理符号链接、大小写差异和平台特定路径规则。
断线、重试与请求关联
浏览器标签页关闭、电脑休眠或网络切换都会导致 WebSocket 断开。bridge 需要明确区分以下几种状态:
- 尚未发送:请求可以在本地队列中等待重新连接。
- 已发送但未确认:不能无条件重试,某些工具可能已经产生副作用。
- 已收到结果:应使用
request_id去重,避免重复返回。 - 已过期:直接拒绝,并让云端 agent 重新规划。
对于只读操作,可以设计幂等重试。对于写入文件、发送邮件或启动进程等副作用操作,建议使用显式确认和一次性请求令牌。云端 agent 也应能处理“本地设备暂时不可用”这一正常状态,而不是无限重试。
采用时的检查清单
在实现 MCP bridge 前,可以先确认以下边界:
- WebSocket 是否使用 TLS,并且连接建立时完成用户认证?
- 每条消息是否有会话绑定、过期时间和唯一请求 ID?
- 本地 host 是否只向标准输出写协议数据?
- MCP tool 是否有明确的允许列表,而不是默认全部开放?
- 文件访问是否限制在用户授权目录内?
- 高风险工具是否需要本地用户确认?
- 断线重试是否考虑了工具的副作用?
- 是否能审计“哪个 agent、哪个用户、在什么时候调用了哪个本地工具”?
MCP bridge 的价值在于把云端 agent 的推理能力和本地环境的实际资源连接起来。它的安全边界应围绕“谁能发起请求、请求能调用什么、调用影响哪些本地资源”来设计。只要坚持不开放入站端口、验证每条消息、限制工具能力,并认真处理重放和重试,浏览器就可以成为云端 agent 访问本地 MCP 工具的一条可控通道。