剧作家MCP代理
Playwright的模型上下文协议(MCP)代理,增加了持久存储、会话管理和优化的响应处理。
建筑
代理由两个基于UV的Python组件组成:
- MCP客户端 -向上游客户端公开Playwright工具的stdio MCP服务器
- HTTP服务器 -管理Playwright MCP子流程,处理会话,并将数据持久化到SQLite
MCP Client --> MCP Client --> HTTP Server --> Playwright MCP
(upstream) (proxy) (FastAPI) (subprocess)
|
v
SQLite特性
- 持久存储:使用UUID引用存储在SQLite中的所有请求和响应
- 会话管理:可以独立创建和管理的基于UUID的浏览器会话
- 优化响应:返回元数据+ref_id,而不是完整的有效载荷
- 内容检索:通过ref_id获取页面快照和控制台日志
- 基于差异的内容 (第二阶段):
get_content仅返回自上次读取以来的更改
- 基于哈希的变化检测 - 跨服务器重启的游标持久性 - reset_cursor 获取完整内容的参数
- 会话恢复 (第7阶段):会话在服务器重启后仍然存在
- 每30秒自动进行一次状态快照(URL、Cookie、本地存储、会话存储、视口) - 启动时孤立会话检测 - 恢复会话并完全恢复状态 - 分类:可恢复、过时或已关闭(基于快照年龄)
- 子流程管理:Playwright MCP的自动健康监测和重启
安装
本地开发安装
# Install in editable mode with UV
uv pip install -e .
# Or install with dev dependencies
uv pip install -e ".[dev]"全局安装(建议用于生产)
选项1:UV工具安装(推荐)
作为全局UV工具安装-命令将在系统范围内可用:
# Install from the current directory
uv tool install .
# Or install from a Git repository
uv tool install git+https://github.com/yourusername/playwright-mcp-proxy.git
# Commands are now available globally:
playwright-proxy-server
playwright-proxy-client稍后更新:
uv tool upgrade playwright-mcp-proxy要卸载,请执行以下操作:
uv tool uninstall playwright-mcp-proxy选项2:全系统UV管道安装
# Install to UV's global Python environment
uv pip install --system .快速开始
1.启动HTTP服务器
# Using the installed script
playwright-proxy-server
# Or using Python module
python -m playwright_mcp_proxy.server服务器将:
- 在以下位置初始化SQLite数据库
./proxy.db - 生成剧作家MCP子流程
- 倾听
http://localhost:34501
2.配置MCP客户端
添加到您的MCP客户端配置中(例如,Claude Desktop、VS Code):
如果全局安装(uv工具安装):
{
"mcpServers": {
"playwright-proxy": {
"command": "playwright-proxy-client"
}
}
}如果使用本地开发安装:
{
"mcpServers": {
"playwright-proxy": {
"command": "uv",
"args": [
"run",
"--directory",
"/path/to/playwright-mcp-proxy",
"playwright-proxy-client"
]
}
}
}或者直接使用Python模块:
{
"mcpServers": {
"playwright-proxy": {
"command": "python",
"args": [
"-m",
"playwright_mcp_proxy.client"
]
}
}
}3.从MCP客户端使用
# Create a new browser session
> create_new_session()
Created session: abc-123-def-456
# Navigate and take snapshot
> browser_navigate(url="https://example.com")
Request completed successfully
Ref ID: xyz-789
Page snapshot available. Use get_content('xyz-789')
# Get the page content
> get_content(ref_id="xyz-789")
[Full accessibility tree snapshot]
# Search within content
> get_content(ref_id="xyz-789", search_for="Example Domain")
- heading "Example Domain"会话恢复(第7阶段)
会话现在可以在服务器重启后存活!代理每30秒自动捕获一次浏览器状态,并可以在崩溃或重新启动后恢复会话。
运作原理
- 自动快照:当会话处于活动状态时,服务器会捕获:
- 当前URL - Cookie - 本地存储 - 会话存储 - 视口大小
- 启动检测:服务器重新启动时,它会自动:
- 检测孤立会话(在关闭前标记为“活动”) - 根据快照年龄对它们进行分类: - 可恢复的:最近快照(\24小时)-可能无法可靠工作 - 关闭:没有可用的快照-无法恢复
- 手动简历:用户可以通过工具列出和恢复会话
用法示例
# Before restart - working in a session
> browser_navigate(url="https://example.com")
> browser_type(element="search box", ref="e1", text="test query")
# Server crashes or restarts...
# After restart - resume your session
> list_sessions(state="recoverable")
Session ID: abc-123-def-456
State: recoverable
URL: https://example.com
Snapshot age: 45 seconds
> resume_session(session_id="abc-123-def-456")
✓ Session resumed successfully
Restored URL: https://example.com
# Your browser is back at the same page with all state restored!HTTP API
您还可以直接使用HTTP端点:
# List recoverable sessions
curl http://localhost:34501/sessions?state=recoverable
# Resume a session
curl -X POST http://localhost:34501/sessions/abc-123-def-456/resume配置
SESSION_SNAPSHOT_INTERVAL:快照的频率(默认值:30秒)MAX_SESSION_AGE:可恢复会话的最大期限(默认值:24小时)MAX_SESSION_SNAPSHOTS:要保留多少个快照(默认值:10)AUTO_REHYDRATE:启动时自动恢复会话(默认值:false)
局限性
- 通过以下方式捕获的Cookie
document.cookie(无httpOnly/secure标志) - 无法捕获JavaScript堆、正在运行的计时器或挂起的请求
- 如果只恢复存储而不恢复完整上下文,某些站点可能会中断
- 已捕获但当前未恢复的视口大小(剧作家限制)
配置
通过环境变量进行配置(前缀: PLAYWRIGHT_PROXY_):
# Server
PLAYWRIGHT_PROXY_SERVER_HOST=localhost
PLAYWRIGHT_PROXY_SERVER_PORT=34501
# Database
PLAYWRIGHT_PROXY_DATABASE_PATH=./proxy.db
# Playwright
PLAYWRIGHT_PROXY_PLAYWRIGHT_BROWSER=chrome # chrome, firefox, webkit
PLAYWRIGHT_PROXY_PLAYWRIGHT_HEADLESS=false
# Subprocess Management
PLAYWRIGHT_PROXY_HEALTH_CHECK_INTERVAL=30
PLAYWRIGHT_PROXY_MAX_RESTART_ATTEMPTS=3
PLAYWRIGHT_PROXY_RESTART_WINDOW=300
PLAYWRIGHT_PROXY_SHUTDOWN_TIMEOUT=5
# Session Recovery (Phase 7)
PLAYWRIGHT_PROXY_SESSION_SNAPSHOT_INTERVAL=30 # Seconds between snapshots
PLAYWRIGHT_PROXY_MAX_SESSION_AGE=86400 # Max age (24h) for recoverable sessions
PLAYWRIGHT_PROXY_AUTO_REHYDRATE=false # Auto-resume sessions on startup
PLAYWRIGHT_PROXY_MAX_SESSION_SNAPSHOTS=10 # Keep last N snapshots per session
# Logging
PLAYWRIGHT_PROXY_LOG_LEVEL=INFO或者创建一个 .env 文件(参见 .env.example).
可用工具
会话管理
create_new_session()-创建新的浏览器会话,返回session_idlist_sessions(state?)-列出所有会话,可选择按状态筛选(第7阶段)resume_session(session_id)-重启后恢复可恢复/过时的会话(第7阶段)
内容检索
get_content(ref_id, search_for?, reset_cursor?)-从以前的请求中获取页面快照
- 第2阶段:仅返回自上次读取以来的更改(基于哈希的差异) - 使用 reset_cursor=true 获取完整内容并重置差异跟踪 - 空响应表示未检测到任何更改
get_console_content(ref_id, level?)-获取控制台日志(按debug/info/warn/error筛选)
剧作家工具(代理)
所有标准Playwright MCP工具均可用:
browser_navigate(url)browser_snapshot()browser_click(element, ref)browser_type(element, ref, text, submit?)browser_console_messages(onlyErrors?)browser_close()- …以及更多
响应返回元数据+ref_id,而不是完整内容。
数据库模式
- 会话 -浏览器会话(UUID、状态、时间戳、恢复字段)
- 请求: -所有代理请求(ref_id、工具、参数)
- 回应 -作为blob的完整响应(结果、页面快照、控制台日志)
- console_logs -标准化控制台条目(级别、消息、位置)
- 差异 -用于第2阶段差异支持
- 会话快照 -第7阶段状态快照(URL、Cookie、存储、视口)
性能比较:代理与直接编剧MCP
用以下方式测量 uv run pytest tests/test_comparison.py -v -s -m integration --将代理与直接Playwright MCP子流程通信进行比较的7种场景。
简单导航(example.com)
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航 | 代理 | 67 | 301 | - |
| 快照(元数据) | 代理 | 6 | 300 | 75 |
| get_content | 代理 | 2 | 440 | 102 |
| 导航 | 直接 | 1711 | - | - |
| 快照(完整) | 直接 | 3 | 787 | 181 |
代理元数据响应:300B,而直接完整快照:787B
Diff抑制(重复读取)
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 第一次读取(完整) | 代理 | 2 | 440 | 102 |
| 第二次读取(diff=空) | 代理 | 2 | 14 | 1 |
| 第三次读取(重置=完全) | 代理 | 2 | 440 | 102 |
| 第一张快照 | 直接 | 3 | 469 | 102 |
| 第二张快照 | 直接 | 5 | 787 | 181 |
| 第三张快照 | 直接 | 3 | 502 | 110 |
3读代币节省:节省188个代币(48%) --代理205 vs直接393
多页导航
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航(example.com) | 代理 | 303 | - | - |
| 快照(example.com) | 代理 | 1013 | 300 | 75 |
| get_content(example.com) | 代理 | 4 | 440 | 102 |
| 导航(www.iana.org) | 代理 | 1307 | - | - |
| 快照(www.iana.org) | 代理 | 1021 | 300 | 75 |
| get_content(www.iana.org) | 代理 | 3 | 7274 | 1749 |
| get_content(第1页重播) | 代理 | 3 | 411 | 102 |
| 导航(example.com) | 直接 | 1508 | - | - |
| 快照(example.com) | 直接 | 3 | 469 | 102 |
| 导航(www.iana.org) | 直接 | 6434 | - | - |
| 快照(www.iana.org) | 直接 | 7 | 7024 | 1677 |
代理持久性:导航到第2页后检索到第1页内容(3ms) Direct没有持久性——导航后前一页内容消失
内容搜索筛选
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 完整内容 | 代理 | 2 | 440 | 102 |
| 已筛选(search_for) | 代理 | 5 | 97 | 19 |
| 快照(已满,无筛选器) | 直接 | 4 | 469 | 102 |
search_for减少:有效负载减少81%,节省约83个令牌 直接MCP没有搜索过滤——总是返回完整的可访问性树
错误处理
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) |
|---|---|---|---|
| 无效的URL导航 | 代理 | 623 | 283 |
| 无效的URL导航 | 直接 | 1106 | - |
两条路径都能优雅地处理错误,而不会崩溃
复杂页面:谷歌搜索
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航(google.com) | 代理 | 1449 | - | - |
| 类型搜索查询 | 代理 | 3064 | - | - |
| 按Enter | 代理 | 1179 | - | - |
| 快照结果(元数据) | 代理 | 2043 | 300 | 75 |
| get_content(第一次读取) | 代理 | 3 | 78262 | 17676 |
| get_content(第二次读取,差异) | 代理 | 2 | 14 | 1 |
| get_content(search_for) | 代理 | 3 | 13711 | 3285 |
| 导航(google.com) | 直接 | 1877 | - | - |
| 类型搜索查询 | 直接 | 2547 | - | - |
| 按Enter键 | 直接 | 2198 | - | - |
| 快照结果(完整) | 直接 | 15 | 3181 | 772 |
| 快照(第二,再次完整) | 直接 | 6 | 3181 | 772 |
搜索“剧作家”减少:81% --3285个代币vs 17676个完整代币
复杂页面:YouTube搜索
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航(youtube.com) | 代理 | 1618 | - | - |
| 快照主页 | 代理 | 79 | 2502 | 625 |
| 类型搜索查询 | 代理 | 32 | - | - |
| 按Enter | 代理 | 1047 | - | - |
| 快照结果(元数据) | 代理 | 22 | 300 | 75 |
| get_content(第一次读取) | 代理 | 3 | 2929 | 703 |
| get_content(第二次读取,差异) | 代理 | 2 | 14 | 1 |
| get_content(search_for) | 代理 | 3 | 175 | 40 |
| 导航(youtube.com) | 直接 | 2201 | - | - |
| 快照主页 | 直接 | 14 | 1956 | 460 |
| 类型搜索查询 | 直接 | 26 | - | - |
| 按Enter键 | 直接键 | 1019 | - | - |
| 快照结果(完整) | 直接 | 15 | 1956 | 460 |
| 快照(第二张,再次完整) | 直接 | 13 | 1956 | 460 |
2读差异节省:216个代币(23%) --代理704与直接920 整页:703个标记|筛选的“剧作家”:40个标记|抑制差异:1个标记
Chrome扩展路径(Chrome中的claude)
通过以下方式测量 mcp__claude-in-chrome__* 针对具有热缓存的实时Chrome浏览器的工具。这些代表了第三种自动化路径——使用现有的Chrome实例,而不是生成无头浏览器。
简单导航(example.com)
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航 | 代理 | 67 | 301 | - |
| get_content | 代理 | 2 | 440 | 102 |
| 导航 | 直接 | 1711 | - | - |
| 快照(完整) | 直接 | 3 | 787 | 181 |
| 导航 | chrome | 403 | - | - |
| read_page | chrome | 3200 | 213 | 53 |
| get_page_text | chrome | 3400 | 183 | 46 |
Chrome导航速度很快(热缓存),但read_page的扩展往返开销约为3s
谷歌搜索
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| 导航(主页) | 代理 | 1449 | - | - |
| get_content(第一次读取) | 代理 | 3 | 78262 | 17676 |
| get_content(第二次读取,差异) | 代理 | 2 | 14 | 1 |
| 快照结果(完整) | 直接 | 15 | 3181 | 772 |
| 导航(主页) | chrome | 1375 | - | - |
| read_page(主页) | chrome | 1400 | 1800 | 450 |
| 导航(搜索结果) | chrome | 3921 | - | - |
| read_page(搜索结果) | chrome | 15800 | 12000 | 3000 |
谷歌搜索结果上的Chrome read_page:15.8s/12KB,由于深度为2的复杂DOM
YouTube搜索
| 操作 | 路径 | 延迟(毫秒) | 有效负载(字节) | 估计值。代币 |
|---|---|---|---|---|
| get_content(第一次读取) | 代理 | 3 | 2929 | 703 |
| get_content(第二次读取,差异) | 代理 | 2 | 14 | 1 |
| 快照结果(完整) | 直接 | 15 | 1956 | 460 |
| 导航(主页) | chrome | 3708 | - | - |
| read_page(主页) | chrome | 2200 | 11000 | 2750 |
| 导航(搜索结果) | chrome | 1246 | - | - |
| read_page(搜索结果) | chrome | 1300 | 10000 | 2500 |
YouTube SPA路由使后续导航速度更快(1.2秒);chrome read_page有效载荷很大(10-11KB)
三路路径比较总结
| 路径 | 导航延迟 | 内容延迟 | 令牌效率 | 持久性 |
|---|---|---|---|---|
| 代理 | 中等(67-1449ms) | 快速(通过ref_id为2-5ms) | 最佳(差异:重复1个标记) | 完整的SQLite历史记录 |
| 直接的 | 慢速(1508-6434ms) | 快速(3-15ms) | 中等(每次满载) | 无 |
| 铬 | 快速中等(403-3921ms) | 慢速(1300-15800ms) | 可变(53-3000个令牌) | 无 |
代理在代币效率上获胜 --差异抑制和搜索过滤将令牌减少了48-83%。 Chrome在导航速度上获胜 --温缓存避免了浏览器冷启动。 内容检索速度上的直接胜利 --无需延长往返开销。
主要代理优势
| 特性 | 描述 |
|---|---|
| 仅元数据响应 | 初始工具调用返回ref_id,而不是完整内容——小而固定大小的有效载荷 |
| 差异抑制 | 重复读取在内容不变时返回空值——节省大量代币 |
| search_for筛选 | 仅检索匹配的行--减少目标查找的负载 |
| 持久性(SQLite) | 存储的所有快照--通过ref_id检索任何历史页面 |
| 会话管理 | 具有生命周期跟踪和错误审计跟踪的命名会话 |
运行比较测试
# Start the proxy server first
uv run playwright-proxy-server
# Run the proxy vs direct comparison suite (requires internet access)
uv run pytest tests/test_comparison.py -v -s -m integration
# Run the chrome path comparison report (uses pre-recorded measurements)
uv run pytest tests/test_chrome_comparison.py -v -s -m chrome_comparison发展
运行测试
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=playwright_mcp_proxy
# Run specific test file
uv run pytest tests/test_database.py代码格式化
# Check formatting
uv run ruff check .
# Auto-fix issues
uv run ruff check --fix .手动测试
# Terminal 1: Start server
uv run playwright-proxy-server
# Terminal 2: Start client (stdio)
uv run playwright-proxy-client
# Terminal 3: Send MCP requests via stdio
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | uv run playwright-proxy-client路线图
第一阶段(完成)
- \[x\] 核心基础设施
- \[x\] SQLite持久性
- \[x\] 具有会话管理的HTTP服务器
- \[x\] 带工具代理的MCP客户端
- \[x\] 子流程生命周期管理
- \[x\] 基础测试(6次测试)
第2阶段(完成)
- \[x\] 基于Diff的内容检索
- \[x\]
get_content仅返回自上次读取以来的更改 - \[x\]
reset_cursor参数 - \[x\] 基于哈希的变化检测
- \[x\] 跨服务器重启的游标持久性
- \[x\] 综合测试(8项附加测试)
第7阶段(完成)
- \[x\] 会话状态持久性(URL、Cookie、本地存储、会话存储、视口)
- \[x\] 每30秒自动定期快照
- \[x\] 启动时孤立会话检测
- \[x\] 会话分类(可恢复/过时/已关闭)
- \[x\] 通过会话补液重新启动恢复
- \[x\]
list_sessions(state)工具 - \[x\]
resume_session(session_id)工具 - \[x\] 综合测试(共16项测试)
第8阶段(完成)
- \[x\] Chrome扩展路径(claude in Chrome)测量基础设施
- \[x\] 3个场景的预先记录测量值(例如.com、谷歌搜索、YouTube搜索)
- \[x\] 三向性能比较表(代理、直接和chrome)
- \[x\]
chrome_comparisonpytest标记用于选择测试
故障排除
服务器无法启动
- 检查端口34501是否可用
- 验证是否安装了Node.js(适用于Playwright MCP)
- 检查控制台输出中的日志
子进程不断重新启动
- 检查
~/.npm有@playwright/mcp@latest安装 - 验证浏览器二进制文件是否已安装
- 查看stderr日志
MCP客户端无法连接
- 确保HTTP服务器在本地主机34501上运行
- 检查防火墙设置
- 验证MCP客户端中的配置
许可证
麻省理工学院
贡献
欢迎投稿!拜托:
- 遵循现有的代码样式(Ruff)
- 为新功能添加测试
- 更新文档
- 确保向后兼容性
贡献领域:
- 会话恢复的集成测试
- 性能优化
- 额外的浏览器状态捕获(iframe支持、web workers等)
- 强化补液策略
- Bug修复和改进
看 CLAUDE.md 了解架构细节和开发模式。
