computer-control-mcp 0.2.7__tar.gz → 0.2.9__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_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/LICENSE +1 -1
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/PKG-INFO +32 -24
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/README.md +20 -12
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/core.py +467 -456
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/pyproject.toml +13 -13
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/.gitignore +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/FZYTK.TTF +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/__init__.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/__main__.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/cli.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/gui.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/server.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/test.py +0 -0
- {computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/test_image.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 AB498 <abcd49800@gmail.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: computer-control-mcp
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.2.9
|
|
4
|
+
Summary: MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies.
|
|
5
5
|
Project-URL: Homepage, https://github.com/AB498/computer-control-mcp
|
|
6
6
|
Project-URL: Issues, https://github.com/AB498/computer-control-mcp/issues
|
|
7
7
|
Project-URL: Documentation, https://github.com/AB498/computer-control-mcp#readme
|
|
8
|
-
Author-email:
|
|
8
|
+
Author-email: AB498 <abcd49800@gmail.com>
|
|
9
9
|
License: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -16,29 +16,37 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries
|
|
17
17
|
Classifier: Topic :: Utilities
|
|
18
18
|
Requires-Python: >=3.12
|
|
19
|
-
Requires-Dist: fuzzywuzzy
|
|
20
|
-
Requires-Dist: mcp[cli]
|
|
21
|
-
Requires-Dist: onnxruntime
|
|
22
|
-
Requires-Dist: opencv-python
|
|
23
|
-
Requires-Dist: pillow
|
|
24
|
-
Requires-Dist: pyautogui
|
|
25
|
-
Requires-Dist: pygetwindow
|
|
26
|
-
Requires-Dist: rapidocr
|
|
27
|
-
Requires-Dist: rapidocr
|
|
19
|
+
Requires-Dist: fuzzywuzzy==0.18.0
|
|
20
|
+
Requires-Dist: mcp[cli]==1.13.0
|
|
21
|
+
Requires-Dist: onnxruntime==1.22.1
|
|
22
|
+
Requires-Dist: opencv-python==4.12.0.88
|
|
23
|
+
Requires-Dist: pillow==11.3.0
|
|
24
|
+
Requires-Dist: pyautogui==0.9.54
|
|
25
|
+
Requires-Dist: pygetwindow==0.0.9
|
|
26
|
+
Requires-Dist: rapidocr-onnxruntime==1.4.4
|
|
27
|
+
Requires-Dist: rapidocr==3.3.1
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# Computer Control MCP
|
|
31
31
|
|
|
32
|
-
MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. With Zero External Dependencies.
|
|
32
|
+
### MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies.
|
|
33
33
|
|
|
34
|
-
*
|
|
34
|
+
* *Only tested on Windows. Should work on other platforms.*
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
<div style="text-align:center;font-family: monospace; display: flex; align-items: center; justify-content: center; width: 100%; gap: 10px">
|
|
37
|
+
<a href="https://discord.gg/ZeeqSBpjU2"><img src="https://img.shields.io/discord/1095854826786668545" alt="Discord"></a>
|
|
38
|
+
<a href="https://img.shields.io/badge/License-MIT-yellow.svg"><img
|
|
39
|
+
src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
40
|
+
</div>
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+

|
|
39
45
|
|
|
40
46
|
## Quick Usage (MCP Setup Using `uvx`)
|
|
41
47
|
|
|
48
|
+
***Note:** Running `uvx computer-control-mcp@latest` for the first time will download python dependencies (around 70MB) which may take some time. Recommended to run this in a terminal before using it as MCP. Subsequent runs will be instant.*
|
|
49
|
+
|
|
42
50
|
```json
|
|
43
51
|
{
|
|
44
52
|
"mcpServers": {
|
|
@@ -86,14 +94,6 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
|
|
|
86
94
|
- `list_windows()`: List all open windows
|
|
87
95
|
- `activate_window(title_pattern: str, use_regex: bool = False, threshold: int = 60)`: Bring specified window to foreground
|
|
88
96
|
|
|
89
|
-
### Running as a Module
|
|
90
|
-
|
|
91
|
-
You can run the package as a module:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
python -m computer_control_mcp
|
|
95
|
-
```
|
|
96
|
-
|
|
97
97
|
## Development
|
|
98
98
|
|
|
99
99
|
### Setting up the Development Environment
|
|
@@ -105,6 +105,9 @@ cd computer-control-mcp
|
|
|
105
105
|
|
|
106
106
|
# Install in development mode
|
|
107
107
|
pip install -e .
|
|
108
|
+
|
|
109
|
+
# Start server
|
|
110
|
+
python -m computer_control_mcp.core
|
|
108
111
|
```
|
|
109
112
|
|
|
110
113
|
### Running Tests
|
|
@@ -120,3 +123,8 @@ See the [API Reference](docs/api.md) for detailed information about the availabl
|
|
|
120
123
|
## License
|
|
121
124
|
|
|
122
125
|
MIT
|
|
126
|
+
|
|
127
|
+
## For more information or help
|
|
128
|
+
|
|
129
|
+
- [Email (abcd49800@gmail.com)](mailto:abcd49800@gmail.com)
|
|
130
|
+
- [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
# Computer Control MCP
|
|
2
2
|
|
|
3
|
-
MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. With Zero External Dependencies.
|
|
3
|
+
### MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies.
|
|
4
4
|
|
|
5
|
-
*
|
|
5
|
+
* *Only tested on Windows. Should work on other platforms.*
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<div style="text-align:center;font-family: monospace; display: flex; align-items: center; justify-content: center; width: 100%; gap: 10px">
|
|
8
|
+
<a href="https://discord.gg/ZeeqSBpjU2"><img src="https://img.shields.io/discord/1095854826786668545" alt="Discord"></a>
|
|
9
|
+
<a href="https://img.shields.io/badge/License-MIT-yellow.svg"><img
|
|
10
|
+
src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
11
|
+
</div>
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+

|
|
10
16
|
|
|
11
17
|
## Quick Usage (MCP Setup Using `uvx`)
|
|
12
18
|
|
|
19
|
+
***Note:** Running `uvx computer-control-mcp@latest` for the first time will download python dependencies (around 70MB) which may take some time. Recommended to run this in a terminal before using it as MCP. Subsequent runs will be instant.*
|
|
20
|
+
|
|
13
21
|
```json
|
|
14
22
|
{
|
|
15
23
|
"mcpServers": {
|
|
@@ -57,14 +65,6 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
|
|
|
57
65
|
- `list_windows()`: List all open windows
|
|
58
66
|
- `activate_window(title_pattern: str, use_regex: bool = False, threshold: int = 60)`: Bring specified window to foreground
|
|
59
67
|
|
|
60
|
-
### Running as a Module
|
|
61
|
-
|
|
62
|
-
You can run the package as a module:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
python -m computer_control_mcp
|
|
66
|
-
```
|
|
67
|
-
|
|
68
68
|
## Development
|
|
69
69
|
|
|
70
70
|
### Setting up the Development Environment
|
|
@@ -76,6 +76,9 @@ cd computer-control-mcp
|
|
|
76
76
|
|
|
77
77
|
# Install in development mode
|
|
78
78
|
pip install -e .
|
|
79
|
+
|
|
80
|
+
# Start server
|
|
81
|
+
python -m computer_control_mcp.core
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
### Running Tests
|
|
@@ -91,3 +94,8 @@ See the [API Reference](docs/api.md) for detailed information about the availabl
|
|
|
91
94
|
## License
|
|
92
95
|
|
|
93
96
|
MIT
|
|
97
|
+
|
|
98
|
+
## For more information or help
|
|
99
|
+
|
|
100
|
+
- [Email (abcd49800@gmail.com)](mailto:abcd49800@gmail.com)
|
|
101
|
+
- [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
|
|
@@ -1,456 +1,467 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Computer Control MCP - Core Implementation
|
|
4
|
-
A compact ModelContextProtocol server that provides computer control capabilities
|
|
5
|
-
using PyAutoGUI for mouse/keyboard control.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import cv2
|
|
28
|
-
from
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return None
|
|
125
|
-
|
|
126
|
-
# For
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
"
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
window =
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
screenshot
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
window
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
pyautogui.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
for
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
#
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Computer Control MCP - Core Implementation
|
|
4
|
+
A compact ModelContextProtocol server that provides computer control capabilities
|
|
5
|
+
using PyAutoGUI for mouse/keyboard control.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
13
|
+
from io import BytesIO
|
|
14
|
+
import re
|
|
15
|
+
import asyncio
|
|
16
|
+
import uuid
|
|
17
|
+
import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import tempfile
|
|
20
|
+
|
|
21
|
+
# --- Auto-install dependencies if needed ---
|
|
22
|
+
import pyautogui
|
|
23
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
24
|
+
import pygetwindow as gw
|
|
25
|
+
from fuzzywuzzy import fuzz, process
|
|
26
|
+
|
|
27
|
+
import cv2
|
|
28
|
+
from rapidocr import RapidOCR
|
|
29
|
+
from rapidocr_onnxruntime import VisRes
|
|
30
|
+
|
|
31
|
+
from pydantic import BaseModel
|
|
32
|
+
|
|
33
|
+
BaseModel.model_config = {'arbitrary_types_allowed': True}
|
|
34
|
+
|
|
35
|
+
engine = RapidOCR()
|
|
36
|
+
vis = VisRes()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
DEBUG = True # Set to False in production
|
|
40
|
+
RELOAD_ENABLED = True # Set to False to disable auto-reload
|
|
41
|
+
|
|
42
|
+
# Create FastMCP server instance at module level
|
|
43
|
+
mcp = FastMCP("ComputerControlMCP")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def log(message: str) -> None:
|
|
47
|
+
"""Log a message to stderr."""
|
|
48
|
+
print(f"STDOUT: {message}", file=sys.stderr)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_downloads_dir() -> Path:
|
|
52
|
+
"""Get the OS downloads directory."""
|
|
53
|
+
if os.name == "nt": # Windows
|
|
54
|
+
import winreg
|
|
55
|
+
|
|
56
|
+
sub_key = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
|
|
57
|
+
downloads_guid = "{374DE290-123F-4565-9164-39C4925E467B}"
|
|
58
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
|
|
59
|
+
downloads_dir = winreg.QueryValueEx(key, downloads_guid)[0]
|
|
60
|
+
return Path(downloads_dir)
|
|
61
|
+
else: # macOS, Linux, etc.
|
|
62
|
+
return Path.home() / "Downloads"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def save_image_to_downloads(
|
|
66
|
+
image, prefix: str = "screenshot", directory: Path = None
|
|
67
|
+
) -> Tuple[str, bytes]:
|
|
68
|
+
"""Save an image to the downloads directory and return its absolute path.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
image: Either a PIL Image object or MCP Image object
|
|
72
|
+
prefix: Prefix for the filename (default: 'screenshot')
|
|
73
|
+
directory: Optional directory to save the image to
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Tuple of (absolute_path, image_data_bytes)
|
|
77
|
+
"""
|
|
78
|
+
# Create a unique filename with timestamp
|
|
79
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
80
|
+
unique_id = str(uuid.uuid4())[:8]
|
|
81
|
+
filename = f"{prefix}_{timestamp}_{unique_id}.png"
|
|
82
|
+
|
|
83
|
+
# Get downloads directory
|
|
84
|
+
downloads_dir = directory or get_downloads_dir()
|
|
85
|
+
filepath = downloads_dir / filename
|
|
86
|
+
|
|
87
|
+
# Handle different image types
|
|
88
|
+
if hasattr(image, "save"): # PIL Image
|
|
89
|
+
image.save(filepath)
|
|
90
|
+
# Also get the bytes for returning
|
|
91
|
+
img_byte_arr = BytesIO()
|
|
92
|
+
image.save(img_byte_arr, format="PNG")
|
|
93
|
+
img_bytes = img_byte_arr.getvalue()
|
|
94
|
+
elif hasattr(image, "data"): # MCP Image
|
|
95
|
+
img_bytes = image.data
|
|
96
|
+
with open(filepath, "wb") as f:
|
|
97
|
+
f.write(img_bytes)
|
|
98
|
+
else:
|
|
99
|
+
raise TypeError("Unsupported image type")
|
|
100
|
+
|
|
101
|
+
log(f"Saved image to {filepath}")
|
|
102
|
+
return str(filepath.absolute()), img_bytes
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _find_matching_window(
|
|
106
|
+
windows: any,
|
|
107
|
+
title_pattern: str = None,
|
|
108
|
+
use_regex: bool = False,
|
|
109
|
+
threshold: int = 60,
|
|
110
|
+
) -> Optional[Dict[str, Any]]:
|
|
111
|
+
"""Helper function to find a matching window based on title pattern.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
windows: List of window dictionaries
|
|
115
|
+
title_pattern: Pattern to match window title
|
|
116
|
+
use_regex: If True, treat the pattern as a regex, otherwise use fuzzy matching
|
|
117
|
+
threshold: Minimum score (0-100) required for a fuzzy match
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The best matching window or None if no match found
|
|
121
|
+
"""
|
|
122
|
+
if not title_pattern:
|
|
123
|
+
log("No title pattern provided, returning None")
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
# For regex matching
|
|
127
|
+
if use_regex:
|
|
128
|
+
for window in windows:
|
|
129
|
+
if re.search(title_pattern, window["title"], re.IGNORECASE):
|
|
130
|
+
log(f"Regex match found: {window['title']}")
|
|
131
|
+
return window
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
# For fuzzy matching using fuzzywuzzy
|
|
135
|
+
# Extract all window titles
|
|
136
|
+
window_titles = [window["title"] for window in windows]
|
|
137
|
+
|
|
138
|
+
# Use process.extractOne to find the best match
|
|
139
|
+
best_match_title, score = process.extractOne(
|
|
140
|
+
title_pattern, window_titles, scorer=fuzz.partial_ratio
|
|
141
|
+
)
|
|
142
|
+
log(f"Best fuzzy match: '{best_match_title}' with score {score}")
|
|
143
|
+
|
|
144
|
+
# Only return if the score is above the threshold
|
|
145
|
+
if score >= threshold:
|
|
146
|
+
# Find the window with the matching title
|
|
147
|
+
for window in windows:
|
|
148
|
+
if window["title"] == best_match_title:
|
|
149
|
+
return window
|
|
150
|
+
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# --- MCP Function Handlers ---
|
|
155
|
+
|
|
156
|
+
@mcp.tool()
|
|
157
|
+
def click_screen(x: int, y: int) -> str:
|
|
158
|
+
"""Click at the specified screen coordinates."""
|
|
159
|
+
try:
|
|
160
|
+
pyautogui.click(x=x, y=y)
|
|
161
|
+
return f"Successfully clicked at coordinates ({x}, {y})"
|
|
162
|
+
except Exception as e:
|
|
163
|
+
return f"Error clicking at coordinates ({x}, {y}): {str(e)}"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@mcp.tool()
|
|
167
|
+
def get_screen_size() -> Dict[str, Any]:
|
|
168
|
+
"""Get the current screen resolution."""
|
|
169
|
+
try:
|
|
170
|
+
width, height = pyautogui.size()
|
|
171
|
+
return {
|
|
172
|
+
"width": width,
|
|
173
|
+
"height": height,
|
|
174
|
+
"message": f"Screen size: {width}x{height}",
|
|
175
|
+
}
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return {"error": str(e), "message": f"Error getting screen size: {str(e)}"}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@mcp.tool()
|
|
181
|
+
def type_text(text: str) -> str:
|
|
182
|
+
"""Type the specified text at the current cursor position."""
|
|
183
|
+
try:
|
|
184
|
+
pyautogui.typewrite(text)
|
|
185
|
+
return f"Successfully typed text: {text}"
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return f"Error typing text: {str(e)}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
def take_screenshot(
|
|
192
|
+
title_pattern: str = None,
|
|
193
|
+
use_regex: bool = False,
|
|
194
|
+
threshold: int = 60,
|
|
195
|
+
with_ocr_text_and_coords: bool = False,
|
|
196
|
+
scale_percent_for_ocr: int = None,
|
|
197
|
+
save_to_downloads: bool = False,
|
|
198
|
+
) -> Image | List[Tuple[List[List[int]], str, float]]:
|
|
199
|
+
"""
|
|
200
|
+
Get screenshot and OCR text with absolute coordinates (returned after adding the window offset from true (0, 0) of screen to the OCR coordinates, so clicking is on-point. Recommended to click in the middle of OCR Box) and confidence from window with the specified title pattern.
|
|
201
|
+
If no title pattern is provided, get screenshot of entire screen and all text on the screen.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
title_pattern: Pattern to match window title, if None, take screenshot of entire screen
|
|
205
|
+
use_regex: If True, treat the pattern as a regex, otherwise best match with fuzzy matching
|
|
206
|
+
threshold: Minimum score (0-100) required for a fuzzy match
|
|
207
|
+
with_ocr_text_and_coords: If True, get OCR text with absolute coordinates from the screenshot
|
|
208
|
+
scale_percent_for_ocr: Percentage to scale the image down before processing, you wont need this most of the time unless your pc is extremely old or slow
|
|
209
|
+
save_to_downloads: If True, save the screenshot to the downloads directory and return the absolute path
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Returns a single screenshot as MCP Image object, if with_ocr_text_and_coords is True, returns a MCP Image object followed by list of UI elements as [[4 corners of box], text, confidence], "content type image not supported" means preview isnt supported but Image object is there.
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
|
|
216
|
+
all_windows = gw.getAllWindows()
|
|
217
|
+
|
|
218
|
+
# Convert to list of dictionaries for _find_matching_window
|
|
219
|
+
windows = []
|
|
220
|
+
for window in all_windows:
|
|
221
|
+
if window.title: # Only include windows with titles
|
|
222
|
+
windows.append(
|
|
223
|
+
{
|
|
224
|
+
"title": window.title,
|
|
225
|
+
"window_obj": window, # Store the actual window object
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
log(f"Found {len(windows)} windows")
|
|
230
|
+
window = _find_matching_window(windows, title_pattern, use_regex, threshold)
|
|
231
|
+
window = window["window_obj"] if window else None
|
|
232
|
+
|
|
233
|
+
# Store the currently active window
|
|
234
|
+
|
|
235
|
+
# Take the screenshot
|
|
236
|
+
if not window:
|
|
237
|
+
log("No matching window found, taking screenshot of entire screen")
|
|
238
|
+
screenshot = pyautogui.screenshot()
|
|
239
|
+
else:
|
|
240
|
+
current_active_window = gw.getActiveWindow()
|
|
241
|
+
log(f"Taking screenshot of window: {window.title}")
|
|
242
|
+
# Activate the window and wait for it to be fully in focus
|
|
243
|
+
try:
|
|
244
|
+
|
|
245
|
+
window.activate()
|
|
246
|
+
pyautogui.sleep(0.5) # Wait for 0.5 seconds to ensure window is active
|
|
247
|
+
screenshot = pyautogui.screenshot(
|
|
248
|
+
region=(window.left, window.top, window.width, window.height)
|
|
249
|
+
)
|
|
250
|
+
# Restore the previously active window
|
|
251
|
+
if current_active_window:
|
|
252
|
+
try:
|
|
253
|
+
current_active_window.activate()
|
|
254
|
+
pyautogui.sleep(0.2) # Wait a bit to ensure previous window is restored
|
|
255
|
+
except Exception as e:
|
|
256
|
+
log(f"Error restoring previous window: {str(e)}")
|
|
257
|
+
except Exception as e:
|
|
258
|
+
log(f"Error taking screenshot of window: {str(e)}")
|
|
259
|
+
return f"Error taking screenshot of window: {str(e)}"
|
|
260
|
+
|
|
261
|
+
# Create temp directory
|
|
262
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
263
|
+
|
|
264
|
+
# Save screenshot and get filepath
|
|
265
|
+
filepath, _ = save_image_to_downloads(
|
|
266
|
+
screenshot, prefix="screenshot", directory=temp_dir
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Create Image object from filepath
|
|
270
|
+
image = Image(filepath)
|
|
271
|
+
|
|
272
|
+
if not with_ocr_text_and_coords:
|
|
273
|
+
return image # MCP Image object
|
|
274
|
+
|
|
275
|
+
# Copy from temp to downloads
|
|
276
|
+
if save_to_downloads:
|
|
277
|
+
log("Copying screenshot from temp to downloads")
|
|
278
|
+
shutil.copy(filepath, get_downloads_dir())
|
|
279
|
+
|
|
280
|
+
image_path = image.path
|
|
281
|
+
img = cv2.imread(image_path)
|
|
282
|
+
|
|
283
|
+
if scale_percent_for_ocr is None:
|
|
284
|
+
# Calculate percent to scale height to 360 pixels
|
|
285
|
+
scale_percent_for_ocr = 100 # 360 / img.shape[0] * 100
|
|
286
|
+
|
|
287
|
+
# Lower down resolution before processing
|
|
288
|
+
width = int(img.shape[1] * scale_percent_for_ocr / 100)
|
|
289
|
+
height = int(img.shape[0] * scale_percent_for_ocr / 100)
|
|
290
|
+
dim = (width, height)
|
|
291
|
+
resized_img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
|
|
292
|
+
# save resized image to pwd
|
|
293
|
+
# cv2.imwrite("resized_img.png", resized_img)
|
|
294
|
+
|
|
295
|
+
output = engine(resized_img)
|
|
296
|
+
boxes = output.boxes
|
|
297
|
+
txts = output.txts
|
|
298
|
+
scores = output.scores
|
|
299
|
+
zipped_results = list(zip(boxes, txts, scores))
|
|
300
|
+
zipped_results = [
|
|
301
|
+
(box.tolist(), text, float(score)) # convert np.array -> list, ensure score is float
|
|
302
|
+
for box, text, score in zipped_results
|
|
303
|
+
]
|
|
304
|
+
log(f"Found {len(zipped_results)} text items in OCR result.")
|
|
305
|
+
log(f"First 5 items: {zipped_results[:5]}")
|
|
306
|
+
return json.dumps(zipped_results)
|
|
307
|
+
# ...existing code...
|
|
308
|
+
except Exception as e:
|
|
309
|
+
log(f"Error in screenshot or getting UI elements: {str(e)}")
|
|
310
|
+
import traceback
|
|
311
|
+
|
|
312
|
+
stack_trace = traceback.format_exc()
|
|
313
|
+
log(f"Stack trace:\n{stack_trace}")
|
|
314
|
+
return f"Error in screenshot or getting UI elements: {str(e)}\nStack trace:\n{stack_trace}"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@mcp.tool()
|
|
318
|
+
def move_mouse(x: int, y: int) -> str:
|
|
319
|
+
"""Move the mouse to the specified screen coordinates."""
|
|
320
|
+
try:
|
|
321
|
+
pyautogui.moveTo(x=x, y=y)
|
|
322
|
+
return f"Successfully moved mouse to coordinates ({x}, {y})"
|
|
323
|
+
except Exception as e:
|
|
324
|
+
return f"Error moving mouse to coordinates ({x}, {y}): {str(e)}"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@mcp.tool()
|
|
328
|
+
async def drag_mouse(
|
|
329
|
+
from_x: int, from_y: int, to_x: int, to_y: int, duration: float = 0.5
|
|
330
|
+
) -> str:
|
|
331
|
+
"""
|
|
332
|
+
Drag the mouse from one position to another.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
from_x: Starting X coordinate
|
|
336
|
+
from_y: Starting Y coordinate
|
|
337
|
+
to_x: Ending X coordinate
|
|
338
|
+
to_y: Ending Y coordinate
|
|
339
|
+
duration: Duration of the drag in seconds (default: 0.5)
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Success or error message
|
|
343
|
+
"""
|
|
344
|
+
try:
|
|
345
|
+
# First move to the starting position
|
|
346
|
+
pyautogui.moveTo(x=from_x, y=from_y)
|
|
347
|
+
# Then drag to the destination
|
|
348
|
+
log("starting drag")
|
|
349
|
+
await asyncio.to_thread(pyautogui.dragTo, x=to_x, y=to_y, duration=duration)
|
|
350
|
+
log("done drag")
|
|
351
|
+
return f"Successfully dragged from ({from_x}, {from_y}) to ({to_x}, {to_y})"
|
|
352
|
+
except Exception as e:
|
|
353
|
+
return f"Error dragging from ({from_x}, {from_y}) to ({to_x}, {to_y}): {str(e)}"
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@mcp.tool()
|
|
357
|
+
def press_key(key: str) -> str:
|
|
358
|
+
"""Press the specified keyboard key."""
|
|
359
|
+
try:
|
|
360
|
+
pyautogui.press(key)
|
|
361
|
+
return f"Successfully pressed key: {key}"
|
|
362
|
+
except Exception as e:
|
|
363
|
+
return f"Error pressing key {key}: {str(e)}"
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@mcp.tool()
|
|
367
|
+
def list_windows() -> List[Dict[str, Any]]:
|
|
368
|
+
"""List all open windows on the system."""
|
|
369
|
+
try:
|
|
370
|
+
windows = gw.getAllWindows()
|
|
371
|
+
result = []
|
|
372
|
+
for window in windows:
|
|
373
|
+
if window.title: # Only include windows with titles
|
|
374
|
+
result.append(
|
|
375
|
+
{
|
|
376
|
+
"title": window.title,
|
|
377
|
+
"left": window.left,
|
|
378
|
+
"top": window.top,
|
|
379
|
+
"width": window.width,
|
|
380
|
+
"height": window.height,
|
|
381
|
+
"is_active": window.isActive,
|
|
382
|
+
"is_visible": window.visible,
|
|
383
|
+
"is_minimized": window.isMinimized,
|
|
384
|
+
"is_maximized": window.isMaximized,
|
|
385
|
+
# "screenshot": pyautogui.screenshot(
|
|
386
|
+
# region=(
|
|
387
|
+
# window.left,
|
|
388
|
+
# window.top,
|
|
389
|
+
# window.width,
|
|
390
|
+
# window.height,
|
|
391
|
+
# )
|
|
392
|
+
# ),
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
return result
|
|
396
|
+
except Exception as e:
|
|
397
|
+
log(f"Error listing windows: {str(e)}")
|
|
398
|
+
return [{"error": str(e)}]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@mcp.tool()
|
|
402
|
+
def activate_window(
|
|
403
|
+
title_pattern: str, use_regex: bool = False, threshold: int = 60
|
|
404
|
+
) -> str:
|
|
405
|
+
"""
|
|
406
|
+
Activate a window (bring it to the foreground) by matching its title.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
title_pattern: Pattern to match window title
|
|
410
|
+
use_regex: If True, treat the pattern as a regex, otherwise use fuzzy matching
|
|
411
|
+
threshold: Minimum score (0-100) required for a fuzzy match
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Success or error message
|
|
415
|
+
"""
|
|
416
|
+
try:
|
|
417
|
+
# Get all windows
|
|
418
|
+
all_windows = gw.getAllWindows()
|
|
419
|
+
|
|
420
|
+
# Convert to list of dictionaries for _find_matching_window
|
|
421
|
+
windows = []
|
|
422
|
+
for window in all_windows:
|
|
423
|
+
if window.title: # Only include windows with titles
|
|
424
|
+
windows.append(
|
|
425
|
+
{
|
|
426
|
+
"title": window.title,
|
|
427
|
+
"window_obj": window, # Store the actual window object
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Find matching window using our improved function
|
|
432
|
+
matched_window_dict = _find_matching_window(
|
|
433
|
+
windows, title_pattern, use_regex, threshold
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
if not matched_window_dict:
|
|
437
|
+
log(f"No window found matching pattern: {title_pattern}")
|
|
438
|
+
return f"Error: No window found matching pattern: {title_pattern}"
|
|
439
|
+
|
|
440
|
+
# Get the actual window object
|
|
441
|
+
matched_window = matched_window_dict["window_obj"]
|
|
442
|
+
|
|
443
|
+
# Activate the window
|
|
444
|
+
matched_window.activate()
|
|
445
|
+
|
|
446
|
+
return f"Successfully activated window: '{matched_window.title}'"
|
|
447
|
+
except Exception as e:
|
|
448
|
+
log(f"Error activating window: {str(e)}")
|
|
449
|
+
return f"Error activating window: {str(e)}"
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def main():
|
|
453
|
+
"""Main entry point for the MCP server."""
|
|
454
|
+
pyautogui.FAILSAFE = True
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
# Run the server
|
|
458
|
+
mcp.run()
|
|
459
|
+
|
|
460
|
+
except KeyboardInterrupt:
|
|
461
|
+
log("Server shutting down...")
|
|
462
|
+
except Exception as e:
|
|
463
|
+
log(f"Error: {str(e)}")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
if __name__ == "__main__":
|
|
467
|
+
main()
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "computer-control-mcp"
|
|
7
|
-
version = "0.2.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.2.9"
|
|
8
|
+
description = "MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "
|
|
13
|
+
{name = "AB498", email = "abcd49800@gmail.com"},
|
|
14
14
|
]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
@@ -22,15 +22,15 @@ classifiers = [
|
|
|
22
22
|
"Topic :: Utilities"
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
|
-
"pyautogui",
|
|
26
|
-
"mcp[cli]",
|
|
27
|
-
"pillow",
|
|
28
|
-
"pygetwindow",
|
|
29
|
-
"fuzzywuzzy",
|
|
30
|
-
"rapidocr",
|
|
31
|
-
"onnxruntime",
|
|
32
|
-
"rapidocr_onnxruntime",
|
|
33
|
-
"opencv-python"
|
|
25
|
+
"pyautogui==0.9.54",
|
|
26
|
+
"mcp[cli]==1.13.0",
|
|
27
|
+
"pillow==11.3.0",
|
|
28
|
+
"pygetwindow==0.0.9",
|
|
29
|
+
"fuzzywuzzy==0.18.0",
|
|
30
|
+
"rapidocr==3.3.1",
|
|
31
|
+
"onnxruntime==1.22.1",
|
|
32
|
+
"rapidocr_onnxruntime==1.4.4",
|
|
33
|
+
"opencv-python==4.12.0.88"
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
@@ -43,7 +43,7 @@ computer-control-mcp = "computer_control_mcp.cli:main"
|
|
|
43
43
|
computer-control-mcp-server = "computer_control_mcp.server:main"
|
|
44
44
|
|
|
45
45
|
[tool.hatch.build.targets.wheel]
|
|
46
|
-
packages = ["computer_control_mcp"]
|
|
46
|
+
packages = ["src/computer_control_mcp"]
|
|
47
47
|
|
|
48
48
|
[tool.pytest.ini_options]
|
|
49
49
|
testpaths = ["tests"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{computer_control_mcp-0.2.7 → computer_control_mcp-0.2.9}/computer_control_mcp/test_image.png
RENAMED
|
File without changes
|