关于 macos-mcp-server
用于控制本地 Mac 的 MCP 服务,面向需要管理应用、窗口、系统设置及其他桌面功能的 Agent。功能涵盖音频和显示器、截图、Finder、通知与专注模式;主机必须运行 macOS。
主要功能
- 01启动、退出、隐藏和显示应用
- 02列出、聚焦和排列窗口
- 03查看和更改音频设置
- 04管理显示器和外观
- 05截取屏幕并控制 Finder
- 06发送通知并设置专注模式
适用场景
- 01在桌面工作流中排列应用窗口
- 02切换音频设备或调整系统音量
- 03截取显示器、应用窗口或屏幕区域
- 04查看系统信息并控制专注模式
MCP 相关
向 MCP 客户端提供 13 个工具和三个资源,支持 STDIO 或 Streamable HTTP。它控制本地 macOS 主机,实际使用场景是通过 STDIO 在本机运行。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @cyanheads/macos-mcp-serverMACOS_SCREENSHOT_DIRDefault directory for screenshot files. Defaults to ~/Desktop when empty.MACOS_DISPLAY_LAYOUTSJSON object mapping layout names to displayplacer argument strings for apply_layout.MCP_LOG_LEVELMinimum log level for output: debug, info, notice, warning, error.MCP_TRANSPORT_TYPESelects the HTTP transport.MCP_HTTP_HOSTThe hostname for the HTTP server.MCP_HTTP_PORTThe port to run the HTTP server on.MCP_HTTP_ENDPOINT_PATHThe endpoint path for the MCP server.MCP_AUTH_MODE密钥Authentication mode: none, jwt, or oauth.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
13收录的工具列表 · 更新于 6 天前
此工具无需参数。
详细介绍
@cyanheads/macos-mcp-server
通过 MCP 控制 macOS 系统设置、应用、窗口、音频、显示器、屏幕截图和专注模式。支持 STDIO 或 Streamable HTTP。
仅限 macOS。 此服务控制本地 macOS 系统,因此主机必须运行 macOS。为完整起见,支持 HTTP 传输,但实际使用场景是 stdio:在本地运行,并让 MCP 客户端连接到它。
概述
通过 Agent 控制你的 Mac:启动、退出和排列应用与窗口,切换音频和显示设备,截取屏幕截图,使用 Finder,发送通知,并切换专注模式。
工具
| 工具 | 描述 |
|---|---|
macos_get_info | 系统快照:电池电量和充电状态、电源来源、Wi-Fi 连接状态和网络名称、主机名、macOS 版本、运行时间和显示器数量;探测失败时,相应字段为 null,并列入 unavailable |
macos_check_permissions | 使用 Apple 无需触发提示的检查,报告调用进程的辅助功能、屏幕录制和自动化 > Finder 状态;无法判断时返回 null,并附上原因 |
macos_manage_apps | 列出正在运行的应用、获取最前端应用、启动、退出、强制退出、隐藏或显示应用 |
macos_manage_windows | 列出、聚焦、移动、调整大小、移动并调整大小、最小化、全屏显示或关闭所有可见应用中的窗口 |
macos_control_volume | 获取或设置系统输出音量(0–100)和静音状态 |
macos_control_audio | 列出音频设备、获取当前默认设备,或切换默认输入/输出设备 |
macos_control_appearance | 获取或设置深色/浅色模式 |
macos_control_system | 锁定屏幕或让显示器进入睡眠状态 |
macos_take_screenshot | 截取主显示器、由 display_index 指定的显示器、指定名称的应用窗口或指定像素区域;保存为 PNG,除非 overwrite 为 true,否则不会替换现有文件;可选 JPEG 预览 |
macos_manage_displays | 列出已连接的显示器(包括各自的 index)和已配置的布局名称,并应用指定名称的显示器布局预设 |
macos_send_notification | 向 macOS 通知中心发送通知 |
macos_manage_focus | 获取或设置“勿扰”/专注模式 |
macos_manage_finder | 获取最前端路径、当前所选项目、显示项目、使用应用打开项目,或将项目移到废纸篓 |
资源
| 资源 | 描述 |
|---|---|
macos://system/info | 当前 macOS 系统快照:电池、电源来源、Wi-Fi 连接状态和网络名称、主机名、版本、运行时间和显示器数量;探测失败时,相应字段为 null,并列入 unavailable |
macos://audio/devices | 所有音频输入和输出设备,包括当前默认设备。需要 SwitchAudioSource CLI。 |
macos://displays | 已连接显示器清单,包括持久 ID、索引(macos_take_screenshot 和 macos_manage_windows 使用的 display_index)、类型、分辨率、原点、旋转角度、缩放比例和启用状态。需要 displayplacer CLI。 |
资源数据也可通过 macos_get_info、macos_control_audio(action=list)和 macos_manage_displays(action=list)访问。
功能参考
macos_get_info 工具
- 电池电量(0–100)、充电状态和电源来源(
AC、Battery、UPS);没有电池的台式机上为null。当电池电量受充电上限限制时(AC attached; not charging),charging为 false;处于finishing charge状态时为 true - 根据 Wi-Fi 设备的链路状态获取 Wi-Fi 连接状态;已连接时,如果 macOS 不提供网络名称,该名称为
null(读取网络名称需要主机应用获得“定位服务”访问权限)。名称在content[]中以代码跨度形式呈现,因此其中的 Markdown 字符会按名称原样显示 - 主机名和运行时间(来自 Node 的
os模块),macOS 版本字符串(例如"15.1") - 已连接的显示器数量(无头 Mac 上为
0) - 探测失败时,相应字段为
null,并列入unavailable;不会填入任何值。若battery: null且unavailable中没有battery,则表示 Mac 没有电池 - 无需权限
macos_check_permissions 工具
- 报告以下权限:辅助功能(每个
macos_manage_windows操作,包括list;macos_manage_apps的hide和show;window_title的frontmost读取;以及macos_control_system的lock在回退到屏幕保护程序前发送的 ⌃⌘Q 按键)、屏幕录制(窗口截图),以及 自动化 > Finder(macos_manage_finder的frontmost_path、get_selection和trash);使用 Apple 提供的无需提示检查:AXIsProcessTrusted、CGPreflightScreenCaptureAccess,以及关闭提示的AEDeterminePermissionToAutomateTarget - 每个字段的值为
true、false;如果检查无法确定,则为null;unknown_reasons会说明每个null值未知的原因。在 macOS 请求用户授权前,以及 Finder 未运行时,自动化 > Finder 会返回null。通知始终为null:macOS 没有无需提示的检查方式 - 返回调用进程的名称(例如
"ghostty"、"node"),以便你知道应授予哪个进程权限 - 只读——不会有检查向其他应用发送 Apple 事件、截取屏幕或显示权限提示
- 调试其他工具为何失败时,请先运行此工具
macos_manage_apps 工具
list— 列出正在运行的面向用户的应用,并提供名称、Bundle ID、PID、可见和前台标记frontmost— 提供当前活动应用的名称、Bundle ID、PID 和前台窗口标题launch— 按app_name或bundle_id打开或激活应用;hidden=true会在后台启动应用quit(通过 AppleScript 正常退出)与force_quit(使用 SIGKILL 强制退出,不提示保存)hide/show— 切换可见状态;需要辅助功能权限quit、force_quit、hide和show会先检查应用是否正在运行——按tell application的方式根据应用名称解析("Visual Studio Code"会找到Code进程),但不会启动应用;如果应用未运行,则返回not_running- 类型化错误:
app_not_found(launch所指定的应用未安装)、no_frontmost_app(System Events 未报告任何前台进程)、not_running、accessibility_required
macos_manage_windows 工具
list— 列出所有应用中可见的窗口,包括位置、大小、minimized和fullscreen状态(分别读取自窗口的AXMinimized/AXFullScreen属性;无法读取时为null),以及display_index:窗口重叠面积最大的显示器(索引由macos_manage_displays action=list报告);无法读取显示器边框或窗口未与任何显示器重叠时为null。标题相同的两个窗口,以及属于名称相同的两个进程的窗口,都会分别列出focus、move、resize、move_resize、minimize、fullscreen、close— 通过window_title(在任意应用中精确匹配,忽略大小写;同一应用有多个同名窗口时,选择最前面的窗口)或app_name(该应用中最前面的窗口)指定目标;window_title优先,之后忽略app_nameapp_name根据正在运行的应用的应用名称("Visual Studio Code")、本地化名称("Code")或进程名称("firefox")匹配,忽略大小写;匹配结果从正在运行的应用列表中解析,因此不会为查找应用而启动应用或向其发送 Apple 事件move需要x/y,resize需要width/height(两者均须大于 0),move_resize需要全部四个值minimize—minimized: true(默认值)会最小化窗口,false会还原窗口;仅当窗口尚未处于目标状态时才写入窗口的AXMinimized属性,并且只有读取到窗口处于请求的状态时才算成功fullscreen—fullscreen: true进入全屏,false退出全屏;省略时切换窗口当前状态。状态不同时,会像focus一样将目标窗口置于前台,并设置其AXFullScreen属性(不使用按键,因此绝不会切换当前具有键盘焦点的窗口);等待最多 4 s,直到读取到窗口处于请求的状态- 所有操作(包括
list)都会调用 System Events 脚本,并且需要辅助功能权限;被拒绝时返回accessibility_required,绝不会将其误报为“没有窗口”。focus会将窗口置于前台,并将其应用带到前台;任何操作都不会启动应用 - 每次写入都会在同一个脚本中查找、修改并回读同一个窗口;
window.app是查找到该窗口时所属的进程;close不返回窗口 - 类型化错误:
not_running(app_name未命名任何正在运行的应用)、window_not_found、accessibility_required、minimize_unsupported和fullscreen_unsupported(窗口缺少AXMinimized/AXFullScreen属性、该属性不可设置,或窗口在 4 s 内未达到请求的状态);osascript 超时或失败时会保留自身错误,不会将其误报为window_not_found
macos_control_volume 工具
get— 获取默认输出设备的当前输出音量(0–100)和静音状态。对于没有软件音量控制的输出设备(许多 HDMI 和 DisplayPort 显示器),level和muted为null,output_device为设备名称(无法识别时为null)set— 必须提供level(0–100)、muted或两者;level=0不会静音。对于不支持所请求值的软件控制的输出设备,操作会失败并返回包含设备名称的volume_unsupported,且不会进行任何更改- 始终返回当前状态,并回显
action - 类型化错误:
volume_unsupported;取消调用后会停止设备查找并以已取消状态结束,绝不会返回output_device: null
macos_control_audio 工具
list— 列出所有输入 / 输出设备,并标记is_default;使用type进行筛选current— 获取默认输入和输出设备的名称switch_output/switch_input— 按名称进行不区分大小写的部分匹配("MacBook"可匹配"MacBook Pro Microphone")- 音量级别单独控制(
macos_control_volume) - 需要 SwitchAudioSource CLI(
brew install switchaudio-osx);类型化错误:device_not_found、switchaudio_unavailable
macos_control_appearance 工具
get— 返回dark_mode: true/false,并回显actionset需要mode: "dark" | "light" | "toggle"—dark/light是幂等操作,toggle每次调用都会切换- 执行 System Events 脚本;当“自动化”>“System Events”权限被拒绝时,返回类型化错误
accessibility_required
macos_control_system 工具
lock— 通过辅助功能发送 ⌃⌘Q;未授予辅助功能权限时,改用 ScreenSaverEngine 二进制文件(已取消的调用不会回退)sleep_display— 执行pmset displaysleepnow;无需权限- 两项操作都会立即生效,并可通过任意输入恢复(唤醒/解锁)
macos_take_screenshot 工具
target:screen(仅主显示器,即带有菜单栏的显示器)、display(从 0 开始的整数display_index,默认值为 0:即macos_manage_displays action=list报告的index)、region(像素矩形,width/height必须大于 0)——无需屏幕录制权限;window(通过app_name指定,匹配方式与macos_manage_windows相同:应用名称、本地化名称或进程名称,不区分大小写;截取该应用最前方且位于屏幕上的窗口)需要屏幕录制权限。若无法确定屏幕录制权限检查结果,仍会执行窗口截取,并在notice中说明原因- 始终保存全分辨率 PNG。省略
path(或传入"")时,会在MACOS_SCREENSHOT_DIR中创建新的screenshot-<timestamp>.png(未设置时为~/Desktop),若文件已存在,则依次添加-2、-3、……,而不是替换文件。自定义path必须以.png结尾(不区分大小写);~/会展开为主目录;其目录必须存在,并且解析符号链接后必须位于MACOS_SCREENSHOT_DIR、/tmp或主目录内。返回的path是解析后的路径(/tmp路径会显示为/private/tmp下的路径) - 绝不替换已存在的条目:若路径已存在,则失败并返回
path_exists,除非设置了overwrite: true;此时仅替换调用检查该路径时存在的普通.png文件(绝不替换符号链接或目录)。检查后在该路径创建的条目,或之后放到该文件位置的条目,绝不会被替换,并会导致path_exists - 每次调用都会在开始后的 55 秒内结束,包括
app_name查找;超时后会以 Timeout(-32004)失败,且不写入任何内容。已取消的请求同样不会写入任何内容 include_data=true时会返回 JPEG 预览图,最长边不超过 1024 px(较小的截取图保持原尺寸),质量为 70:作为第一个content[]块(image/jpeg)返回,并以 base64 编码的preview以及preview_width/preview_height的形式包含在structuredContent中width/height(以及预览图的宽高)从图像文件头读取- 类型化错误:
screen_recording_required、window_not_found(应用未运行,或应用没有位于屏幕上的窗口;窗口查找期间发生 osascript 超时或失败时,保留其自身错误)、display_not_found、path_not_writable(也适用于无法解析或搜索的目录,包括路径过长的目录)、path_exists;若path不以.png结尾、包含 U+0000,或文件名/完整解析路径超过文件系统限制(文件名 255 字节,完整路径 1024 字节),则会以-32602拒绝,并返回原因invalid_arguments
macos_manage_displays 工具
list— 每台显示器的信息:index(在NSScreen.screens中从 0 开始的位置;macos_take_screenshot和macos_manage_windows使用的display_index;没有对应 NSScreen 条目的显示器(例如已停用的显示器)为null)、持久 ID、类型("MacBook built in screen"或"<N> inch external screen")、分辨率、刷新率(macOS 未报告时为"N/A")、原点、旋转、缩放和启用状态(镜像其他显示器时也为true);此外还有current_config(可重现当前排列的 displayplacer 命令)和layouts(在MACOS_DISPLAY_LAYOUTS中配置的名称;没有配置时为[])。当MACOS_DISPLAY_LAYOUTS无效时,list仍会成功,但会省略layouts,并在notice中指出该变量。当 NSScreen 读取失败时,会省略index,并在notice中说明原因;包括因并发的 System Events 枚举而等待 15 s 的读取apply_layout— 激活MACOS_DISPLAY_LAYOUTS中的命名预设(即list返回为layouts的名称之一);绝不接受调用方传入原始 displayplacer 参数。若MACOS_DISPLAY_LAYOUTS无效,apply_layout会以layouts_invalid(ConfigurationError)失败,而不会报layout_not_found- 需要 displayplacer CLI(
brew install displayplacer);类型化错误:displayplacer_not_found、displayplacer_output_unrecognized、layout_not_found、layouts_invalid - displayplacer 运行失败(非零退出、信号、中止或超时)时,调用会失败,而不会返回其输出内容;显示器信息块缺少列表会返回其值的某个字段,或列表缺少排列命令时,会以
displayplacer_output_unrecognized失败。可解析 displayplacer 1.3.0 和 1.4.0
macos_send_notification 工具
- 必填
title;body、subtitle和sound=true(默认通知声音)为可选项 - 每次调用都会创建一条新通知,不具备幂等性
- 发布通知无需权限;若当前启用了“专注模式”(例如“勿扰模式”),除非该模式允许发布通知的应用,否则通知会被静音
macos_manage_focus 工具
get— 尽力读取~/Library/DoNotDisturb/DB/Assertions.json,并返回status: "active" | "inactive" | "unknown";无法读取或解析该文件时返回unknown(附带reason),例如服务器进程无权读取该文件时;已取消的调用会以已取消状态结束,绝不会返回unknownset— 运行快捷指令库中名为"Set Focus"的快捷指令,并将{"mode": …, "enabled": …}作为 JSON 输入传给它。该快捷指令并非内置:请创建一个快捷指令,让“从输入获取字典”操作向“设置专注模式”操作提供输入。mode必须与已配置的专注模式名称完全一致,包括大小写(例如"Do Not Disturb"、"Work");enabled默认为true。结果会回显mode和enabled;success: true表示快捷指令运行完毕且未出错(不会重新读取专注模式状态)- 类型化错误:
shortcuts_unavailable(缺少 shortcuts CLI,或没有名为 "Set Focus" 的快捷指令)、shortcut_failed(快捷指令运行出错;附带错误文本)。快捷指令运行超过 15 s 时会被停止,并报告为Timeout错误;已取消的调用会停止运行并以已取消状态结束
macos_manage_finder 工具
frontmost_path— 活动 Finder 窗口的 POSIX 路径;若没有打开的窗口,则为null;取消的调用会以取消状态结束,不会返回nullget_selection— 所选项目的 POSIX 路径reveal(open -R)、open_with(open -a <App>;若省略app_name,则使用默认应用)、trash(移至废纸篓,可恢复,绝不会永久删除)open_with和trash在调用open或 Finder 之前会检查路径是否存在frontmost_path、get_selection和trash会调用 Finder 脚本,需要 Automation > Finder 权限;reveal和open_with无需权限- 类型化错误:
finder_not_open、path_not_found、app_not_found(open_with中的应用未知)、trash_refused(Finder 拒绝处理现有项目——项目已锁定、正在使用,或所在宗卷没有废纸篓)、accessibility_required
macos://system/info 资源
- 当前 macOS 系统快照,格式为
application/json——包括电池、电源、Wi-Fi 连接状态和网络名称、主机名、版本、运行时间、显示器数量以及unavailable - 也可通过
macos_get_info获取相同数据
macos://audio/devices 资源
- 所有音频输入和输出设备,以及当前默认设备,格式为
application/json - 需要 SwitchAudioSource CLI
- 也可通过
macos_control_audio(action=list)获取相同数据
macos://displays 资源
- 已连接显示器清单——包括
index、持久 ID、类型、分辨率、原点、旋转、缩放、启用状态以及current_config,格式为application/json;若 NSScreen 读取失败,则会包含notice(且不包含index) - 需要 displayplacer CLI
- 也可通过
macos_manage_displays(action=list)获取相同数据
功能
基于 @cyanheads/mcp-ts-core 构建:支持 stdio 和 Streamable HTTP 传输、可插拔身份验证(none / jwt / oauth)、可切换存储(in-memory、filesystem、Supabase、Cloudflare KV/R2/D1),以及支持可选 OpenTelemetry 跟踪的结构化日志。
macOS 专属:
- osascript 服务同时封装 JXA(
runJxa)和 AppleScript(runAppleScript),并提供可配置超时 - SwitchAudioSource 和 displayplacer 集成为可选依赖;每次调用时都会在 PATH、
/opt/homebrew/bin或/usr/local/bin中查找;如果这些位置都没有相应命令行工具,受影响的工具会返回ServiceUnavailable和安装说明 - macOS 系统二进制文件(osascript、screencapture、sips、pmset、networksetup、system_profiler、…)均通过绝对路径运行,因此主机 PATH 不会决定运行哪个二进制文件
- screencapture 会在目标文件旁的私有目录中写入文件,放置 PNG 时不会替换本次调用未检查过的任何文件;sips 会生成 JPEG 预览;图像尺寸取自 PNG/JPEG 标头
- 使用 pmset 获取电池信息,使用 networksetup + ifconfig 获取 Wi-Fi 信息,使用 sw_vers 获取版本,使用
system_profiler -json SPDisplaysDataType获取显示器信息,使用 Node 的os获取主机名和运行时间 - 权限优先设计——工具返回
Forbidden前,macos_check_permissions会准确报告哪个进程需要哪项授权
便于 Agent 使用的输出:
- 权限错误包含
accessibility_required原因,并针对实际被拒绝的权限提供授权说明——Privacy & Security > Accessibility,或针对指定应用的Privacy & Security > Automation - 如果缺少某项操作的参数(例如
set未提供mode,或quit未提供app_name),或者文本无法用 AppleScript 字面量表示(U+0000、未配对代理项),会在执行任何操作前拒绝请求,并以-32602返回错误,附带invalid_arguments原因和说明应发送哪些内容的提示;每个多操作工具都会在其inputSchema中说明各项操作的要求 - 错误包含失败程序自身的错误文本,不包含其命令行或脚本源代码
- 可选 CLI 依赖缺失时,会返回
ServiceUnavailable,并提供确切的brew install命令 macos_manage_windows会为每个窗口报告display_index(窗口重叠最多的显示器;未知时为null),便于 Agent 判断多显示器布局macos_take_screenshot将全分辨率磁盘写入与可选预览分开;预览会作为图像内容块发送,也会以 base64 格式放在structuredContent中,因此两种形式都包含预览
入门
此服务仅限本地使用——它控制其运行所在的 macOS 系统。请将 STDIO 传输与 MCP 客户端配合使用。
将以下内容添加到 MCP 客户端配置文件中:
{
"mcpServers": {
"macos-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/macos-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
或者使用 npx(无需 Bun):
{
"mcpServers": {
"macos-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/macos-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
若要使用 Streamable HTTP,请设置传输方式并启动服务:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
前置条件
- macOS 13(Ventura)或更高版本。
- Bun v1.4.0 或更高版本(或 Node.js v24+)。
- 可选:SwitchAudioSource,用于音频路由(
brew install switchaudio-osx)。 - 可选:displayplacer 1.3.0 或 1.4.0,用于显示器管理(
brew install displayplacer)。
这两个可选 CLI 都可以通过 Apple Silicon 上的 Homebrew(/opt/homebrew/bin)或 Intel 上的 Homebrew(/usr/local/bin)使用,也可以从 PATH 中的任意位置使用。
部分工具需要向终端或 MCP 宿主应用授予 macOS 权限:
| 权限 | 所需工具 |
|---|---|
| 辅助功能 | macos_manage_windows(每项操作,包括 list)、macos_manage_apps(隐藏/显示;frontmost 使用此权限读取 window_title)、macos_control_system(锁定按键;无此权限时会回退到屏幕保护程序) |
| 屏幕录制 | macos_take_screenshot,且 target=window |
| 自动化 > Finder | macos_manage_finder,且 action=frontmost_path、get_selection 或 trash |
运行需要权限的操作前,使用 macos_check_permissions 检查当前权限状态。
安装
- 克隆仓库:
git clone https://github.com/cyanheads/macos-mcp-server.git
- 进入目录:
cd macos-mcp-server
- 安装依赖:
bun install
- 配置环境:
cp .env.example .env
# edit .env if you want to set MACOS_SCREENSHOT_DIR or MACOS_DISPLAY_LAYOUTS
配置
| 变量 | 描述 | 默认值 |
|---|---|---|
MACOS_SCREENSHOT_DIR | 截图文件的默认目录,也是自定义 path 可解析到的目录之一(另外两个是 /tmp 和主目录)。会解析 ~ 和符号链接。 | ~/Desktop |
MACOS_DISPLAY_LAYOUTS | 将布局名称映射到非空 displayplacer 参数字符串的 JSON 对象。由 macos_manage_displays action=apply_layout 使用;action=list 会将名称作为 layouts 返回。无效值只会导致 apply_layout 失败。 | {} |
MCP_TRANSPORT_TYPE | 传输方式:stdio 或 http。 | stdio |
MCP_HTTP_PORT | HTTP 服务的端口。 | 3010 |
MCP_AUTH_MODE | 身份验证模式:none、jwt 或 oauth。 | none |
MCP_LOG_LEVEL | 日志级别。 | info |
MCP_SESSION_MODE | 会话存储:auto、stateful 或 stateless(仅限 HTTP)。此服务不保存任何会话状态,因此 .env.example 会显式设置为 stateless。 | stateless(在 src/index.ts 中声明) |
OTEL_ENABLED | 启用 OpenTelemetry 检测。 | false |
完整的可选覆盖项列表请参阅 .env.example。
显示器布局示例:
# Get the current displayplacer command for your setup:
displayplacer list
# Then configure named layouts in your env:
MACOS_DISPLAY_LAYOUTS='{"office":"\"id:1234 res:2560x1440 hz:60 color_depth:8 scaling:on origin:(0,0) degree:0\" \"id:5678 res:1920x1080 hz:60 color_depth:8 scaling:on origin:(2560,0) degree:0\""}'
每个屏幕都有自己的带引号片段,其格式与 displayplacer list 打印的布局命令相同;不带引号片段的布局值会作为单个参数传递给 displayplacer。macos_manage_displays action=list 会返回包含相同逐屏带引号片段的 current_config,并将已配置的名称作为 layouts 返回。
运行服务
本地开发
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# Run checks
bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
项目结构
| 目录 | 用途 |
|---|---|
src/index.ts | createApp() 入口——注册工具/资源并初始化服务 |
src/config/server-config.ts | 解析 MACOS_SCREENSHOT_DIR 和 MACOS_DISPLAY_LAYOUTS 环境变量 |
src/mcp-server/tools/definitions/ | 13 个工具定义(macos-*.tool.ts) |
src/mcp-server/tools/action-requirements.ts | 各操作的参数要求——由输入架构强制执行,并在 inputSchema 中声明 |
src/mcp-server/resources/definitions/ | 3 个资源定义(macos-*.resource.ts) |
src/services/osascript/ | osascript JXA 和 AppleScript 执行器,支持可配置超时和权限拒绝分类;用于调用方文本的 AppleScript 字符串字面量;以及运行中应用的名称解析器 |
src/utils/exec-failure.ts | CLI 调用失败时对调用方安全的错误文本(绝不包含命令行) |
src/services/binaries/binary-paths.ts | macOS 系统二进制文件的绝对路径,以及用于查找 displayplacer 和 SwitchAudioSource 的每次调用 PATH + Homebrew 查找 |
src/services/permissions/ | 无提示的“辅助功能”“屏幕录制”和“自动化 > Finder”检查 |
src/services/audio/ | SwitchAudioSource 设备列表和切换 |
src/services/display/ | displayplacer 列表和应用布局,以及用于获取各显示器索引的 JXA NSScreen 读取 |
src/services/screencapture/ | screencapture PNG 截取、路径检查和禁止覆盖的文件放置、sips JPEG 预览 |
src/services/system-info/ | 通过 pmset/networksetup/ifconfig/sw_vers/system_profiler 获取电池、Wi-Fi、版本和显示器数量;通过 node:os 获取主机名和运行时间 |
tests/tools/ | 与工具定义对应的工具测试 |
开发指南
开发指南和架构规则请参阅 CLAUDE.md。简要说明:
- 处理器抛出异常,框架负责捕获——工具逻辑中不要使用
try/catch - 使用
ctx.log进行请求范围内的日志记录;不要调用console - 读取实际的系统/CLI 状态,绝不伪造信息——当操作系统无法提供答案时(例如台式机上的电池信息,或无法读取 Focus 状态文件时的 Focus 状态),返回
null或unknown,不要猜测 - 服务是单例,在
createApp()中初始化,并通过get*Service()访问器调用
贡献
欢迎提交问题。提交前请运行检查和测试:
bun run devcheck
bun run test
许可证
Apache-2.0——详情请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。