关于 Apple Safari
面向 macOS Safari 的 MCP 服务,可读取浏览历史、书签和阅读列表,也能查看当前标签页及页面内容。文件、Apple Events 和扩展程序分别提供不同数据与操作,并各自需要相应权限。
主要功能
- 01搜索浏览历史并查看访问次数
- 02列出标签页、书签和阅读列表
- 03读取页面文本、HTML 与元素
- 04可选查看页面网络记录
- 05启用写入后打开网址和操作页面
- 06按需提取页面中的验证码
适用场景
- 01查找曾访问的页面
- 02查看当前标签页中的内容和表单
- 03检查已授权页面发出的网络请求
- 04启用写入后填写表单或保存阅读项目
MCP 相关
向 MCP 客户端提供 Safari 历史记录、标签页、书签、阅读列表和页面工具。页面读取、网络记录及写入操作分别受权限和配置开关控制。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @mgcrea/mcp-apple-safariAPPLE_SAFARI_ALLOW_WRITESRegister the mutating tools. Off by default, and the gate decides whether the write tools exist at all rather than merely refusing — an agent with writes off cannot see them.APPLE_SAFARI_ALLOW_CODESReturn one-time codes: a one-time-code field in apple_safari_page_elements, and apple_safari_find_codes for a code a page shows as text. Its own gate rather than allowWrites, because it is a read. Credential fields stay redacted whatever this says.APPLE_SAFARI_ALLOW_NETWORKRegister apple_safari_network_record, _list and _get, which record what a page you allowed the extension on fetches and read the responses. Its own gate rather than allowWrites, because it is a read; off means the tools do not exist. Headers named like credentials are masked, bodies are not. Reloading a tab to record and saving a body to a file still need APPLE_SAFARI_ALLOW_WRITES.APPLE_SAFARI_DOWNLOAD_DIRWhere apple_safari_network_get's saveAs writes a file. Defaults to ~/Downloads. A name is kept to its basename and an existing file is never replaced unless asked.APPLE_SAFARI_NETWORK_CHUNK_CHARSCharacters of a body the server asks the page for per command when it reads a large body. Defaults to 262144, which was measured to work; how far above that Safari's native-message channel goes is unmeasured.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
@mgcrea/mcp-apple-safari
适用于 macOS Safari 浏览器的 MCP 服务:你访问过哪些内容、当前打开了哪些页面,以及当前页面上有什么。
{
"mcpServers": {
"apple-safari": { "command": "npx", "args": ["-y", "@mgcrea/mcp-apple-safari"] }
}
}
三条彼此不能互为后备的通道
首先要了解这一点。此套件中的其他界面都有两种方式访问同一数据,选择哪种方式取决于速度。Safari 的各条通道所能看到的内容几乎完全不同,并且各自需要不同的权限——因此,未获授权的 Safari 服务并不是速度较慢的 Safari 服务,而是功能更少、内容也大不相同的服务。
| 通道 | 所需条件 | 能获取的信息 |
|---|---|---|
| 文件 | 完全磁盘访问权限 | 过去的信息:历史记录、书签、阅读列表 |
| Apple Events | 自动化授权,且 Safari 正在运行 | 当前信息:哪些标签页处于打开状态 |
| 扩展 | 已启用扩展,且允许其访问该网站 | 页面信息:页面文本、元素,以及对页面的操作 |
扩展通道无需任何 TCC 授权。Safari 会按网站逐一征求用户同意,用户可以查看并撤销对各个网站的授权。
工具
读取
| 工具 | 说明 |
|---|---|
apple_safari_search_history | 按文本或日期范围搜索历史记录,并显示访问次数 |
apple_safari_get_page | 根据 ref 获取一条历史记录 |
apple_safari_list_tabs | 列出当前打开的标签页,可选择从历史记录中补充信息 |
apple_safari_list_bookmarks | 书签树 |
apple_safari_list_reading_list | 已保存的项目,以及哪些项目尚未阅读 |
apple_safari_read_page | 将捕获的页面读取为文本或 HTML |
apple_safari_page_elements | 查看实时页面上可点击或可输入内容的元素 |
apple_safari_diagnostics | 查看哪些通道可用,以及不可用的原因 |
选择启用读取 — APPLE_SAFARI_ALLOW_CODES
| 工具 | 说明 |
|---|---|
apple_safari_find_codes | 从打开页面的文本中查找一次性 2FA 代码 |
选择启用读取 — APPLE_SAFARI_ALLOW_NETWORK
| 工具 | 说明 |
|---|---|
apple_safari_network_record | 在页面上启动、停止、清除或报告录制状态(reload 需要写入权限) |
apple_safari_network_list | 查看页面发出的请求:方法、URL、状态、耗时,不含请求正文 |
apple_safari_network_get | 查看单个请求的标头和正文;saveAs 会写入文件(需要写入权限) |
写入 — APPLE_SAFARI_ALLOW_WRITES
| 工具 | 说明 |
|---|---|
apple_safari_open_url | 在新标签页或当前标签页中打开 URL |
apple_safari_add_reading_list_item | 保存 URL,以便稍后阅读 |
apple_safari_click | 点击实时页面上的元素 |
apple_safari_fill | 在元素中输入内容 |
apple_safari_scroll | 滚动页面 |
配置
| 变量 | 默认值 | |
|---|---|---|
APPLE_SAFARI_ALLOW_WRITES | off | 是否注册五个写入工具。 |
APPLE_SAFARI_ALLOW_CODES | off | 一次性验证码——见下文。 |
APPLE_SAFARI_ALLOW_NETWORK | off | 网络捕获——见下文。 |
APPLE_SAFARI_DOWNLOAD_DIR | ~/Downloads | network_get 的 saveAs 写入的位置。 |
APPLE_SAFARI_NETWORK_CHUNK_CHARS | 262144 | 拉取正文时,每条原生消息包含的字符数。 |
APPLE_SAFARI_LIVE_TABS | on | 使用 Apple Events 通道。关闭 = 不使用 Apple Event。 |
APPLE_SAFARI_STORE | auto | 显式指定 History.db 路径。 |
APPLE_SAFARI_BOOKMARKS | auto | 显式指定 Bookmarks.plist 路径。 |
APPLE_SAFARI_PAGES | auto | 显式指定扩展捕获目录。 |
APPLE_SAFARI_ACTION_TIMEOUT_MS | 12000 | 操作等待页面响应的时长。 |
APPLE_SAFARI_LIVE_READ_TIMEOUT_MS | 2500 | read_page 等待标签页的时长。0 = 捕获。 |
APPLE_SAFARI_READING_LIST_CONFIRM_MS | 1500 | 再次读取前的等待时间,用于确认已添加到阅读列表。 |
APPLE_SAFARI_INDEX_MODE | auto | auto | ro | immutable | off。 |
APPLE_SAFARI_DEFAULT_RANGE_DAYS | 30 | 仅提供起始时间时使用的时间范围。 |
APPLE_SAFARI_MAX_RANGE_DAYS | 3660 | 单个时间范围的硬性上限。 |
APPLE_SAFARI_MAX_RESULTS | — | 默认页面大小。 |
APPLE_SAFARI_EXPOSE_PROMPTS | off | 注册提示词和 cupertino:// 资源。 |
APPLE_SAFARI_DEBUG | off | 详细日志记录。 |
APPLE_SAFARI_ALLOW_CODES
启用后,两项功能会同时开放:注册 find_codes,并让 page_elements 返回一次性验证码字段的值。这是读取操作,特意不归入 ALLOW_WRITES——如果要通过写入权限门控来读取,就意味着必须授予点击按钮的权限才能查看验证码。
无论如何,凭据字段都会被隐藏。 密码、卡号或 CVC 都会以 redacted: "credential" 返回,不包含值,也没有任何设置可以返回该值。此标志仅针对验证码,不会扩大到其他内容。两种情况下,hasValue 仍会表明字段是否已填写,这通常正是你需要的信息。
它的权限比同名的 Messages 门控更宽松,值得了解其中区别。 在 Messages 中,关闭表示工具不可用,替代方案是筛查整个对话串。在这里,read_page 不受门控,因此关闭后移除的是针对目标字段的读取和实时 DOM 扫描,而不是页面上的所有字节。
请参阅仓库中的 docs/passwords.md,了解为什么任何通道都无法访问 Passwords 应用本身,以及读取_网站显示的_验证码为何与读取密码库是两回事。
APPLE_SAFARI_ALLOW_NETWORK
注册 network_record、network_list 和 network_get,用于查看你已允许此扩展访问的页面获取了哪些内容:顶层框架发出的 fetch 和 XMLHttpRequest 调用、控制台错误以及资源计时条目。这是一道读取权限门槛,不会并入 ALLOW_WRITES,原因与 ALLOW_CODES 不同:已登录页面的流量属于会话材料,暴露这类信息与暴露一次性验证码是两回事。
- 响应正文和 URL 都不会脱敏。 名称为
authorization、proxy-authorization、cookie或set-cookie,或包含token、secret、api-key或apikey的标头会以<redacted: N chars>的形式返回。响应正文没有可供匹配的名称,因此会按页面看到的内容原样返回;URL 也会完整返回,包括查询字符串,因此其中的令牌也会一并返回。启用此权限门槛前请牢记这一点。 - 需按标签页选择启用,且会过期。 在
network_record启动前,不会包装任何内容;录制在重新加载后仍会保留,默认 15 分钟,最长 120 分钟;stop会立即结束录制。 - 另有两项操作也需要
ALLOW_WRITES:network_record中的reload: true会重新运行页面;network_get中的saveAs会写入APPLE_SAFARI_DOWNLOAD_DIR(仅接受纯文件名;除非设置overwrite: true,否则不会覆盖已有文件)。这两项操作失败时,都会返回指出APPLE_SAFARI_ALLOW_WRITES的错误。 - 无法查看的内容: 子框架、Web 和服务工作线程、
<img>/<script>响应正文以及 WebSocket。页面重新加载或导航后,请求 ID 会失效。对于拒绝内联脚本的 Content-Security-Policy 页面,会改用 URL 加载录制器。在 Safari 中尚未验证此方案;即使有效,录制器也会延迟加载,因此可能遗漏页面的首批请求;如果无效,hook.injection会显示"blocked"。 - 从 API 响应中保存文件: 使用
network_get,通过jsonPath指定字段,设置decode: "base64"并提供saveAs。字节会写入磁盘,而不会进入对话。
缓冲区限制:每个页面最多 1,000 条记录,响应正文总量最多 50 MiB;响应正文最多保留 maxBodyBytes 指定的大小(默认 10 MiB,最高 50 MiB)。请参阅 docs/safari.md。
可能让你措手不及的注意事项
- 没有匹配项的标签页很正常,不是错误。 实时标签页与历史记录之间唯一的关联键是 URL,而且它很容易丢失:三次运行的测量匹配率分别为 60.7%、55.3% 和 8.3%。通过
pushState访问的单页应用不会提交任何历史记录,因此页面确实被访问过,但确实不在存储中。将history: null视为未找到,绝不能视为“从未访问过”。 - URL 完全匹配,并不能证明这条记录对应的是该页面。 一次测量中,
http://localhost:4321/上的标签页匹配到了一条属于完全不同项目的历史记录。任何本地地址都可能出现这种情况。 - 只要标签页能响应,
read_page读取的就是实时页面;无法响应时,则读取快照。 它会先向打开的标签页发起请求,并等待APPLE_SAFARI_LIVE_READ_TIMEOUT_MS;可见标签页会在一秒内响应。隐藏标签页每十秒才响应一次,因此它和已关闭的标签页一样,会使用扩展在页面加载时以及页面稳定后保存的内容;source: "capture"、ageSeconds和liveRead会说明原因。执行操作的工具始终针对实时页面,因此处理可见标签页大约需要一秒,隐藏标签页则最多需要十秒。 - 使用
page_elements而非read_page读取表单。 字段值不属于页面文本。元素包含value(也包括 select 当前显示的选项)、复选框和单选按钮的checked,以及灰显控件的disabled: true;这些控件会列出,而不会被省略。除非通过maxValueChars提高上限,否则值会截断到 200 个字符;如果值被截断,会通过valueTruncated标明。 - 使用
fill和click设置表单。fill会通过元素的原生 setter 写入值,这样 React 才会识别更改——直接使用el.value =只会显示文本,React 状态仍保留旧值。对于 select,应传入page_elements报告的选项文本。复选框和单选按钮应使用点击,而不是填充;点击会切换状态,因此请先读取checked。无法设置文件输入框:内容脚本无法访问磁盘上的文件。 - 操作超时意味着没有任何监听方——扩展已关闭、未获准在该网站运行,或页面已关闭。这绝不意味着页面拒绝了操作。命令最多执行一次:已发送但未收到的命令不会重试,因为可能已经生效的点击绝不能重复执行。
- 元素 ID 会在页面导航后失效。 它们由
page_elements调用生成,仅对当前页面有效。绝不要自行构造元素 ID;任何点击操作加载了新内容后,都要重新枚举元素。 - 添加到阅读列表会访问网络。 Safari 会在后台获取 URL 以创建条目,并会用页面自身的标题覆盖你传入的
withTitle。verified有三种状态,且绝不会是false:文件更新比添加操作晚约 2 秒;将未确认的写入报告为失败会导致调用方重试,最终向一个没有删除操作的列表添加重复条目。 - 写入通道不需要完全磁盘访问权限。 在读取通道无法工作的机器上,两种 Apple Events 写入仍能工作。
APPLE_SAFARI_LIVE_TABS=false的优先级高于写入门控,因为该标志承诺服务不会发送任何 Apple Event。 open_url只接受http和https。javascript:URL 等同于通过前端入口执行do JavaScript——测量结果表明,Safari 接受通过导航动词执行的此类操作;而file:是会读取本地文件的导航动词。
许可证
MIT.
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。