computer-use-omni 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- computer_use_omni-1.0.0/.gitignore +30 -0
- computer_use_omni-1.0.0/LICENSE +21 -0
- computer_use_omni-1.0.0/PKG-INFO +166 -0
- computer_use_omni-1.0.0/README.md +138 -0
- computer_use_omni-1.0.0/SPEC.md +173 -0
- computer_use_omni-1.0.0/pyproject.toml +42 -0
- computer_use_omni-1.0.0/server.json +46 -0
- computer_use_omni-1.0.0/src/computer_use_omni/__init__.py +7 -0
- computer_use_omni-1.0.0/src/computer_use_omni/__main__.py +37 -0
- computer_use_omni-1.0.0/src/computer_use_omni/apps.py +757 -0
- computer_use_omni-1.0.0/src/computer_use_omni/batch.py +602 -0
- computer_use_omni-1.0.0/src/computer_use_omni/clipboard.py +90 -0
- computer_use_omni-1.0.0/src/computer_use_omni/config.py +124 -0
- computer_use_omni-1.0.0/src/computer_use_omni/inputs.py +343 -0
- computer_use_omni-1.0.0/src/computer_use_omni/keyboard.py +281 -0
- computer_use_omni-1.0.0/src/computer_use_omni/keymap.py +378 -0
- computer_use_omni-1.0.0/src/computer_use_omni/logsetup.py +91 -0
- computer_use_omni-1.0.0/src/computer_use_omni/overlay.py +1214 -0
- computer_use_omni-1.0.0/src/computer_use_omni/permissions.py +256 -0
- computer_use_omni-1.0.0/src/computer_use_omni/screen.py +597 -0
- computer_use_omni-1.0.0/src/computer_use_omni/server.py +726 -0
- computer_use_omni-1.0.0/src/computer_use_omni/terminal.py +1373 -0
- computer_use_omni-1.0.0/tests/drive.py +75 -0
- computer_use_omni-1.0.0/tests/drive_timed.py +94 -0
- computer_use_omni-1.0.0/tests/mcp_client.py +205 -0
- computer_use_omni-1.0.0/tests/scen_calc.json +10 -0
- computer_use_omni-1.0.0/tests/scen_cap.json +4 -0
- computer_use_omni-1.0.0/tests/scen_clip.json +8 -0
- computer_use_omni-1.0.0/tests/scen_hold.json +15 -0
- computer_use_omni-1.0.0/tests/scen_logtest.json +5 -0
- computer_use_omni-1.0.0/tests/scen_notepad.json +11 -0
- computer_use_omni-1.0.0/tests/scen_np1.json +6 -0
- computer_use_omni-1.0.0/tests/scen_paint.json +8 -0
- computer_use_omni-1.0.0/tests/scen_paint_draw.json +11 -0
- computer_use_omni-1.0.0/tests/scen_paint_draw2.json +11 -0
- computer_use_omni-1.0.0/tests/scen_phase3.json +7 -0
- computer_use_omni-1.0.0/tests/scen_reload.json +5 -0
- computer_use_omni-1.0.0/tests/scen_shot.json +4 -0
- computer_use_omni-1.0.0/tests/scen_sweep.json +17 -0
- computer_use_omni-1.0.0/tests/scen_type.json +11 -0
- computer_use_omni-1.0.0/tests/scen_uwp.json +6 -0
- computer_use_omni-1.0.0/tests/test_launch.py +82 -0
- computer_use_omni-1.0.0/tests/test_selfheal.py +101 -0
- computer_use_omni-1.0.0/uv.lock +823 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python / uv
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*.egg-info/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
|
|
11
|
+
# Generated test output (screenshots, crops, captures, results)
|
|
12
|
+
tests/out*/
|
|
13
|
+
|
|
14
|
+
# one-off development probes (stale paths / Win32 diagnostics) — kept locally, not shipped
|
|
15
|
+
tests/dev/
|
|
16
|
+
|
|
17
|
+
# Runtime logs
|
|
18
|
+
logs/
|
|
19
|
+
|
|
20
|
+
# OS / editor
|
|
21
|
+
Thumbs.db
|
|
22
|
+
.DS_Store
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
|
|
26
|
+
# local-only AI feedback notes (not version-controlled)
|
|
27
|
+
feedback/
|
|
28
|
+
|
|
29
|
+
# private workspace (KANBAN / dev-log / task docs) — never in the public repo
|
|
30
|
+
dev-workplace/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason26214
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: computer-use-omni
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Windows computer-use MCP for the Claude Code CLI — a 1:1 replica of Claude Desktop's computer-use tool surface.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Jason26214/computer-use-omni
|
|
6
|
+
Project-URL: Repository, https://github.com/Jason26214/computer-use-omni
|
|
7
|
+
Project-URL: Issues, https://github.com/Jason26214/computer-use-omni/issues
|
|
8
|
+
Author: Jason26214
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: automation,claude,computer-use,desktop,mcp,windows
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Desktop Environment
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: mcp
|
|
24
|
+
Requires-Dist: mss
|
|
25
|
+
Requires-Dist: pillow
|
|
26
|
+
Requires-Dist: pywin32
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# computer-use-omni
|
|
30
|
+
|
|
31
|
+
A faithful **1:1 replica of Anthropic's official `computer-use` tool surface** — for **Windows**, where Anthropic doesn't ship it. It lets the **Claude Code CLI** (or any MCP client) drive the Windows desktop the way Claude Desktop's built-in computer-use does: screenshots, mouse / keyboard / scroll / drag / batch input, clipboard, multi-monitor, and application launch.
|
|
32
|
+
|
|
33
|
+
Same tool names, same parameters, same coordinate semantics as the desktop tool — an agent that already knows Anthropic's computer-use works here unchanged. Built and verified against Claude Desktop's own `computer-use` as the ground-truth oracle.
|
|
34
|
+
|
|
35
|
+
> Vision-and-coordinate based, like the official tool (rather than a UI-tree / accessibility approach) — for when you want Anthropic's computer-use paradigm on a Windows CLI.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Requires Windows and Python 3.11+ (uv / uvx fetch Python for you).
|
|
40
|
+
|
|
41
|
+
Run standalone:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
uvx computer-use-omni
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Add to the Claude Code CLI:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
claude mcp add computer-use-omni -s user -- uvx computer-use-omni
|
|
51
|
+
claude mcp list # expect: computer-use-omni … ✓ Connected
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or in Claude Desktop's `claude_desktop_config.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"computer-use-omni": { "command": "uvx", "args": ["computer-use-omni"] }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Example
|
|
65
|
+
|
|
66
|
+
On a multi-monitor rig every screenshot self-describes — it names the monitor it captured and what else is attached, so the agent never loses track of which screen it is looking at:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
This screenshot was taken on monitor "\\.\DISPLAY2" (2560x1600, primary).
|
|
70
|
+
Other attached monitors: "\\.\DISPLAY1" (2560x1440). Use switch_display to
|
|
71
|
+
capture a different monitor.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`open_application` reports what actually happened instead of fire-and-forget:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Opened "Calculator". # a real window appeared
|
|
78
|
+
launched "…" but its process exited within ~2s # crashed on startup — reported, not faked
|
|
79
|
+
without showing a window …
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Status
|
|
83
|
+
|
|
84
|
+
- **29 tools** — 27 matching Claude Desktop's computer-use, plus `deactivate` and `display_overview`; a dev `reload` tool makes **30** when `COMPUTER_USE_DEV=on`.
|
|
85
|
+
- **16-scenario end-to-end suite** under `tests/` (`scen_*.json`), each run through a fresh MCP process.
|
|
86
|
+
- Built and verified on **Windows 11** (2560×1600 @ 150% DPI) against Claude Desktop's computer-use as the oracle; the screenshot downscale matches CDC's ~1.2 MP within rounding.
|
|
87
|
+
- **In daily use since 2026-06.**
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
**Matching Anthropic's computer-use (27):** `request_access`, `list_granted_applications`, `request_teach_access`, `screenshot`, `zoom`, `switch_display`, `cursor_position`, `mouse_move`, `left_click`, `right_click`, `middle_click`, `double_click`, `triple_click`, `left_click_drag`, `left_mouse_down`, `left_mouse_up`, `scroll`, `key`, `hold_key`, `type`, `wait`, `open_application`, `read_clipboard`, `write_clipboard`, `computer_batch`, `teach_step`, `teach_batch`.
|
|
92
|
+
|
|
93
|
+
**omni-specific (2):**
|
|
94
|
+
|
|
95
|
+
- `deactivate` — end the session without killing the process (glow off, controlling window restored, grants revoked); the counterpart to `request_access`.
|
|
96
|
+
- `display_overview` — one composite, labeled image of all monitors laid out per the virtual-desktop arrangement (an orientation aid for "which screen is that window on?"; not a click surface).
|
|
97
|
+
|
|
98
|
+
All click / move / scroll / drag / zoom coordinates are in the **image-pixel space of the most recent screenshot**; the server maps them back to physical pixels.
|
|
99
|
+
|
|
100
|
+
## How it works (the parts worth reading)
|
|
101
|
+
|
|
102
|
+
**Faithful desktop visuals.** While a session is active the server reproduces Claude Desktop's on-screen affordances, pixel-calibrated from reference captures: a static orange edge glow, a centered "Claude is using your computer" pill that flies to the corner, and the controlling window (the Windows Terminal running the CLI, or the Claude Desktop window itself) shrunk flush to the top-right and **parked off-screen during each capture** so screenshots show the true desktop with no black box. Click-through is kind-aware: a non-layered terminal drops to the bottom of the z-order for each synthetic click; the layered Claude Desktop window gets `WS_EX_TRANSPARENT` (the desktop tool's own approach) so clicks pass through to whatever is beneath — the window itself never moves, so the user's real mouse is unaffected.
|
|
103
|
+
|
|
104
|
+
**Keyboard self-harm guard.** Synthetic keystrokes go to whatever holds OS focus. If the controlling window (the Claude window, or the hosting terminal) is frontmost, `type` / `key` are **blocked** — otherwise the text would land in the agent's own conversation, or run as a shell command with a trailing Return. The guard is unconditional and identifies the control surface by window identity and owning process, while still leaving a second, unrelated terminal window a legitimate target. Mouse actions are exempt (a click carries its own coordinate).
|
|
105
|
+
|
|
106
|
+
**Multi-monitor.** Screenshots carry an event-driven note naming the captured monitor and flagging when it changed; `open_application` warns when a window opened on a different monitor than captures currently target — precisely, by monitor name, when it has the window handle; `display_overview` returns the all-screens map. The glow and shrink land on the controlling window's own monitor, leaving other displays untouched.
|
|
107
|
+
|
|
108
|
+
**Honest launching & self-heal.** `open_application` polls for a real window and distinguishes *opened* / *running-no-window-yet* / *crashed-on-startup* / *nothing-launched* instead of always reporting success. A force-killed session's shrunk terminal is restored on the next start from a small state file (guarded against window-handle reuse).
|
|
109
|
+
|
|
110
|
+
**Hot-reload (dev).** Set `COMPUTER_USE_DEV=on` to add a `reload` tool that `importlib.reload`s the logic modules in-process, so edited code takes effect without restarting the session — handy while developing automation against the server. It is off by default (the clean 29-tool surface).
|
|
111
|
+
|
|
112
|
+
See **[SPEC.md](SPEC.md)** for the authoritative, tool-by-tool contract and the module architecture.
|
|
113
|
+
|
|
114
|
+
## Differences from the desktop tool (by design)
|
|
115
|
+
|
|
116
|
+
A CLI has no permission GUI, so `request_access` **auto-grants** resolvable apps at `tier:"full"` and returns the same JSON shape; foreground gating is permissive by default (it only errors on an empty allowlist). Masking of non-allowlisted windows defaults off (the rect-based masker over-masks). Teach mode is a stub — it executes the step's actions and returns a screenshot, but there is no fullscreen tooltip overlay (a desktop-app feature). Each of these is controlled by the env vars below.
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
| Variable | Default | Meaning |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `COMPUTER_USE_MAX_PIXELS` | `1200000` | Max pixels in a downscaled screenshot (≈ 1.2 MP). |
|
|
123
|
+
| `COMPUTER_USE_MASKING` | `off` | Mask non-allowlisted app windows in screenshots. |
|
|
124
|
+
| `COMPUTER_USE_ENFORCE_FOREGROUND` | `off` | Block input when the frontmost app isn't allowlisted. |
|
|
125
|
+
| `COMPUTER_USE_AUTOGRANT` | `on` | Auto-grant resolvable apps on `request_access`. |
|
|
126
|
+
| `COMPUTER_USE_DEV` | `off` | Register the developer `reload` hot-reload tool (on → 30 tools). |
|
|
127
|
+
| `COMPUTER_USE_LOG_DIR` | `%LOCALAPPDATA%\computer-use-omni\logs` | Directory for the rotating `mcp.log` (tool calls + tracebacks). |
|
|
128
|
+
| `COMPUTER_USE_GLOW` | `on` | Static orange edge glow while a session is active. |
|
|
129
|
+
| `COMPUTER_USE_SHRINK_TERMINAL` | `on` | Shrink the controlling window to the top-right corner. |
|
|
130
|
+
| `COMPUTER_USE_HIDE_CONTROLLING` | `on` | Park the controlling window off-screen during captures. |
|
|
131
|
+
| `COMPUTER_USE_PILL` | `on` | Centered "Claude is using your computer" pill. |
|
|
132
|
+
| `COMPUTER_USE_GLOW_COLOR` | `217,119,87` | Glow / pill color (`#D97757`). |
|
|
133
|
+
| `COMPUTER_USE_GLOW_ALPHA` | `0.4` | Peak glow opacity at the very edge. |
|
|
134
|
+
| `COMPUTER_USE_GLOW_BAND` | `0.05` | Glow band width as a fraction of the smaller screen dimension. |
|
|
135
|
+
| `COMPUTER_USE_GLOW_EXCLUDE` | `on` | Exclude the glow / pill from captures. |
|
|
136
|
+
|
|
137
|
+
## Gotchas (real Windows behavior)
|
|
138
|
+
|
|
139
|
+
- **IME affects `key` / `hold_key`, not `type`.** `type` injects Unicode directly and bypasses the input method (CJK and emoji work in any layout). `key` / `hold_key` send virtual-key codes that pass **through** the active IME — so with a Chinese IME active, sending the letter `a` opens a pinyin candidate list instead of typing `a`. Use `type` for text; switch the IME to English for letter shortcuts.
|
|
140
|
+
- **Elevated apps** (Task Manager, UAC prompts, admin installers) can't be driven — Windows UIPI blocks input from a non-elevated process. Same limitation as the desktop tool.
|
|
141
|
+
|
|
142
|
+
## Tests
|
|
143
|
+
|
|
144
|
+
`tests/` holds a 16-scenario end-to-end suite (`scen_*.json`) plus the driver that launches a fresh server and runs a scenario's JSON list of tool calls, interleaving the returned images:
|
|
145
|
+
|
|
146
|
+
```powershell
|
|
147
|
+
uv run python tests/drive.py tests/scen_calc.json # compute 7×8 by clicks, screenshot, verify
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`tests/dev/` holds the one-off Win32 probes written during development (glow/pill sampling, z-order and capture-affinity experiments).
|
|
151
|
+
|
|
152
|
+
## Tech stack
|
|
153
|
+
|
|
154
|
+
Python ≥ 3.11, packaged with [uv], src layout, hatchling. [`mcp`] Python SDK (FastMCP) over stdio; [`mss`] for capture; [`pillow`] for imaging; [`pywin32`] + `ctypes` for DPI awareness, window / clipboard / foreground access, and raw Win32 `SendInput` mouse/keyboard injection.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT © Jason26214
|
|
159
|
+
|
|
160
|
+
[uv]: https://github.com/astral-sh/uv
|
|
161
|
+
[`mcp`]: https://github.com/modelcontextprotocol/python-sdk
|
|
162
|
+
[`mss`]: https://github.com/BoboTiG/python-mss
|
|
163
|
+
[`pillow`]: https://python-pillow.org/
|
|
164
|
+
[`pywin32`]: https://github.com/mhammond/pywin32
|
|
165
|
+
|
|
166
|
+
<!-- mcp-name: io.github.Jason26214/computer-use-omni -->
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# computer-use-omni
|
|
2
|
+
|
|
3
|
+
A faithful **1:1 replica of Anthropic's official `computer-use` tool surface** — for **Windows**, where Anthropic doesn't ship it. It lets the **Claude Code CLI** (or any MCP client) drive the Windows desktop the way Claude Desktop's built-in computer-use does: screenshots, mouse / keyboard / scroll / drag / batch input, clipboard, multi-monitor, and application launch.
|
|
4
|
+
|
|
5
|
+
Same tool names, same parameters, same coordinate semantics as the desktop tool — an agent that already knows Anthropic's computer-use works here unchanged. Built and verified against Claude Desktop's own `computer-use` as the ground-truth oracle.
|
|
6
|
+
|
|
7
|
+
> Vision-and-coordinate based, like the official tool (rather than a UI-tree / accessibility approach) — for when you want Anthropic's computer-use paradigm on a Windows CLI.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Requires Windows and Python 3.11+ (uv / uvx fetch Python for you).
|
|
12
|
+
|
|
13
|
+
Run standalone:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
uvx computer-use-omni
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Add to the Claude Code CLI:
|
|
20
|
+
|
|
21
|
+
```powershell
|
|
22
|
+
claude mcp add computer-use-omni -s user -- uvx computer-use-omni
|
|
23
|
+
claude mcp list # expect: computer-use-omni … ✓ Connected
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or in Claude Desktop's `claude_desktop_config.json`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"computer-use-omni": { "command": "uvx", "args": ["computer-use-omni"] }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
On a multi-monitor rig every screenshot self-describes — it names the monitor it captured and what else is attached, so the agent never loses track of which screen it is looking at:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
This screenshot was taken on monitor "\\.\DISPLAY2" (2560x1600, primary).
|
|
42
|
+
Other attached monitors: "\\.\DISPLAY1" (2560x1440). Use switch_display to
|
|
43
|
+
capture a different monitor.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`open_application` reports what actually happened instead of fire-and-forget:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Opened "Calculator". # a real window appeared
|
|
50
|
+
launched "…" but its process exited within ~2s # crashed on startup — reported, not faked
|
|
51
|
+
without showing a window …
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Status
|
|
55
|
+
|
|
56
|
+
- **29 tools** — 27 matching Claude Desktop's computer-use, plus `deactivate` and `display_overview`; a dev `reload` tool makes **30** when `COMPUTER_USE_DEV=on`.
|
|
57
|
+
- **16-scenario end-to-end suite** under `tests/` (`scen_*.json`), each run through a fresh MCP process.
|
|
58
|
+
- Built and verified on **Windows 11** (2560×1600 @ 150% DPI) against Claude Desktop's computer-use as the oracle; the screenshot downscale matches CDC's ~1.2 MP within rounding.
|
|
59
|
+
- **In daily use since 2026-06.**
|
|
60
|
+
|
|
61
|
+
## Tools
|
|
62
|
+
|
|
63
|
+
**Matching Anthropic's computer-use (27):** `request_access`, `list_granted_applications`, `request_teach_access`, `screenshot`, `zoom`, `switch_display`, `cursor_position`, `mouse_move`, `left_click`, `right_click`, `middle_click`, `double_click`, `triple_click`, `left_click_drag`, `left_mouse_down`, `left_mouse_up`, `scroll`, `key`, `hold_key`, `type`, `wait`, `open_application`, `read_clipboard`, `write_clipboard`, `computer_batch`, `teach_step`, `teach_batch`.
|
|
64
|
+
|
|
65
|
+
**omni-specific (2):**
|
|
66
|
+
|
|
67
|
+
- `deactivate` — end the session without killing the process (glow off, controlling window restored, grants revoked); the counterpart to `request_access`.
|
|
68
|
+
- `display_overview` — one composite, labeled image of all monitors laid out per the virtual-desktop arrangement (an orientation aid for "which screen is that window on?"; not a click surface).
|
|
69
|
+
|
|
70
|
+
All click / move / scroll / drag / zoom coordinates are in the **image-pixel space of the most recent screenshot**; the server maps them back to physical pixels.
|
|
71
|
+
|
|
72
|
+
## How it works (the parts worth reading)
|
|
73
|
+
|
|
74
|
+
**Faithful desktop visuals.** While a session is active the server reproduces Claude Desktop's on-screen affordances, pixel-calibrated from reference captures: a static orange edge glow, a centered "Claude is using your computer" pill that flies to the corner, and the controlling window (the Windows Terminal running the CLI, or the Claude Desktop window itself) shrunk flush to the top-right and **parked off-screen during each capture** so screenshots show the true desktop with no black box. Click-through is kind-aware: a non-layered terminal drops to the bottom of the z-order for each synthetic click; the layered Claude Desktop window gets `WS_EX_TRANSPARENT` (the desktop tool's own approach) so clicks pass through to whatever is beneath — the window itself never moves, so the user's real mouse is unaffected.
|
|
75
|
+
|
|
76
|
+
**Keyboard self-harm guard.** Synthetic keystrokes go to whatever holds OS focus. If the controlling window (the Claude window, or the hosting terminal) is frontmost, `type` / `key` are **blocked** — otherwise the text would land in the agent's own conversation, or run as a shell command with a trailing Return. The guard is unconditional and identifies the control surface by window identity and owning process, while still leaving a second, unrelated terminal window a legitimate target. Mouse actions are exempt (a click carries its own coordinate).
|
|
77
|
+
|
|
78
|
+
**Multi-monitor.** Screenshots carry an event-driven note naming the captured monitor and flagging when it changed; `open_application` warns when a window opened on a different monitor than captures currently target — precisely, by monitor name, when it has the window handle; `display_overview` returns the all-screens map. The glow and shrink land on the controlling window's own monitor, leaving other displays untouched.
|
|
79
|
+
|
|
80
|
+
**Honest launching & self-heal.** `open_application` polls for a real window and distinguishes *opened* / *running-no-window-yet* / *crashed-on-startup* / *nothing-launched* instead of always reporting success. A force-killed session's shrunk terminal is restored on the next start from a small state file (guarded against window-handle reuse).
|
|
81
|
+
|
|
82
|
+
**Hot-reload (dev).** Set `COMPUTER_USE_DEV=on` to add a `reload` tool that `importlib.reload`s the logic modules in-process, so edited code takes effect without restarting the session — handy while developing automation against the server. It is off by default (the clean 29-tool surface).
|
|
83
|
+
|
|
84
|
+
See **[SPEC.md](SPEC.md)** for the authoritative, tool-by-tool contract and the module architecture.
|
|
85
|
+
|
|
86
|
+
## Differences from the desktop tool (by design)
|
|
87
|
+
|
|
88
|
+
A CLI has no permission GUI, so `request_access` **auto-grants** resolvable apps at `tier:"full"` and returns the same JSON shape; foreground gating is permissive by default (it only errors on an empty allowlist). Masking of non-allowlisted windows defaults off (the rect-based masker over-masks). Teach mode is a stub — it executes the step's actions and returns a screenshot, but there is no fullscreen tooltip overlay (a desktop-app feature). Each of these is controlled by the env vars below.
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
| Variable | Default | Meaning |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `COMPUTER_USE_MAX_PIXELS` | `1200000` | Max pixels in a downscaled screenshot (≈ 1.2 MP). |
|
|
95
|
+
| `COMPUTER_USE_MASKING` | `off` | Mask non-allowlisted app windows in screenshots. |
|
|
96
|
+
| `COMPUTER_USE_ENFORCE_FOREGROUND` | `off` | Block input when the frontmost app isn't allowlisted. |
|
|
97
|
+
| `COMPUTER_USE_AUTOGRANT` | `on` | Auto-grant resolvable apps on `request_access`. |
|
|
98
|
+
| `COMPUTER_USE_DEV` | `off` | Register the developer `reload` hot-reload tool (on → 30 tools). |
|
|
99
|
+
| `COMPUTER_USE_LOG_DIR` | `%LOCALAPPDATA%\computer-use-omni\logs` | Directory for the rotating `mcp.log` (tool calls + tracebacks). |
|
|
100
|
+
| `COMPUTER_USE_GLOW` | `on` | Static orange edge glow while a session is active. |
|
|
101
|
+
| `COMPUTER_USE_SHRINK_TERMINAL` | `on` | Shrink the controlling window to the top-right corner. |
|
|
102
|
+
| `COMPUTER_USE_HIDE_CONTROLLING` | `on` | Park the controlling window off-screen during captures. |
|
|
103
|
+
| `COMPUTER_USE_PILL` | `on` | Centered "Claude is using your computer" pill. |
|
|
104
|
+
| `COMPUTER_USE_GLOW_COLOR` | `217,119,87` | Glow / pill color (`#D97757`). |
|
|
105
|
+
| `COMPUTER_USE_GLOW_ALPHA` | `0.4` | Peak glow opacity at the very edge. |
|
|
106
|
+
| `COMPUTER_USE_GLOW_BAND` | `0.05` | Glow band width as a fraction of the smaller screen dimension. |
|
|
107
|
+
| `COMPUTER_USE_GLOW_EXCLUDE` | `on` | Exclude the glow / pill from captures. |
|
|
108
|
+
|
|
109
|
+
## Gotchas (real Windows behavior)
|
|
110
|
+
|
|
111
|
+
- **IME affects `key` / `hold_key`, not `type`.** `type` injects Unicode directly and bypasses the input method (CJK and emoji work in any layout). `key` / `hold_key` send virtual-key codes that pass **through** the active IME — so with a Chinese IME active, sending the letter `a` opens a pinyin candidate list instead of typing `a`. Use `type` for text; switch the IME to English for letter shortcuts.
|
|
112
|
+
- **Elevated apps** (Task Manager, UAC prompts, admin installers) can't be driven — Windows UIPI blocks input from a non-elevated process. Same limitation as the desktop tool.
|
|
113
|
+
|
|
114
|
+
## Tests
|
|
115
|
+
|
|
116
|
+
`tests/` holds a 16-scenario end-to-end suite (`scen_*.json`) plus the driver that launches a fresh server and runs a scenario's JSON list of tool calls, interleaving the returned images:
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
uv run python tests/drive.py tests/scen_calc.json # compute 7×8 by clicks, screenshot, verify
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`tests/dev/` holds the one-off Win32 probes written during development (glow/pill sampling, z-order and capture-affinity experiments).
|
|
123
|
+
|
|
124
|
+
## Tech stack
|
|
125
|
+
|
|
126
|
+
Python ≥ 3.11, packaged with [uv], src layout, hatchling. [`mcp`] Python SDK (FastMCP) over stdio; [`mss`] for capture; [`pillow`] for imaging; [`pywin32`] + `ctypes` for DPI awareness, window / clipboard / foreground access, and raw Win32 `SendInput` mouse/keyboard injection.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT © Jason26214
|
|
131
|
+
|
|
132
|
+
[uv]: https://github.com/astral-sh/uv
|
|
133
|
+
[`mcp`]: https://github.com/modelcontextprotocol/python-sdk
|
|
134
|
+
[`mss`]: https://github.com/BoboTiG/python-mss
|
|
135
|
+
[`pillow`]: https://python-pillow.org/
|
|
136
|
+
[`pywin32`]: https://github.com/mhammond/pywin32
|
|
137
|
+
|
|
138
|
+
<!-- mcp-name: io.github.Jason26214/computer-use-omni -->
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# PROJECT: computer-use-omni — Windows computer-use MCP for Claude Code CLI
|
|
2
|
+
|
|
3
|
+
## GOAL
|
|
4
|
+
Replicate, on Windows, the EXACT tool surface and behavior of Claude Desktop's "computer-use" MCP, so the Claude Code CLI (CCC) can drive the desktop (screenshot + mouse/keyboard/scroll/drag/batch + clipboard + app launch + multi-monitor). Must be a faithful 1:1 replica of the tool names, params and semantics.
|
|
5
|
+
|
|
6
|
+
## TARGET MACHINE (measured ground truth — code MUST stay general, but tune defaults to these)
|
|
7
|
+
- Physical primary monitor: 2560x1600. Windows DPI scaling: 150% (logical 1707x1067).
|
|
8
|
+
- Claude Desktop downscales its screenshot to ~1388x868 (~1.20 megapixels), aspect-ratio preserved, captured from the PHYSICAL framebuffer.
|
|
9
|
+
- Click coordinates are in IMAGE-pixel space of the most recent screenshot; the server scales them back to physical pixels. Measured per-axis scale ~1.84 (= 2560/1388).
|
|
10
|
+
- Monitors: originally developed single-monitor; the dev rig now also has a secondary. Measured dual layout: primary = \\.\DISPLAY2 2560x1600 @(0,0) (laptop panel), secondary = \\.\DISPLAY1 2560x1440 @(2560,157) (to the RIGHT, offset down). NOTE the numbering/identity mismatch (DISPLAY1 is NOT the primary) — never assume; code must key off Monitor.primary and real geometry.
|
|
11
|
+
|
|
12
|
+
## TECH STACK (use exactly this)
|
|
13
|
+
- Python (>=3.11), packaged with uv. src layout. Package name: computer_use_omni. Package name (PyPI): computer-use-omni
|
|
14
|
+
- MCP framework: official 'mcp' Python SDK, FastMCP API (from mcp.server.fastmcp import FastMCP). Run over stdio.
|
|
15
|
+
- Screen capture: mss. Image: pillow. Window/clipboard/foreground: pywin32 (win32gui/win32process/win32api/win32clipboard) + ctypes for SendInput/DPI.
|
|
16
|
+
- Input injection: raw Win32 SendInput via ctypes (NOT pyautogui) for reliable absolute coords + Unicode typing.
|
|
17
|
+
- dependencies in pyproject: mcp, mss, pillow, pywin32
|
|
18
|
+
- Entry: python -m computer_use_omni (so __main__.py defines main()). Also a console_script 'computer-use-omni'.
|
|
19
|
+
- The MCP will be launched by CCC like the user's existing servers: uvx computer-use-omni
|
|
20
|
+
|
|
21
|
+
## FILE MANIFEST (create ALL of these)
|
|
22
|
+
pyproject.toml
|
|
23
|
+
README.md
|
|
24
|
+
SPEC.md (copy of this spec, authoritative contract)
|
|
25
|
+
src/computer_use_omni/__init__.py
|
|
26
|
+
src/computer_use_omni/__main__.py (main(): set DPI awareness, run FastMCP stdio)
|
|
27
|
+
src/computer_use_omni/config.py (env-driven config: MAX_PIXELS default 1_200_000; MASKING default 'on'; ENFORCE_FOREGROUND default 'off'; CCC_AUTOGRANT default 'on')
|
|
28
|
+
src/computer_use_omni/screen.py (DPI, monitors, capture, downscale, coordinate scaling, masking apply, multi-monitor notes/hints, display_overview compositing)
|
|
29
|
+
src/computer_use_omni/inputs.py (SendInput mouse: move/click/drag/down/up/scroll, get_cursor_pos) — works in PHYSICAL/virtual-desktop coords
|
|
30
|
+
src/computer_use_omni/keymap.py (key-name->VK map, chord parsing, aliases)
|
|
31
|
+
src/computer_use_omni/keyboard.py (press_chord/hold/type_text/press_keys/release_keys via SendInput) — imports keymap
|
|
32
|
+
src/computer_use_omni/apps.py (resolve_app, launch_and_focus, foreground_process, enumerate_windows, mask_rects_for)
|
|
33
|
+
src/computer_use_omni/permissions.py(Allowlist state, request_access logic, list_granted, is_allowed)
|
|
34
|
+
src/computer_use_omni/clipboard.py (read_text/write_text)
|
|
35
|
+
src/computer_use_omni/server.py (FastMCP app, register all 29 tools + dev reload, dispatch) — written in Integrate phase
|
|
36
|
+
src/computer_use_omni/batch.py (computer_batch dispatcher + teach stubs) — written in Integrate phase
|
|
37
|
+
src/computer_use_omni/overlay.py (CDC-style orange edge glow + centered pill; layered topmost click-through windows, WDA-excluded from captures)
|
|
38
|
+
src/computer_use_omni/terminal.py (find/shrink the controlling window — Windows Terminal (CCC) OR the Claude Desktop window (CDC, Electron Chrome_WidgetWin_1/claude.exe, resolved by ancestry); off-screen-hide on capture; kind-aware click-through: z-order drop for the non-layered terminal, WS_EX_TRANSPARENT for the layered Claude window; crash-safety state + startup self-heal)
|
|
39
|
+
tests/mcp_client.py (smoke client) — written in Boot test phase
|
|
40
|
+
|
|
41
|
+
## MODULE PUBLIC CONTRACT (exact signatures; stubs raise NotImplementedError)
|
|
42
|
+
|
|
43
|
+
### config.py
|
|
44
|
+
MAX_PIXELS: int # default int(os.environ.get('COMPUTER_USE_MAX_PIXELS', 1_200_000))
|
|
45
|
+
MASKING: bool # default os.environ.get('COMPUTER_USE_MASKING','on') != 'off'
|
|
46
|
+
ENFORCE_FOREGROUND: bool # default os.environ.get('COMPUTER_USE_ENFORCE_FOREGROUND','off') == 'on'
|
|
47
|
+
AUTOGRANT: bool # default os.environ.get('COMPUTER_USE_AUTOGRANT','on') != 'off'
|
|
48
|
+
|
|
49
|
+
### screen.py
|
|
50
|
+
def set_dpi_awareness() -> None # SetProcessDpiAwarenessContext(-4) Per-Monitor-V2, fallback shcore/user32
|
|
51
|
+
@dataclass class Monitor: name:str; index:int; x:int; y:int; width:int; height:int; primary:bool # x,y,width,height in PHYSICAL px
|
|
52
|
+
@dataclass class ScaleInfo: image_w:int; image_h:int; phys_w:int; phys_h:int; origin_x:int; origin_y:int; scale_x:float; scale_y:float
|
|
53
|
+
def list_monitors() -> list[Monitor]
|
|
54
|
+
def select_monitor(name: str | None) -> None # 'auto'/None => automatic; else match Monitor.name
|
|
55
|
+
def current_monitor() -> Monitor
|
|
56
|
+
def capture(mask_rects: list[tuple[int,int,int,int]] | None = None) -> tuple["PIL.Image.Image", ScaleInfo]
|
|
57
|
+
# capture active monitor PHYSICAL pixels via mss; if mask_rects given (PHYSICAL coords x0,y0,x1,y1) fill them solid BEFORE downscale; downscale preserving AR so image_w*image_h <= MAX_PIXELS, never upscale, LANCZOS; return (image, scaleinfo). Also store as last scale.
|
|
58
|
+
def last_scale() -> ScaleInfo | None
|
|
59
|
+
def image_to_physical(x: float, y: float, scale: ScaleInfo | None = None) -> tuple[int,int] # uses last_scale if None: origin + round(x*scale_x), origin + round(y*scale_y)
|
|
60
|
+
def physical_to_image(px: int, py: int, scale: ScaleInfo | None = None) -> tuple[int,int] # inverse, for cursor_position
|
|
61
|
+
def last_capture_monitor() -> Monitor | None # monitor used by the most recent capture (feeds the screenshot note)
|
|
62
|
+
def describe_monitor(mon: Monitor) -> str # '"\\.\DISPLAY2" (2560x1600, primary)'
|
|
63
|
+
def monitor_for_hwnd(hwnd: int) -> Monitor | None # MonitorFromWindow + GetMonitorInfoW rect matched against list_monitors
|
|
64
|
+
def cross_monitor_hint(hwnd: int) -> str # multi-monitor launch hint: precise (names the window's monitor, empty when same/auto-followed) with an hwnd; generic warning without one; '' on single-monitor
|
|
65
|
+
def capture_overview(mask_rects=None) -> tuple["PIL.Image.Image", str] # all monitors composited per virtual-desktop layout (uniform scale, labeled panels, <= MAX_PIXELS); does NOT touch last_scale; returns (image, not-clickable note)
|
|
66
|
+
|
|
67
|
+
### inputs.py (PHYSICAL/virtual-desktop coordinates; uses SendInput absolute over virtual screen normalized 0..65535 with MOUSEEVENTF_VIRTUALDESK|ABSOLUTE)
|
|
68
|
+
def move_to(px:int, py:int) -> None
|
|
69
|
+
def click(px:int, py:int, button:str='left', count:int=1, modifiers:list[str]|None=None) -> None # modifiers pressed via keyboard.press_keys/release_keys around the click
|
|
70
|
+
def mouse_down(px:int|None=None, py:int|None=None, button:str='left') -> None
|
|
71
|
+
def mouse_up(px:int|None=None, py:int|None=None, button:str='left') -> None
|
|
72
|
+
def drag(px0:int, py0:int, px1:int, py1:int, button:str='left') -> None
|
|
73
|
+
def scroll(px:int, py:int, direction:str, amount:int) -> None # direction in up/down/left/right; amount = wheel ticks; move cursor there first then wheel
|
|
74
|
+
def get_cursor_pos() -> tuple[int,int] # physical via GetCursorPos (process is DPI aware)
|
|
75
|
+
|
|
76
|
+
### keymap.py
|
|
77
|
+
KEY_MAP: dict[str,int] # names -> Windows VK codes. Support xdotool-ish + common aliases (case-insensitive): return/enter->VK_RETURN, esc/escape, tab, space, backspace/bksp, delete/del, home,end,pageup/pgup,pagedown/pgdn, up/down/left/right, f1..f24, plus letters a-z digits 0-9, punctuation. Modifiers: ctrl/control, shift, alt/option, win/cmd/super/meta -> VK_LWIN.
|
|
78
|
+
MODIFIER_NAMES: set[str]
|
|
79
|
+
def normalize_key(name:str) -> str
|
|
80
|
+
def to_vk(name:str) -> int
|
|
81
|
+
def parse_chord(text:str) -> list[int] # 'ctrl+shift+a' -> [VK_CONTROL, VK_SHIFT, ord('A')]; split on '+'; last is main key, earlier are modifiers; return VKs in press order
|
|
82
|
+
|
|
83
|
+
### keyboard.py (imports keymap)
|
|
84
|
+
def press_keys(vks: list[int]) -> None # key-down each (modifiers), via SendInput keybd
|
|
85
|
+
def release_keys(vks: list[int]) -> None # key-up in reverse
|
|
86
|
+
def press_chord(text:str, repeat:int=1) -> None # parse_chord; hold modifiers, tap main key repeat times, release
|
|
87
|
+
def hold(text:str, duration:float) -> None # press chord down, sleep duration, release
|
|
88
|
+
def type_text(text:str) -> None # Unicode via SendInput KEYEVENTF_UNICODE (handle surrogate pairs); '\n' -> Return
|
|
89
|
+
|
|
90
|
+
### apps.py
|
|
91
|
+
@dataclass class AppInfo: display:str; exe:str; launch:str; bundle_id:str # exe = expected foreground process basename lowercased (best-effort); launch = command/target; bundle_id = synthetic id
|
|
92
|
+
@dataclass class WinInfo: hwnd:int; rect:tuple[int,int,int,int]; exe:str; visible:bool # rect PHYSICAL px
|
|
93
|
+
def resolve_app(name:str) -> AppInfo | None # search Start Menu .lnk under ProgramData + AppData, and running processes; case-insensitive contains/best match; for UWP allow shell:AppsFolder
|
|
94
|
+
@dataclass class LaunchResult: hwnd:int; pid:int; exited:bool; kind:str # kind='popen'|'shell-file'|'shell-bare'; exited=True only when a spawned pid died with no window (crash-on-startup)
|
|
95
|
+
def launch_and_focus(app: AppInfo) -> LaunchResult # classic on-disk .exe -> subprocess.Popen with cwd=exe dir + DETACHED + DEVNULL stdio (cwd fixes Tauri/WebView2 relative-resource crashes; DEVNULL stops a console child corrupting JSON-RPC stdout); .lnk/moniker/UWP -> shell (os.startfile/ShellExecute, no pid). Poll ≤~2s for a focusable window (by our pid, else by exe) then SetForegroundWindow + AllowSetForegroundWindow + ShowWindow restore. Returns what actually happened so the caller can report honestly.
|
|
96
|
+
def foreground_process() -> str # basename(lower) of GetForegroundWindow's process exe; '' if unknown
|
|
97
|
+
def enumerate_windows() -> list[WinInfo] # EnumWindows visible top-level, with PHYSICAL rect via GetWindowRect (DPI aware)
|
|
98
|
+
def mask_rects_for(allowed_exes: set[str]) -> list[tuple[int,int,int,int]] # rects (physical) of visible top-level windows whose exe basename not in allowed_exes; skip desktop/shell
|
|
99
|
+
|
|
100
|
+
### permissions.py
|
|
101
|
+
@dataclass class Granted: display:str; bundle_id:str; exe:str; granted_at:int; tier:str
|
|
102
|
+
class Allowlist:
|
|
103
|
+
apps: list[Granted]
|
|
104
|
+
clipboard_read: bool; clipboard_write: bool; system_key_combos: bool
|
|
105
|
+
def request_access(self, apps:list[str], reason:str, clipboardRead=False, clipboardWrite=False, systemKeyCombos=False) -> dict
|
|
106
|
+
# CCC AUTOGRANT mode: for each name call apps.resolve_app; resolved -> add Granted(tier='full', granted_at=epoch_ms); unresolved -> notInstalled. Merge OR the clipboard/system flags. Return CDC-SHAPED dict: {granted:[{bundleId,displayName,grantedAt,tier}], denied:[], notInstalled?:{apps:[{requestedName,didYouMean:[]}],guidance:str}, screenshotFiltering:'mask'}
|
|
107
|
+
def list_granted(self) -> dict # {allowedApps:[{bundleId,displayName,grantedAt,tier}], grantFlags:{clipboardRead,clipboardWrite,systemKeyCombos}}
|
|
108
|
+
def allowed_exes(self) -> set[str]
|
|
109
|
+
def is_allowed(self, exe_basename:str) -> bool
|
|
110
|
+
def is_empty(self) -> bool
|
|
111
|
+
ALLOWLIST = Allowlist() # module-level singleton
|
|
112
|
+
def epoch_ms() -> int # time.time()*1000 int
|
|
113
|
+
|
|
114
|
+
### clipboard.py
|
|
115
|
+
def read_text() -> str # win32clipboard
|
|
116
|
+
def write_text(s:str) -> None
|
|
117
|
+
|
|
118
|
+
## THE 29 TOOLS (server.py registers these; tools 1-27 match Claude Desktop, tools 28-29 are omni-specific; + a dev-only `reload`)
|
|
119
|
+
Meta/permission:
|
|
120
|
+
1 request_access(apps:list[str], reason:str, clipboardRead?:bool, clipboardWrite?:bool, systemKeyCombos?:bool) -> ALLOWLIST.request_access(...)
|
|
121
|
+
2 list_granted_applications() -> ALLOWLIST.list_granted()
|
|
122
|
+
3 request_teach_access(apps:list[str], reason:str) -> STUB: return {'granted':[...auto...], 'note':'teach mode overlay not implemented in CLI build (phase 2)'}
|
|
123
|
+
Capture:
|
|
124
|
+
4 screenshot(save_to_disk?:bool) -> error if ALLOWLIST.is_empty(); build mask_rects via apps.mask_rects_for(ALLOWLIST.allowed_exes()) when config.MASKING; screen.capture(mask_rects) -> return MCP image (PNG). If save_to_disk, also write PNG to a temp path and include the path in a text note. MULTI-MONITOR: prepend an event-driven note naming the captured monitor + the other attached monitors (fires on the first multi-monitor capture and whenever the captured monitor changes; silent on same-monitor repeats and single-monitor rigs — official-computer-use parity). Return list of content [note?, text?, image].
|
|
125
|
+
5 zoom(region:[x0,y0,x1,y1], save_to_disk?:bool) -> crop last screenshot region in IMAGE space, upscale 2x for detail, return image. Read-only.
|
|
126
|
+
6 switch_display(display:str) -> screen.select_monitor(display); return text confirming + monitor list. 'auto' = follow the foreground window's monitor (primary fallback); otherwise a device name as listed in screenshot notes (e.g. '\\.\DISPLAY2').
|
|
127
|
+
7 cursor_position() -> physical = inputs.get_cursor_pos(); return screen.physical_to_image(px,py) as {x,y} text.
|
|
128
|
+
Mouse (coordinate is IMAGE space; map via screen.image_to_physical):
|
|
129
|
+
8 mouse_move(coordinate)
|
|
130
|
+
9 left_click(coordinate, text?) text=modifiers
|
|
131
|
+
10 right_click(coordinate, text?)
|
|
132
|
+
11 middle_click(coordinate, text?)
|
|
133
|
+
12 double_click(coordinate, text?) count=2
|
|
134
|
+
13 triple_click(coordinate, text?) count=3
|
|
135
|
+
14 left_click_drag(coordinate, start_coordinate?) # start optional => from current cursor
|
|
136
|
+
15 left_mouse_down()
|
|
137
|
+
16 left_mouse_up()
|
|
138
|
+
17 scroll(coordinate, scroll_direction, scroll_amount)
|
|
139
|
+
Keyboard:
|
|
140
|
+
18 key(text, repeat?)
|
|
141
|
+
19 hold_key(text, duration)
|
|
142
|
+
20 type(text)
|
|
143
|
+
Misc:
|
|
144
|
+
21 wait(duration)
|
|
145
|
+
22 open_application(app) -> resolve_app; if None error 'not in allowlist / not found'; require it be in allowlist (in CCC autogrant, resolving also implies allowed if previously granted; if not granted, return guidance to call request_access). launch_and_focus, then report honestly on the LaunchResult: exited -> raise 'crashed on startup'; hwnd -> 'Opened "X"'; pid but no window -> 'running, no window yet (wait+screenshot)'; kind=='shell-bare' with nothing -> raise 'could not confirm... pass the full .exe path'; else (UWP/moniker, unverifiable) -> 'Opened "X"'. MULTI-MONITOR: success paths append screen.cross_monitor_hint — with an hwnd, a PRECISE note only when the window's monitor differs from the capture monitor (names the target monitor; silent otherwise, incl. under 'auto' which follows the fresh focus); without an hwnd (UWP/shell), a generic 'may have opened on a different monitor' warning (official parity).
|
|
146
|
+
23 read_clipboard() -> require grantFlags.clipboardRead else error; clipboard.read_text()
|
|
147
|
+
24 write_clipboard(text) -> require clipboard_write else error; clipboard.write_text()
|
|
148
|
+
Batch:
|
|
149
|
+
25 computer_batch(actions:list) -> batch.run_batch(actions): execute sequentially, stop on first error, return per-action outputs; screenshot/zoom images interleaved. Coordinates always refer to the full-screen screenshot taken BEFORE the batch.
|
|
150
|
+
26 teach_step(explanation, next_preview, actions, anchor?) -> STUB returning {'exited':false,'note':'teach overlay not implemented (phase 2)'} but DO execute the actions (so it degrades to computer_batch-like behavior) then return a screenshot.
|
|
151
|
+
27 teach_batch(steps) -> STUB: iterate steps executing actions, return final screenshot + note.
|
|
152
|
+
omni-specific (not in CDC):
|
|
153
|
+
28 deactivate() -> end the computer-use session WITHOUT killing the process: overlay off + controlling window (terminal or Claude Desktop window) restored to its original size/state (incl. re-maximize if it was maximized) + ALLOWLIST.clear() + mark session inactive (a later request_access re-activates). Counterpart to request_access; mirrors how CDC auto-restores its window when a task ends.
|
|
154
|
+
29 display_overview(save_to_disk?:bool) -> batch.do_overview: ONE composite image of ALL monitors laid out per the virtual-desktop arrangement (each panel grabbed full-res, masked, downscaled with a single uniform factor within MAX_PIXELS, pasted at its scaled virtual position, labeled name+resolution+PRIMARY). Same allowlist gate / masking / controlling-window hide as screenshot, but does NOT touch last_scale — coordinates in the overview are NOT clickable (the note says so and points at switch_display + screenshot). Orientation aid the official computer-use lacks ("which screen is that window on?" in one call).
|
|
155
|
+
|
|
156
|
+
## TOOL GATING (foreground check)
|
|
157
|
+
Before each INPUT action (mouse/keyboard/scroll/drag/type/key/hold) and inside computer_batch per action:
|
|
158
|
+
- if ALLOWLIST.is_empty(): error like Claude Desktop ("allowlist empty, call request_access").
|
|
159
|
+
- compute fg = apps.foreground_process(). If config.ENFORCE_FOREGROUND and fg and not ALLOWLIST.is_allowed(fg): return error "frontmost app '<fg>' is not in the session allowlist; call request_access". If ENFORCE_FOREGROUND is off (DEFAULT for CCC), do NOT block — just proceed (optionally include a note). This is decision #1: pragmatic, permissive by default, structure preserved.
|
|
160
|
+
- KEYBOARD SELF-HARM GUARD (unconditional — independent of ENFORCE_FOREGROUND/DEV, added after a near-miss): before key/hold_key/type, if terminal.foreground_is_controlling() (the foreground window IS the controlling window, or — CDC only — shares its owner process / is a Claude main window), raise "keyboard input blocked ... Click the target application first". Synthetic keystrokes go to whatever holds OS focus; without this guard they land in Claude's own control surface (conversation pollution; a trailing Return sends a message or runs a shell command). Mouse actions stay exempt (a click carries its own coordinate and takes focus). Terminal hosts match by EXACT hwnd only (a second Windows Terminal window of the same process is a legitimate target, not the control surface).
|
|
161
|
+
screenshot is allowed whenever allowlist non-empty.
|
|
162
|
+
|
|
163
|
+
## COORDINATE INVARIANTS
|
|
164
|
+
- All click/move/scroll/drag/zoom coordinates from tool inputs are IMAGE-space of the most recent screenshot. Convert with screen.image_to_physical using screen.last_scale().
|
|
165
|
+
- If no screenshot taken yet, capture one first to establish scale (so first click still works), OR map using a fresh capture's scale.
|
|
166
|
+
- inputs.* receive PHYSICAL coords only.
|
|
167
|
+
|
|
168
|
+
## KEY/CHORD RULES
|
|
169
|
+
- Accept '+'-joined chords. Names case-insensitive. 'cmd'/'super'/'win'/'meta' -> Windows key. Primary modifier on Windows is ctrl. Examples that must work: 'Return','Escape','ctrl+a','ctrl+shift+tab','alt+F4','win+d', single chars, 'Page_Down'/'pagedown'.
|
|
170
|
+
- type() uses KEYEVENTF_UNICODE so any Unicode (incl Chinese) types correctly regardless of keyboard layout. If config has clipboard_write granted, a clipboard fast-path for long multi-line text is allowed but optional.
|
|
171
|
+
|
|
172
|
+
## RETURN/ERROR STYLE
|
|
173
|
+
- Match Claude Desktop tone: short confirmations ('Moved.', 'Opened "X".'), structured dicts for request_access/list_granted, images for screenshot/zoom. Errors are returned as tool errors (raise) with helpful guidance text.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "computer-use-omni"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Windows computer-use MCP for the Claude Code CLI — a 1:1 replica of Claude Desktop's computer-use tool surface."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Jason26214" }]
|
|
13
|
+
keywords = ["mcp", "computer-use", "windows", "automation", "claude", "desktop"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: Microsoft :: Windows",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
"Topic :: Desktop Environment",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"mcp",
|
|
28
|
+
"mss",
|
|
29
|
+
"pillow",
|
|
30
|
+
"pywin32",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/Jason26214/computer-use-omni"
|
|
35
|
+
Repository = "https://github.com/Jason26214/computer-use-omni"
|
|
36
|
+
Issues = "https://github.com/Jason26214/computer-use-omni/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
computer-use-omni = "computer_use_omni.__main__:main"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/computer_use_omni"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.Jason26214/computer-use-omni",
|
|
4
|
+
"description": "Windows computer-use MCP for the Claude Code CLI — a 1:1 replica of Claude Desktop's computer-use tool surface.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/Jason26214/computer-use-omni",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "pypi",
|
|
13
|
+
"identifier": "computer-use-omni",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"description": "Max pixels in a downscaled screenshot (default 1200000, ~1.2 MP).",
|
|
21
|
+
"isRequired": false,
|
|
22
|
+
"format": "string",
|
|
23
|
+
"name": "COMPUTER_USE_MAX_PIXELS"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "Mask non-allowlisted app windows in screenshots ('on'/'off', default off).",
|
|
27
|
+
"isRequired": false,
|
|
28
|
+
"format": "string",
|
|
29
|
+
"name": "COMPUTER_USE_MASKING"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "Block input when the frontmost app is not allowlisted ('on'/'off', default off).",
|
|
33
|
+
"isRequired": false,
|
|
34
|
+
"format": "string",
|
|
35
|
+
"name": "COMPUTER_USE_ENFORCE_FOREGROUND"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Register the reload hot-reload developer tool ('on'/'off', default on).",
|
|
39
|
+
"isRequired": false,
|
|
40
|
+
"format": "string",
|
|
41
|
+
"name": "COMPUTER_USE_DEV"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|