Safari 原生 MCP:零依赖的浏览器自动化
macOS 自带的 safaridriver 内置 MCP 模式,一条命令就能把真实 Safari 变成可调用工具集。不需要 Playwright、不需要下载 Chromium、不需要第三方桥接服务。
这个方案解决什么问题
| 常见痛点 | 本方案的做法 |
|---|---|
| Playwright 要下 100MB+ 浏览器内核 | 用系统 Safari,零下载 |
| 静态抓取拿不到 JS 渲染后的内容 | 真实浏览器渲染后提取文本 |
| 抓不到接口请求 | 内置网络请求列表 + 单条详情(含 headers/body/timing) |
| 登录态页面无法访问 | 直接复用你已登录的 Safari |
| 看 console 报错要开开发者工具 | 一个调用直接拿到日志 |
前置条件(只需一次)
Safari → 菜单栏「开发」→ 勾选「允许远程自动化」。
- 菜单里没有「开发」这一项:先到 Safari 设置 → 高级 → 勾选「显示网页开发者功能」。
- 这一步会要求输入管理员密码,属于 macOS 系统级授权。
- 改完必须 ⌘Q 完全退出 Safari 再打开,否则不生效(这是最常见的失败原因)。
判断是否真的生效:看能否建会话,不要以菜单是否打勾为准。
接入(两种任选)
方式一:MCP 客户端配置(推荐)
在客户端的 MCP 配置里加:
{
"mcpServers": {
"safari": {
"command": "/usr/bin/safaridriver",
"args": ["--mcp"]
}
}
}
方式二:命令行直接调(客户端还没配好时用它应急)
node scripts/safari-cli.mjs goto https://example.com # 打开页面
node scripts/safari-cli.mjs content # 读正文(markdown)
node scripts/safari-cli.mjs shot /绝对路径/shot.png # 截图
node scripts/safari-cli.mjs tools # 列出全部工具
脚本零依赖,只用 Node 内置模块,直连 stdio 协议。
17 个工具速查
读(优先用这些,别截图喂模型)
| 工具 | 说明 |
|---|---|
get_page_content |
首选读页方式。WebKit 原生文本提取,支持 markdown/text,可选区域、子框架、链接、无障碍属性、select 选项 |
page_info |
当前页 URL / 标题 / 加载状态 |
list_tabs |
全部标签页的 handle、标题、URL |
动
| 工具 | 说明 |
|---|---|
navigate_to_url |
导航并直接返回页面内容;无标签页时自动新建 |
wait_for_navigation |
等待加载完成,返回最终 URL 与标题 |
page_interactions |
批量 DOM 交互:click / type / keyPress / scroll / hover / selectText / selectMenuItem / highlightText |
evaluate_javascript |
页面内执行函数体 |
取证
| 工具 | 说明 |
|---|---|
screenshot |
截图存 PNG,支持整页与指定节点 |
browser_console_messages |
控制台日志,可按等级过滤 |
list_network_requests |
请求摘要:URL / method / status / 耗时 |
get_network_request |
单条详情:headers、body、timing |
browser_dialogs |
列出并应答 alert / confirm / prompt |
环境与标签页
| 工具 | 说明 |
|---|---|
create_tab / switch_tab / close_tab |
标签页增删切换 |
set_viewport_size |
设置视口尺寸(响应式测试) |
set_emulated_media |
模拟 screen / print 媒体类型 |
五个实战配方
# 1. 读一篇文章(最常用)
navigate_to_url("https://example.com/文章")
get_page_content({ format: "markdown" })
# 2. 抓某个页面的接口数据
navigate_to_url("https://某站点")
list_network_requests({ filter: "api" }) # 找目标请求
get_network_request({ request_id: "<上一步的 id>" })
# 3. 登录后表单操作(批量,别一个动作一次调用)
page_interactions({ interactions: [
{ action: "type", selector: "input[name=user]", text: "账号" },
{ action: "type", selector: "input[name=pwd]", text: "密码" },
{ action: "click", selector: "button[type=submit]" }
]})
wait_for_navigation({ timeout_seconds: 20 })
get_page_content({ format: "markdown" })
# 4. 页面报错排查
browser_console_messages({ level_filter: "error" })
# 5. 移动端视口下的排版验收
set_viewport_size({ width: 390, height: 844 })
screenshot({ savePath: "/绝对路径/mobile.png", full_page: true })
三个必须记住的坑
screenshot的savePath必须是绝对路径。 传相对路径会报The file "xxx" doesn't exist—— 它按 safaridriver 自己的 cwd 解析,不是你的 cwd。evaluate_javascript忘记return会拿到undefined。 传的是函数体,不是表达式,例如要写const a = 1; return a * 2;。- 别用第三方 safari-bridge 的失败反推成"权限问题"。 同一个 Safari、同一个驱动,基于 WebDriver 会话的第三方桥可能一直超时,而原生
--mcp直接可用。判定顺序:先换原生通道,再查授权。
排错
| 现象 | 原因 | 处理 |
|---|---|---|
建立会话超时 / session not created |
允许远程自动化没生效 | ⌘Q Safari 重开;确认菜单项已勾选 |
| 工具全部报连接失败 | 驱动进程没起 | node scripts/healthcheck.mjs 自检 |
The file "xxx" doesn't exist |
截图相对路径 | 改绝对路径 |
evaluate 返回 undefined |
没写 return | 函数体里显式 return |
| 页面内容为空 / 骨架 | 用了静态抓取 | 改用 navigate_to_url + get_page_content |
自检
node scripts/healthcheck.mjs
输出四行结论:驱动是否存在、MCP 握手是否成功、工具数量、以及会话是否可用(后者即"远程自动化是否真的开了")。零依赖,不读任何本地配置。
边界说明
- 仅 macOS 可用(
safaridriver是系统组件)。 - 一次只能驱动一个 Safari 实例,多任务需要自行排队。
- 不做反爬规避、不伪造登录、不绕过验证码——它操作的是你自己的浏览器。