关于 oswright
OSWright 是面向 AI Agent 的桌面自动化 MCP 服务,通过增量感知只重读发生变化的屏幕区域。它结合像素变化、无障碍树、OCR 和图像匹配,驱动真实桌面应用。
主要功能
- 01增量屏幕感知与变化检测
- 02按角色和名称查找界面元素
- 03OCR 与图像模板匹配
- 04窗口、鼠标、键盘和剪贴板操作
- 05启动应用并等待界面稳定
适用场景
- 01自动填写桌面应用中的表单
- 02在桌面应用间查找并操作界面元素
- 03通过剪贴板在应用间传递数据
MCP 相关
OSWright 作为 MCP 服务,向客户端暴露桌面、OCR、无障碍、鼠标键盘、窗口和剪贴板等工具,用于驱动桌面应用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
OSWright
面向 AI Agent 的桌面自动化,无需每一步都为截图付费。
mcp-name: io.github.Ask-812/oswright
一个 MCP 服务,可让大模型操控真实的桌面应用——相当于桌面版的 Playwright MCP。它会在操作之间保留屏幕模型,并只重新读取发生变化的部分,因此完成相同任务所需的令牌减少一个数量级。

从发票中读取 8 个字段并输入费用表单,再由应用本身验证。任务相同,结果相同,上下文用量比每次操作后都返回截图少 7.4×。屏幕上的每个数字都是运行过程中实测的——运行 python benchmarks/record_demo.py 即可重新生成完整演示。
为什么要开发它
大多数 GUI Agent 每一步都会重新感知整个屏幕:截图、OCR、把图像交给模型,再重复操作。在真实桌面上测得,观察内容中位数只有 0.012% 的屏幕像素发生变化。重新读取所有内容所做的工作远超变化本身所需,而且无论是否发生变化,都会消耗约 2,800 个图像令牌。
OSWright 会询问合成器发生了哪些变化,只重新扫描这些部分,并从成本最低的可用来源获取元素查询结果。下方的指标均在这台机器上测得,可通过 benchmarks/ 复现——包括对它不利的结果。
主要功能
- 跨平台。 Windows(Win32 API)、Linux(pynput/X11)、macOS(pynput/Quartz)。
- 辅助功能树。 通过 Windows UI Automation 按角色和名称确定性地查找元素——准确率 100%,即时完成,无需模型。
- 快速 OCR。 Windows OCR(内置、即时),Linux/macOS 则使用 EasyOCR 作为备用方案。结果会自动缓存。
- Windows 上轻量。 无需下载 PyTorch——Windows 使用内置 OCR 引擎,因此完整安装只需几 MB,而非几 GB。
- 图像匹配。 通过 OpenCV 使用模板图像定位元素。
- 窗口管理。 列出、聚焦、最小化、关闭窗口,并对指定窗口截图。
- 截图差异检测。 使用
wait_for_change检测屏幕是否发生变化。 - 剪贴板访问。 读取和写入系统剪贴板以传输数据。
- 应用启动器。 启动应用并等待其加载。
- 自动快照。 每个操作都会返回截图,让 Agent 始终看到当前状态。
- 43 个 MCP 工具。 涵盖屏幕、OCR、UIA、鼠标、键盘、窗口、剪贴板和组合操作。
- 增量感知。 只重新扫描屏幕上发生变化的部分,还可以返回变化内容而非完整截图——每步令牌用量减少约 21×。
- 屏幕记忆。 识别之前读取过的屏幕并复用结果,经像素验证——成本比重新读取低 89×。
- 推测式感知。 学习各类操作的效果,并确认预期结果,而不是重新读取——成本降低 19–23×;如果界面出现意外情况,还会生成
surprise报告。 - 自适应等待。 等待屏幕实际稳定下来,而不是固定休眠 300 ms——50 步任务可节省 11.9 s。
- 分级解析。 元素查询会在找到可用的最低成本方法后停止;重复查询耗时约 0.05 ms。
- DPI 坐标准确。 坐标始终使用物理像素,因此在缩放显示器上点击也能准确定位。
- 测试套件。 包含 259 项自动化测试;没有可用显示器时,桌面控制相关测试会自动跳过。
要求
- Python 3.10 或更高版本
- VS Code、Cursor、Windsurf、Claude Desktop 或任何其他 MCP 客户端
入门
首先,使用你的客户端安装 OSWright MCP 服务。
标准配置适用于大多数工具:
{
"mcpServers": {
"oswright": {
"command": "uvx",
"args": ["oswright"]
}
}
}
注意: 如果你没有
uvx,可以使用pip install oswright,然后直接将"command": "oswright"设为命令。
Claude Desktop
按照 MCP 安装指南,使用上面的标准配置。
Claude Code
claude mcp add oswright uvx oswright
VS Code
在用户或工作区的 settings.json 中,将配置添加到 mcp.servers 下:
{
"mcp": {
"servers": {
"oswright": {
"command": "uvx",
"args": ["oswright"]
}
}
}
}
或者使用 VS Code CLI:
code --add-mcp '{"name":"oswright","command":"uvx","args":["oswright"]}'
Cursor
前往 Cursor Settings -> MCP -> Add new MCP Server。将名称设为 oswright,类型设为 command,并使用命令 uvx oswright。
Windsurf
按照 Windsurf MCP文档操作。使用上面的标准配置。
Cline
将配置添加到 cline_mcp_settings.json:
{
"mcpServers": {
"oswright": {
"type": "stdio",
"command": "uvx",
"args": ["oswright"],
"disabled": false
}
}
}
Goose
前往 Advanced settings -> Extensions -> Add custom extension。将名称设为 oswright,类型设为 STDIO,并将 command 设为 uvx oswright。
使用 pip 而非 uvx
如果你更倾向于使用标准 pip 安装:
pip install oswright
然后使用以下配置:
{
"mcpServers": {
"oswright": {
"command": "oswright"
}
}
}
或者直接运行:
python -m oswright
Agent 技能
skills/oswright/SKILL.md 会告诉 Agent 何时使用 OSWright,以及哪些工具成本较低。它遵循开放的 Agent Skills 格式。将 skills/oswright 文件夹复制到 Agent 的技能目录中——Claude Code 的目录为 ~/.claude/skills/,其他客户端则使用对应的目录。
该技能会设置 --observation-mode delta。如果不设置,每个操作都会返回完整截图,而 OSWright 正是为了避免这种开销而存在的。
增量感知
大多数 GUI Agent 每一步都会重新感知整个屏幕:完整截图、完整 OCR,然后把一张新图交给模型。在实时桌面上测得,中位数观察结果中只有 0.012% 的像素发生变化——因此,完整扫描所做的工作约比实际变化所需的多 240 倍,而且无论是否发生变化,返回的截图都要消耗约 2,800 个图像 token。
OSWright 会在各次观察之间保留屏幕模型,只重新扫描实际发生变化的区域。
observe() -> {"changed": true,
"added": [{"text": "Saved", "x": 812, "y": 447}],
"removed": ["Unsaved changes"],
"screen_fraction_scanned": 0.015}
在这台机器上对一个 14 步 Agent 循环进行测量:
| v0.4.0(完整 OCR + 截图) | 增量 | |
|---|---|---|
| 每步中位数延迟 | 212 ms | 33 ms |
| 每次观察的 token 数 | ~2,764 | ~49 |
| 14 步的 token 总数 | 38,696 | 1,025 |
| 屏幕重新读取比例 | 100% | 16% |
屏幕越繁忙,差距越大:完整 OCR 的开销随屏幕上的文本量增长,而增量路径的开销随变化量增长。同样的对比在安静的桌面上为 6.5 倍,打开内容密集的网页时则为 14.3 倍。请通过 benchmarks/ 重新测量,而不是盲目相信这些数据。
不过,成本只是一个代理指标;如果更省成本的感知路径会在不知不觉中降低准确率,那还不如不用。因此,我们会用任务完成情况进行验证:通过真实工具界面驱动四个应用的脚本化任务,并根据各应用自身的状态评分——Calculator 和 Explorer 使用 UI Automation,Chrome 和 VS Code 使用窗口标题——绝不依据 OCR 评分。
| 配置 | Calculator | File Explorer | Chrome | token 数 |
|---|---|---|---|---|
| v0.4 式(完整截图) | 9/9 | 3/3 | 3/3 | 118,858 |
| 仅增量 | 9/9 | 3/3 | 3/3 | 5,252 |
| 增量 + 记忆 | 9/9 | 3/3 | 3/3 | 5,099 |
| 增量 + 记忆 + 预测 | 9/9 | 3/3 | 3/3 | 7,981 |
**各配置的准确率完全相同,而 token 成本降低了 23 倍。**运行 python benchmarks/bench_tasks.py。
为何兼用像素与辅助功能
这一设计的前提是,两种感知路径都无法在所有情况下胜出。关闭各自的一半,就能实际测量这一点,而不是只作断言:
| 配置 | Calculator | File Explorer | Chrome |
|---|---|---|---|
| 完整级联 | 9/9 | 3/3 | 3/3 |
| 仅辅助功能 | 9/9 | 0/3 | 0/3 |
| 仅像素 | 6/9 | 3/3 | 3/3 |
仅用辅助功能——大多数 Windows GUI Agent 采用的方式——在 XAML 上表现完美,但无法识别 Win32 列表视图和网页内容。对 VS Code 进行探测时,它只能看到 18 个元素:整个 IDE 被识别为一个名为 Chrome Legacy Window 的节点;而 OCR 能读取 94 个元素,包括每个文件名。
仅用像素无法识别 Calculator 的按钮,因为人类看作 7 的按钮,名称却是 Seven,而 Windows OCR 完全无法从 Calculator 识别出数字。
只有级联配置能在所有场景中通过测试。
分层解析
find_element 和 click_element 会在找到第一个能给出答案的方法后停止,因此成本取决于请求有多么新颖,而不是屏幕有多大:
| 层级 | 方法 | 典型成本 |
|---|---|---|
| 0 | 已在屏幕模型中 | 约 0.05 毫秒 |
| 1 | 仅重新扫描发生变化的部分 | 约 70 毫秒 |
| 2 | 无障碍树(知道某个 Button 就是按钮) | 约 40 毫秒 |
| 3 | 通过 UIA TextPattern 读取应用自身的文本缓冲区——获取精确字符 | 约 400 毫秒 |
| 4 | 全屏 OCR | 约 250 毫秒 |
查找模型已经知道的文本,成本比 v0.4.0 路径低 约 5,000 倍(0.05 毫秒,而非 244 毫秒)。响应会报告是哪个层级给出了答案,因此你可以了解任务实际耗费的成本。
第 3 层值得了解:UIA 的 TextRange.FindText 会搜索应用自身的文本缓冲区,并返回精确的边界矩形。它不受字体、DPI、抗锯齿和 OCR 错误影响。它排在像素层级之后,仅仅是因为扫描窗口控件需要几百毫秒的跨进程 COM 调用——它是精确的一层,而不是快速的一层。
关于顺序的说明。 这些层级的排序依据是测量结果,而非理论。常见建议是将无障碍树作为主要方法,但在实际应用中,它不一定更省时:这里遍历 Chrome 的无障碍树耗时 537 毫秒,比全屏 OCR 还慢;而 VS Code 只向它暴露了 18 个元素。无论是像素还是无障碍,在所有情况下都不是最佳选择,因此这里采用的是分层解析,而非二选一。
询问合成器,而不是自行检查
在 Windows 上,桌面合成器已经知道哪些像素发生了变化,并通过 DXGI Desktop Duplication 将这些信息公开。向它查询只需 0.14 毫秒,且无需传输任何像素;相比之下,捕获一帧再发现画面完全相同需要几十毫秒——因此,在空闲观察时会完全跳过捕获。
发生变化时,该帧仍保留在合成器中,因此会直接从 GPU 读取其像素,而不是再通过另一个 API 获取一次——根据这里的测量结果,比 mss 快 1.5–2.3 倍。
合成器仅用作检测变化的快速否定判断。当它报告发生变化时,脏区域仍通过对捕获帧进行哈希来确定:两者的测量时间区间略有不同,因此合成器矩形可能比实际捕获的像素范围报告得更少,而少报的区域中可能包含不会再次读取的文本。在 Desktop Duplication 不可用时,它会静默退化为分块哈希和常规捕获。
使用 --observation-mode delta 为操作工具启用增量观察。为兼容现有客户端,默认值仍为 screenshot。
亲自复现上述全部内容: 请参阅 benchmarks/。
每项决策背后的理由(包括失败的尝试)均记录在 docs/ENGINEERING_LOG.md 中。
配置
OSWright MCP 服务支持以下参数。可以将它们作为 "args" 列表的一部分写入 JSON 配置:
| 选项 | 描述 | 环境变量 |
|---|---|---|
--port <port> | SSE 传输使用的端口。省略时使用 stdio(默认)。 | FASTMCP_PORT |
--host <host> | HTTP/SSE 服务绑定的主机。默认值:127.0.0.1。 | FASTMCP_HOST |
--transport <mode> | 传输协议:stdio、sse、streamable-http。根据 --port 自动检测。 | |
--ocr-languages <langs> | OCR 语言(默认值:en)。示例:--ocr-languages en es fr | OSWRIGHT_OCR_LANGUAGES |
--timeout <seconds> | 自动等待操作的默认超时时间(默认值:10)。 | OSWRIGHT_TIMEOUT |
--snapshot-max-width <px> | 缩小每次操作后返回的自动快照。0(默认值)表示保留完整分辨率。较低的值可大幅降低 token 开销。 | OSWRIGHT_SNAPSHOT_MAX_WIDTH |
--observation-mode <mode> | 操作工具返回的内容:screenshot(默认)、delta(仅返回发生变化的内容,token 数量约减少 30 倍)或 both。 | OSWRIGHT_OBSERVATION_MODE |
--no-atlas | 不跨访问记录屏幕信息。 | OSWRIGHT_NO_ATLAS |
--no-speculate | 不预测操作结果。 | OSWRIGHT_NO_SPECULATE |
--allow-remote | 绑定非环回地址时必须使用。请参阅安全性。 | |
--log-level <level> | 日志级别:DEBUG、INFO、WARNING、ERROR。默认值:INFO。 | OSWRIGHT_LOG_LEVEL |
显式指定的命令行标志始终优先于相应的环境变量。
示例:多语言 OCR
{
"mcpServers": {
"oswright": {
"command": "uvx",
"args": ["oswright", "--ocr-languages", "en", "es", "fr"]
}
}
}
独立 MCP 服务(SSE)
从工作进程或另一台机器运行时,请使用 SSE 传输:
uvx oswright --port 8931
然后在 MCP 客户端配置中:
{
"mcpServers": {
"oswright": {
"url": "http://127.0.0.1:8931/sse"
}
}
}
安全性
**OSWright 没有身份验证。**任何能够访问此端口的人都可以完全控制这台机器的键盘、鼠标、屏幕和剪贴板——这相当于远程接管桌面,而不是使用沙盒化 API。
因此,服务默认绑定到 127.0.0.1,除非传入 --allow-remote,否则拒绝在非环回地址上启动。若要从另一台机器访问,请优先使用 SSH 隧道,而不是暴露端口:
ssh -L 8931:127.0.0.1:8931 user@desktop-host
stdio 传输(默认方式,也是上述所有 MCP 客户端配置使用的方式)完全不会暴露到网络中,是运行 OSWright 的推荐方式。
可能破坏工作的工具会相应标注:close_window 标记为具有破坏性,launch_app 会启动任意程序。截图工具不会覆盖现有的 save_path。
平台说明
| 平台 | 输入后端 | OCR 后端 | 额外下载内容 |
|---|---|---|---|
| Windows | Win32 API (SendInput) | Windows OCR(即时、内置) | **无。**不需要 PyTorch。包含 UI Automation。 |
| Linux | pynput (X11) | EasyOCR | PyTorch(约 2.5 GB)。需要 X11;对 Wayland 的支持有限。 |
| macOS | pynput (Quartz) | EasyOCR | PyTorch(约 2.5 GB)。请在“系统设置”>“隐私”>“辅助功能”中授予辅助功能权限。 |
在 Windows 上不会安装 EasyOCR,因为内置的 Windows OCR 引擎速度更快,也无需下载模型。只有在需要使用 Windows OCR 不支持的语言时,才安装它:
pip install "oswright[easyocr]"
坐标
OCR、图像匹配和 UI Automation 返回的所有坐标都是绝对物理屏幕像素,可以直接传给 mouse_click。子区域以及虚拟桌面原点为负值的多显示器设置也同样适用。screenshot 还会报告 origin_x/origin_y,即图像左上角像素的绝对位置,方便你自行从图像中读取坐标。
工具
屏幕
- screenshot -- 截取整个屏幕或指定区域。以 MCP 原生图像内容的形式返回图像。也可以选择将图像保存到文件路径。
- 只读:是
- get_screen_info —— 获取屏幕尺寸和显示器数量。
- 只读:true
OCR / 文本查找
-
find_text_on_screen —— 使用 OCR 查找屏幕上所有匹配的文本。返回匹配项的坐标和置信度。
- 参数:
text、exact、区域边界、monitor - 只读:true
- 参数:
-
read_screen_text —— 使用 OCR 读取屏幕上所有可见文本。返回每个检测到的文本元素及其位置。
- 参数:区域边界、
monitor - 只读:true
- 参数:区域边界、
图像匹配
- find_image_on_screen —— 使用 OpenCV 模板匹配,在屏幕上查找模板图像的所有匹配项。
- 参数:
template_path、threshold、monitor - 只读:true
- 参数:
鼠标
-
mouse_click —— 在指定坐标或当前位置单击鼠标。返回屏幕截图。
- 参数:
x、y、button、clicks
- 参数:
-
mouse_double_click —— 在指定坐标或当前位置双击。返回屏幕截图。
-
mouse_move —— 将鼠标光标移至屏幕坐标。
-
mouse_scroll —— 滚动鼠标滚轮。返回屏幕截图。
- 参数:
amount、x、y
- 参数:
-
mouse_drag —— 从一个点拖动到另一个点。返回屏幕截图。
- 参数:
start_x、start_y、end_x、end_y、button、duration
- 参数:
-
get_mouse_position —— 获取当前鼠标光标位置。
- 只读:true
键盘
-
type_text —— 逐个字符输入文本。返回屏幕截图。
- 参数:
text、delay
- 参数:
-
press_key —— 按下按键或组合键,例如
Enter、Ctrl+C、Alt+Tab。返回屏幕截图。- 参数:
key
- 参数:
组合操作
-
click_text —— 通过 OCR 查找文本并单击。会自动重试,直到找到文本或超时。返回屏幕截图。
- 参数:
text、exact、button、timeout、poll_interval、monitor
- 参数:
-
double_click_text —— 通过 OCR 查找文本并双击。返回屏幕截图。
-
right_click_text —— 通过 OCR 查找文本并右击。返回屏幕截图。
-
hover_text —— 通过 OCR 查找文本并将鼠标悬停在其上。返回屏幕截图。
-
fill_field —— 查找标签,单击该标签、清除内容并输入值。返回屏幕截图。
- 参数:
target_text、value、exact、timeout、monitor
- 参数:
-
fill_form —— 一次调用即可填写多个字段,减少往返调用。
- 参数:
fields({label, value}列表)、timeout、monitor
- 参数:
-
wait_for_text —— 等待文本出现在屏幕上。通过 OCR 轮询。
- 参数:
text、exact、timeout、poll_interval、monitor - 只读:true
- 参数:
-
wait_for_text_gone —— 等待文本从屏幕上消失。
- 参数:
text、exact、timeout、poll_interval、monitor - 只读:true
- 参数:
-
wait_for_time —— 等待指定时长(最长为 30s),然后截取屏幕截图。
窗口管理
-
list_windows —— 列出所有可见窗口。可按标题子字符串筛选。
- 参数:
title_filter - 只读:true
- 参数:
-
focus_window —— 按标题将窗口置于前台。返回屏幕截图。
- 参数:
title
- 参数:
-
close_window —— 按标题关闭窗口(发送 WM_CLOSE)。返回屏幕截图。
- 参数:
title
- 参数:
-
minimize_window —— 按标题最小化窗口。返回屏幕截图。
- 参数:
title
- 参数:
-
screenshot_window —— 仅截取一个窗口的屏幕截图。
- 参数:
title、save_path - 只读:true
- 参数:
剪贴板
-
get_clipboard —— 获取系统剪贴板中的当前文本内容。
- 只读:true
-
set_clipboard —— 将文本复制到系统剪贴板。
- 参数:
text
- 参数:
应用管理
-
launch_app -- 启动应用,并可选择等待其加载。直接运行程序,不会通过 shell 运行。
- 参数:
command、args、wait_text、timeout - 报告
wait_text_found,以便判断应用是否确实已加载。
- 参数:
-
get_ocr_info -- 获取当前 OCR 后端及可用后端的信息。
- 只读:true
增量感知
-
observe -- 报告自上次观察以来屏幕上的变化。仅重新扫描移动过的区域。跟踪状态时优先使用此工具,而非
screenshot。- 参数:
force_full - 只读:true
- 参数:
-
find_element -- 使用能够回答问题的最省资源的方法查找屏幕上的文本。报告哪个级联层级响应了请求。
- 参数:
text、exact、window_title - 只读:true
- 参数:
-
click_element -- 通过级联查找文本并点击它。比
click_text更省资源的替代方案。- 参数:
text、exact、button、window_title
- 参数:
-
read_model_text -- 从增量模型读取屏幕上的文本,无需重新对显示内容进行 OCR。
- 参数:
query、limit - 只读:true
- 参数:
-
perception_stats -- 报告模型避免了多少感知工作。
- 只读:true
-
remember_screen -- 记住当前屏幕,以便后续访问时跳过读取。此记忆会跨会话保留。
- 只读:true
-
atlas_stats -- 报告屏幕图谱记住了哪些内容,以及它发挥作用的频率。
- 只读:true
辅助功能 / UI 自动化(Windows)
-
get_ui_tree -- 获取当前焦点窗口的辅助功能树。返回所有交互元素的名称、类型和位置。结果确定且即时。
- 参数:
window_title、max_depth - 只读:true
- 参数:
-
click_ui_element -- 使用辅助功能树点击 UI 元素。比 OCR 更可靠。
- 参数:
name、control_type、automation_id、window_title
- 参数:
-
fill_ui_element -- 设置 UI 元素的值(例如文本框)。比基于 OCR 的填充更可靠。
- 参数:
value、name、automation_id、window_title
- 参数:
高级屏幕功能
-
get_active_window -- 获取当前焦点窗口的信息。
- 只读:true
-
wait_for_change -- 等待屏幕发生视觉变化。先截取基准屏幕截图,然后轮询,直到屏幕出现变化。
- 参数:
timeout、poll_interval
- 参数:
Python 库
OSWright 也可以作为独立的 Python 库使用,并提供类似 Playwright 的 API:
from oswright import OSWright
with OSWright() as ow:
screen = ow.screen()
screen.click(text="Start")
screen.type_text("Hello World")
screen.press("Ctrl+S")
screen.screenshot("desktop.png")
更多内容请参见 examples/ 目录。
架构
oswright/
__init__.py # Package entry point (single source of __version__)
core.py # OSWright class (= Browser)
screen.py # Screen class (= Page)
locator.py # Locator + Assertions (= Locator + expect)
capture.py # Screen capture (mss - cross-platform, thread-safe)
dirty.py # Change detection - which parts of the screen moved
screenmodel.py # Persistent screen model, updated incrementally
cascade.py # Resolution cascade - cheapest method that can answer
atlas.py # Remembers screens across visits and sessions
settle.py # Knowing when the screen has finished responding
speculate.py # Predicting what an action does, instead of looking
textprovider.py # Exact text from the app itself via UIA TextPattern
detect.py # OCR dispatcher with caching (auto-selects best backend)
_ocr_windows.py # Windows OCR backend (instant, built-in)
accessibility.py # Windows UI Automation (deterministic element finding)
cache.py # Screenshot diffing, image hashing, OCR result cache
_dpi.py # Process DPI awareness (keeps every API in physical pixels)
_dxgi_windows.py # Compositor dirty rectangles via DXGI Desktop Duplication
input.py # Platform dispatcher for input backends
_input_windows.py # Windows input backend (Win32 API)
_input_pynput.py # Linux/macOS input backend (pynput)
window.py # Window management (list, focus, close)
clipboard.py # Clipboard read/write (cross-platform)
mcp_server.py # MCP server (43 tools for AI agents)
tests/
conftest.py # Fixtures that skip when no display/OCR is available
test_core.py # Unit tests (no desktop required)
test_perception.py # Incremental perception (stubbed, runs headless)
test_atlas.py # Screen memory and its failure modes (headless)
test_speculate.py # Prediction, settling, and their limits (headless)
test_e2e.py # End-to-end tests against the real desktop (marked `e2e`)
记住屏幕
应用的行为具有确定性——同一个对话框每次都会有相同的布局。 OSWright 会记住已读取的屏幕,并在下次访问时跨会话复用:冷读耗时 125 ms → 热回忆耗时 1.4 ms(89×)。
仅凭识别结果绝不会直接信任已记住的屏幕。复用布局前,会通过 像素 对少数区域进行抽查,因此,如果屏幕发生变化,就会拒绝该布局,而不会基于它采取操作。验证失败时会采取封闭策略:完全没有可检查内容的屏幕不会被记住。
使用 --no-atlas 禁用。已记住的屏幕存储在 ~/.oswright/atlas.json 中。
预测操作,而不是观察操作
应用程序具有确定性——点击“保存”每次都会打开相同的对话框——因此,首次观察后,操作结果便已可知。OSWright 会学习各项操作的效果,并确认预期画面,而不是再次读取:成本比观察低 19–23 倍(2.3 毫秒,而观察需要 43–50 毫秒)。
一个预测必须连续两次得到验证才会被信任;如果证明有误,就会被弃用;并且会像已记住的画面一样,用相同的两种方式进行检查。预测失败时,会向 Agent 报告为 surprise——界面出现了平常不会发生的情况,值得了解,而不是悄悄忽略。
**已确认的预测能保证什么:**布局相同——控件及其位置都相同。但不保证每个字符都完全一致。一个数字的变化所改变的像素,比闪烁的插入符还少,因此无论分辨率如何,都无法通过整屏检查将两者区分开来。需要精确文本时,请使用 observe(force_full=True)。
使用 --no-speculate 禁用。
只等待所需的时间
操作工具过去会固定休眠 300 毫秒,这个时长是按最慢的情况设定的,因此每次操作都要等待最坏情况所需的时间。合成器知道屏幕何时停止变化,所以现在会在界面实际稳定时结束等待:
| 之前的固定休眠时间 | 300 毫秒 |
| 实际等待时间的中位数 | 61.5 毫秒 |
| 执行 50 步任务节省的时间 | 11.9 秒 |
“稳定”是指最近没有发生大幅变化,而不是毫无变化:真实桌面永远不会静止——插入符和时钟每约 18 毫秒就会产生一次变化事件,影响约 32 个像素;而真正的 UI 变化会影响数万个像素。
尚未完成
- Wayland 输入注入,以及作为 TextPattern 等效方案的 macOS
AXTextMarker。 - 用于既无法访问、文本又不可读的界面的视觉模型层:游戏、画布、图像编辑器。
- 基于多个前序画面触发的状态转换,用于结果取决于不可见状态的操作。
测量了什么,未测量什么
本机同时测量了感知成本和任务成功率,并可通过 benchmarks/ 复现——在四款应用中,更低的感知成本并未牺牲准确性;像素与无障碍信息的取舍也经过了测量,而非仅凭论证。
与 Windows-MCP 对比
相同的任务、四种场景,每项都由应用本身评分。两个工具都不会给自己评分,而 Windows-MCP 使用其默认设置运行:
| 计算器 | 资源管理器 | Chrome | Chrome,2 步 | 通过 | 令牌数 | |
|---|---|---|---|---|---|---|
| oswright | 5/5 | 4/5 | 5/5 | 5/5 | 19/20 | 832 |
| Windows-MCP,每次操作截取一次快照 | 5/5 | 5/5 | 5/5 | 5/5 | 20/20 | 14,053 |
| Windows-MCP,仅截取一次快照 | 5/5 | 5/5 | 5/5 | 5/5 | 20/20 | 8,214 |
如实来看:Windows-MCP 更可靠,而 oswright 的成本低了 16.9 倍。 oswright 漏掉了 20 次点击中的 1 次,发生在刚刚打开的窗口中。
成本差异源自架构,而非调优带来的优势。Windows-MCP 将屏幕内容返回给 Agent——Snapshot 会将辅助功能树渲染为 (x,y) button "Seven" [action: click]——并接收坐标,因此每次操作都会将屏幕描述计入模型上下文。oswright 接收文本并返回结果。
可靠性差距可能是速度造成的:oswright 会在约 100 毫秒内解析并点击,有时窗口刚获得焦点、尚未准备好接收输入时就已点击;而较慢的循环能给应用留出它从未要求过的等待时间。这只是一个假设,而非结论——在操作前增加等待,在十次试验中并未带来可测量的差异,因此我们记录了这一点,但未作修复。
设置 Chrome,2 步 这一场景,是因为这里的其他任务都很短,工具可以读取一次屏幕并复用那些坐标。在这个场景中,第一次点击会使控件在页面上向下移动 325 px,因此仅截取一次快照的配置不得不重新读取屏幕——所以对于界面会移动的任务,它的低成本数据并不成立,实际成本与每次操作截取快照的配置相同。
运行 python benchmarks/bench_head_to_head.py 即可复现(设置方法见文件的文档字符串)。
本测试无法证明的事项: 在一台笔记本电脑上进行的四项短任务。它并未涉及长时间多步任务、故障恢复或产品成熟度——Windows-MCP 有 OAuth、分析功能、看门狗和安装程序;oswright 则都没有。它的辅助功能遍历还会读取 Chrome 的页面内容,而 oswright 自身的辅助功能层级不会读取这些内容。主张是“每次操作的成本大幅降低,可靠性略有损失”,而不是“产品更好”。
开发
pip install -e ".[dev]"
pytest tests/ # everything available on this machine
pytest tests/ -m "not e2e" # unit tests only, no desktop needed
ruff check oswright tests # lint
python benchmarks/bench_pipeline.py # reproduce the performance numbers
python benchmarks/bench_tasks.py # task success (opens Calculator repeatedly)
设计决策、测量数据和失败尝试记录在
docs/ENGINEERING_LOG.md 中。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。