computer-control-mcp 0.3.7__tar.gz → 0.3.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: computer-control-mcp
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
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
@@ -28,6 +28,7 @@ Requires-Dist: python-levenshtein>=0.20.9
28
28
  Requires-Dist: pywinctl==0.4.1
29
29
  Requires-Dist: rapidocr-onnxruntime==1.2.3
30
30
  Requires-Dist: rapidocr==3.3.1
31
+ Requires-Dist: windows-capture>=1.0.0; sys_platform == 'win32'
31
32
  Description-Content-Type: text/markdown
32
33
 
33
34
  # Computer Control MCP
@@ -82,6 +83,99 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
82
83
  - List and activate windows
83
84
  - Press keyboard keys
84
85
  - Drag and drop operations
86
+ - Enhanced screenshot capture for GPU-accelerated windows (Windows only)
87
+
88
+ ## Why Windows Graphics Capture (WGC) is Needed
89
+
90
+ Traditional screenshot methods like GDI/PrintWindow cannot capture the content of GPU-accelerated windows, resulting in black screens. This affects:
91
+
92
+ - **Games and 3D applications** that use DirectX/OpenGL
93
+ - **Media players** with hardware-accelerated video decoding
94
+ - **Electron applications** like Discord, WhatsApp, and Slack
95
+ - **Browsers** with GPU acceleration enabled
96
+ - **Streaming/recording software** like OBS Studio
97
+ - **CAD and design software** that utilize GPU rendering
98
+
99
+ WGC solves this by using the modern Windows Graphics Capture API, which can capture frames directly from the GPU composition surface, bypassing the limitations of traditional capture methods.
100
+
101
+ ## Configuration
102
+
103
+ ### Custom Screenshot Directory
104
+
105
+ By default, screenshots are saved to the OS downloads directory. You can customize this by setting the `COMPUTER_CONTROL_MCP_SCREENSHOT_DIR` environment variable:
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "computer-control-mcp": {
111
+ "command": "uvx",
112
+ "args": ["computer-control-mcp@latest"],
113
+ "env": {
114
+ "COMPUTER_CONTROL_MCP_SCREENSHOT_DIR": "C:\\Users\\YourName\\Pictures\\Screenshots"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ Or set it system-wide:
122
+ ```bash
123
+ # Windows (PowerShell)
124
+ $env:COMPUTER_CONTROL_MCP_SCREENSHOT_DIR = "C:\Users\YourName\Pictures\Screenshots"
125
+
126
+ # macOS/Linux
127
+ export COMPUTER_CONTROL_MCP_SCREENSHOT_DIR="/home/yourname/Pictures/Screenshots"
128
+ ```
129
+
130
+ If the specified directory doesn't exist, the server will fall back to the default downloads directory.
131
+
132
+ ### Automatic WGC for Specific Windows
133
+
134
+ You can configure the system to automatically use Windows Graphics Capture (WGC) for specific windows by setting the `COMPUTER_CONTROL_MCP_WGC_PATTERNS` environment variable. This variable should contain comma-separated patterns that match window titles:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "computer-control-mcp": {
140
+ "command": "uvx",
141
+ "args": ["computer-control-mcp@latest"],
142
+ "env": {
143
+ "COMPUTER_CONTROL_MCP_WGC_PATTERNS": "obs, discord, game, steam"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ Or set it system-wide:
151
+ ```bash
152
+ # Windows (PowerShell)
153
+ $env:COMPUTER_CONTROL_MCP_WGC_PATTERNS = "obs, discord, game, steam"
154
+
155
+ # macOS/Linux
156
+ export COMPUTER_CONTROL_MCP_WGC_PATTERNS="obs, discord, game, steam"
157
+ ```
158
+
159
+ When this variable is set, any window whose title contains any of the specified patterns will automatically use WGC for screenshot capture, eliminating black screens for GPU-accelerated applications.
160
+
161
+ ## Windows Graphics Capture (WGC) Support
162
+
163
+ On Windows 10 version 1803 and later, this package supports the Windows Graphics Capture (WGC) API for enhanced screenshot capabilities. This is particularly useful for capturing GPU-accelerated windows such as:
164
+
165
+ - OBS Studio
166
+ - Games and 3D applications
167
+ - Discord, WhatsApp (Electron applications)
168
+ - Video players with hardware decode
169
+ - Browsers with GPU acceleration
170
+
171
+ To enable WGC support, install the optional dependency:
172
+ ```bash
173
+ pip install windows-capture
174
+ ```
175
+
176
+ When WGC is available, the [take_screenshot](file:///D:/code/mcp/computer-control-mcp/src/computer_control_mcp/core.py#L305-L424) tool will automatically attempt to use it for window captures when:
177
+ 1. The `use_wgc` parameter is set to `True`
178
+ 2. The window title matches any pattern defined in the `COMPUTER_CONTROL_MCP_WGC_PATTERNS` environment variable
85
179
 
86
180
  ## Available Tools
87
181
 
@@ -100,7 +194,7 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
100
194
  - `press_keys(keys: Union[str, List[Union[str, List[str]]]])`: Press keyboard keys (supports single keys, sequences, and combinations)
101
195
 
102
196
  ### Screen and Window Management
103
- - `take_screenshot(title_pattern: str = None, use_regex: bool = False, threshold: int = 60, scale_percent_for_ocr: int = None, save_to_downloads: bool = False)`: Capture screen or window
197
+ - `take_screenshot(title_pattern: str = None, use_regex: bool = False, threshold: int = 60, scale_percent_for_ocr: int = None, save_to_downloads: bool = False, use_wgc: bool = False)`: Capture screen or window
104
198
  - `take_screenshot_with_ocr(title_pattern: str = None, use_regex: bool = False, threshold: int = 10, scale_percent_for_ocr: int = None, save_to_downloads: bool = False)`: Extract adn return text with coordinates using OCR from screen or window
105
199
  - `get_screen_size()`: Get current screen resolution
106
200
  - `list_windows()`: List all open windows
@@ -116,15 +210,17 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
116
210
  git clone https://github.com/AB498/computer-control-mcp.git
117
211
  cd computer-control-mcp
118
212
 
119
- # Install in development mode
213
+ # Build/Run:
214
+
215
+ # 1. Install in development mode | Meaning that your edits to source code will be reflected in the installed package.
120
216
  pip install -e .
121
217
 
122
- # Start server
123
- python -m computer_control_mcp.core
218
+ # Then Start server | This is equivalent to `uvx computer-control-mcp@latest` just the local code is used
219
+ computer-control-mcp
124
220
 
125
221
  # -- OR --
126
222
 
127
- # Build after `pip install hatch`
223
+ # 2. Build after `pip install hatch` | This needs version increment in orer to reflect code changes
128
224
  hatch build
129
225
 
130
226
  # Windows
@@ -155,4 +251,4 @@ MIT
155
251
  ## For more information or help
156
252
 
157
253
  - [Email (abcd49800@gmail.com)](mailto:abcd49800@gmail.com)
158
- - [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
254
+ - [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
@@ -50,6 +50,99 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
50
50
  - List and activate windows
51
51
  - Press keyboard keys
52
52
  - Drag and drop operations
53
+ - Enhanced screenshot capture for GPU-accelerated windows (Windows only)
54
+
55
+ ## Why Windows Graphics Capture (WGC) is Needed
56
+
57
+ Traditional screenshot methods like GDI/PrintWindow cannot capture the content of GPU-accelerated windows, resulting in black screens. This affects:
58
+
59
+ - **Games and 3D applications** that use DirectX/OpenGL
60
+ - **Media players** with hardware-accelerated video decoding
61
+ - **Electron applications** like Discord, WhatsApp, and Slack
62
+ - **Browsers** with GPU acceleration enabled
63
+ - **Streaming/recording software** like OBS Studio
64
+ - **CAD and design software** that utilize GPU rendering
65
+
66
+ WGC solves this by using the modern Windows Graphics Capture API, which can capture frames directly from the GPU composition surface, bypassing the limitations of traditional capture methods.
67
+
68
+ ## Configuration
69
+
70
+ ### Custom Screenshot Directory
71
+
72
+ By default, screenshots are saved to the OS downloads directory. You can customize this by setting the `COMPUTER_CONTROL_MCP_SCREENSHOT_DIR` environment variable:
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "computer-control-mcp": {
78
+ "command": "uvx",
79
+ "args": ["computer-control-mcp@latest"],
80
+ "env": {
81
+ "COMPUTER_CONTROL_MCP_SCREENSHOT_DIR": "C:\\Users\\YourName\\Pictures\\Screenshots"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ Or set it system-wide:
89
+ ```bash
90
+ # Windows (PowerShell)
91
+ $env:COMPUTER_CONTROL_MCP_SCREENSHOT_DIR = "C:\Users\YourName\Pictures\Screenshots"
92
+
93
+ # macOS/Linux
94
+ export COMPUTER_CONTROL_MCP_SCREENSHOT_DIR="/home/yourname/Pictures/Screenshots"
95
+ ```
96
+
97
+ If the specified directory doesn't exist, the server will fall back to the default downloads directory.
98
+
99
+ ### Automatic WGC for Specific Windows
100
+
101
+ You can configure the system to automatically use Windows Graphics Capture (WGC) for specific windows by setting the `COMPUTER_CONTROL_MCP_WGC_PATTERNS` environment variable. This variable should contain comma-separated patterns that match window titles:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "computer-control-mcp": {
107
+ "command": "uvx",
108
+ "args": ["computer-control-mcp@latest"],
109
+ "env": {
110
+ "COMPUTER_CONTROL_MCP_WGC_PATTERNS": "obs, discord, game, steam"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Or set it system-wide:
118
+ ```bash
119
+ # Windows (PowerShell)
120
+ $env:COMPUTER_CONTROL_MCP_WGC_PATTERNS = "obs, discord, game, steam"
121
+
122
+ # macOS/Linux
123
+ export COMPUTER_CONTROL_MCP_WGC_PATTERNS="obs, discord, game, steam"
124
+ ```
125
+
126
+ When this variable is set, any window whose title contains any of the specified patterns will automatically use WGC for screenshot capture, eliminating black screens for GPU-accelerated applications.
127
+
128
+ ## Windows Graphics Capture (WGC) Support
129
+
130
+ On Windows 10 version 1803 and later, this package supports the Windows Graphics Capture (WGC) API for enhanced screenshot capabilities. This is particularly useful for capturing GPU-accelerated windows such as:
131
+
132
+ - OBS Studio
133
+ - Games and 3D applications
134
+ - Discord, WhatsApp (Electron applications)
135
+ - Video players with hardware decode
136
+ - Browsers with GPU acceleration
137
+
138
+ To enable WGC support, install the optional dependency:
139
+ ```bash
140
+ pip install windows-capture
141
+ ```
142
+
143
+ When WGC is available, the [take_screenshot](file:///D:/code/mcp/computer-control-mcp/src/computer_control_mcp/core.py#L305-L424) tool will automatically attempt to use it for window captures when:
144
+ 1. The `use_wgc` parameter is set to `True`
145
+ 2. The window title matches any pattern defined in the `COMPUTER_CONTROL_MCP_WGC_PATTERNS` environment variable
53
146
 
54
147
  ## Available Tools
55
148
 
@@ -68,7 +161,7 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
68
161
  - `press_keys(keys: Union[str, List[Union[str, List[str]]]])`: Press keyboard keys (supports single keys, sequences, and combinations)
69
162
 
70
163
  ### Screen and Window Management
71
- - `take_screenshot(title_pattern: str = None, use_regex: bool = False, threshold: int = 60, scale_percent_for_ocr: int = None, save_to_downloads: bool = False)`: Capture screen or window
164
+ - `take_screenshot(title_pattern: str = None, use_regex: bool = False, threshold: int = 60, scale_percent_for_ocr: int = None, save_to_downloads: bool = False, use_wgc: bool = False)`: Capture screen or window
72
165
  - `take_screenshot_with_ocr(title_pattern: str = None, use_regex: bool = False, threshold: int = 10, scale_percent_for_ocr: int = None, save_to_downloads: bool = False)`: Extract adn return text with coordinates using OCR from screen or window
73
166
  - `get_screen_size()`: Get current screen resolution
74
167
  - `list_windows()`: List all open windows
@@ -84,15 +177,17 @@ computer-control-mcp # instead of uvx computer-control-mcp, so you can use the l
84
177
  git clone https://github.com/AB498/computer-control-mcp.git
85
178
  cd computer-control-mcp
86
179
 
87
- # Install in development mode
180
+ # Build/Run:
181
+
182
+ # 1. Install in development mode | Meaning that your edits to source code will be reflected in the installed package.
88
183
  pip install -e .
89
184
 
90
- # Start server
91
- python -m computer_control_mcp.core
185
+ # Then Start server | This is equivalent to `uvx computer-control-mcp@latest` just the local code is used
186
+ computer-control-mcp
92
187
 
93
188
  # -- OR --
94
189
 
95
- # Build after `pip install hatch`
190
+ # 2. Build after `pip install hatch` | This needs version increment in orer to reflect code changes
96
191
  hatch build
97
192
 
98
193
  # Windows
@@ -123,4 +218,4 @@ MIT
123
218
  ## For more information or help
124
219
 
125
220
  - [Email (abcd49800@gmail.com)](mailto:abcd49800@gmail.com)
126
- - [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
221
+ - [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
@@ -1,128 +1,128 @@
1
- """
2
- Command-line interface for Computer Control MCP.
3
-
4
- This module provides a command-line interface for interacting with the Computer Control MCP.
5
- """
6
-
7
- import argparse
8
- import sys
9
- from computer_control_mcp.core import mcp, main as run_server
10
-
11
- def parse_args():
12
- """Parse command-line arguments."""
13
- parser = argparse.ArgumentParser(description="Computer Control MCP CLI")
14
-
15
- subparsers = parser.add_subparsers(dest="command", help="Command to run")
16
-
17
- # Server command
18
- server_parser = subparsers.add_parser("server", help="Run the MCP server")
19
-
20
- # Click command
21
- click_parser = subparsers.add_parser("click", help="Click at specified coordinates")
22
- click_parser.add_argument("x", type=int, help="X coordinate")
23
- click_parser.add_argument("y", type=int, help="Y coordinate")
24
-
25
- # Type text command
26
- type_parser = subparsers.add_parser("type", help="Type text at current cursor position")
27
- type_parser.add_argument("text", help="Text to type")
28
-
29
- # Screenshot command
30
- screenshot_parser = subparsers.add_parser("screenshot", help="Take a screenshot")
31
- screenshot_parser.add_argument("--mode", choices=["all_windows", "single_window", "whole_screen"],
32
- default="whole_screen", help="Screenshot mode")
33
- screenshot_parser.add_argument("--title", help="Window title pattern (for single_window mode)")
34
- screenshot_parser.add_argument("--regex", action="store_true", help="Use regex for title matching")
35
- screenshot_parser.add_argument("--output", help="Output file path (if not provided, saves to downloads directory)")
36
- screenshot_parser.add_argument("--no-save", action="store_true", help="Don't save images to downloads directory")
37
-
38
- # List windows command
39
- subparsers.add_parser("list-windows", help="List all open windows")
40
-
41
- # GUI command
42
- subparsers.add_parser("gui", help="Launch the GUI test harness")
43
-
44
- return parser.parse_args()
45
-
46
- def main():
47
- """Main entry point for the CLI."""
48
- args = parse_args()
49
-
50
- if args.command == "server":
51
- run_server()
52
-
53
- elif args.command == "click":
54
- # Call the tool using the call_tool method
55
- import asyncio
56
- result = asyncio.run(mcp.call_tool("click_screen", {"x": args.x, "y": args.y}))
57
- print(result)
58
-
59
- elif args.command == "type":
60
- # Call the tool using the call_tool method
61
- import asyncio
62
- result = asyncio.run(mcp.call_tool("type_text", {"text": args.text}))
63
- print(result)
64
-
65
- elif args.command == "screenshot":
66
- if args.mode == "single_window" and not args.title:
67
- print("Error: --title is required for single_window mode")
68
- sys.exit(1)
69
-
70
- # Call the tool using the call_tool method
71
- import asyncio
72
- result = asyncio.run(mcp.call_tool("take_screenshot", {
73
- "mode": args.mode,
74
- "title_pattern": args.title,
75
- "use_regex": args.regex,
76
- "save_to_downloads": not args.no_save
77
- }))
78
-
79
- if args.output:
80
- # Save the screenshot to a specific file path provided by user
81
- with open(args.output, "wb") as f:
82
- f.write(result.image.data)
83
- print(f"Screenshot saved to {args.output}")
84
- elif hasattr(result, 'file_path'):
85
- # If image was saved to downloads, show the path
86
- print(f"Screenshot saved to {result.file_path}")
87
- else:
88
- print("Screenshot taken successfully")
89
-
90
- # If we have multiple results (all_windows mode)
91
- if args.mode == "all_windows" and isinstance(result, list):
92
- print("\nAll screenshots:")
93
- for i, item in enumerate(result):
94
- if hasattr(item, 'file_path'):
95
- window_title = item.window_info.title if hasattr(item, 'window_info') else f"Window {i+1}"
96
- print(f"{i+1}. {window_title}: {item.file_path}")
97
-
98
- elif args.command == "list-windows":
99
- # Call the tool using the call_tool method
100
- import asyncio
101
- result = asyncio.run(mcp.call_tool("list_windows", {}))
102
-
103
- # Parse the result
104
- windows = []
105
- for item in result:
106
- if hasattr(item, 'text'):
107
- try:
108
- import json
109
- window_info = json.loads(item.text)
110
- windows.append(window_info)
111
- except json.JSONDecodeError:
112
- print(f"Failed to parse window info: {item.text}")
113
-
114
- # Display the windows
115
- for i, window in enumerate(windows):
116
- print(f"{i+1}. {window.get('title')} ({window.get('width')}x{window.get('height')})")
117
-
118
- elif args.command == "gui":
119
- from computer_control_mcp.gui import main as run_gui
120
- run_gui()
121
-
122
- else:
123
- # When no command is specified, run the server by default
124
- print("MCP server started!")
125
- run_server()
126
-
127
- if __name__ == "__main__":
128
- main()
1
+ """
2
+ Command-line interface for Computer Control MCP.
3
+
4
+ This module provides a command-line interface for interacting with the Computer Control MCP.
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from computer_control_mcp.core import mcp, main as run_server
10
+
11
+ def parse_args():
12
+ """Parse command-line arguments."""
13
+ parser = argparse.ArgumentParser(description="Computer Control MCP CLI")
14
+
15
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
16
+
17
+ # Server command
18
+ server_parser = subparsers.add_parser("server", help="Run the MCP server")
19
+
20
+ # Click command
21
+ click_parser = subparsers.add_parser("click", help="Click at specified coordinates")
22
+ click_parser.add_argument("x", type=int, help="X coordinate")
23
+ click_parser.add_argument("y", type=int, help="Y coordinate")
24
+
25
+ # Type text command
26
+ type_parser = subparsers.add_parser("type", help="Type text at current cursor position")
27
+ type_parser.add_argument("text", help="Text to type")
28
+
29
+ # Screenshot command
30
+ screenshot_parser = subparsers.add_parser("screenshot", help="Take a screenshot")
31
+ screenshot_parser.add_argument("--mode", choices=["all_windows", "single_window", "whole_screen"],
32
+ default="whole_screen", help="Screenshot mode")
33
+ screenshot_parser.add_argument("--title", help="Window title pattern (for single_window mode)")
34
+ screenshot_parser.add_argument("--regex", action="store_true", help="Use regex for title matching")
35
+ screenshot_parser.add_argument("--output", help="Output file path (if not provided, saves to downloads directory)")
36
+ screenshot_parser.add_argument("--no-save", action="store_true", help="Don't save images to downloads directory")
37
+
38
+ # List windows command
39
+ subparsers.add_parser("list-windows", help="List all open windows")
40
+
41
+ # GUI command
42
+ subparsers.add_parser("gui", help="Launch the GUI test harness")
43
+
44
+ return parser.parse_args()
45
+
46
+ def main():
47
+ """Main entry point for the CLI."""
48
+ args = parse_args()
49
+
50
+ if args.command == "server":
51
+ run_server()
52
+
53
+ elif args.command == "click":
54
+ # Call the tool using the call_tool method
55
+ import asyncio
56
+ result = asyncio.run(mcp.call_tool("click_screen", {"x": args.x, "y": args.y}))
57
+ print(result)
58
+
59
+ elif args.command == "type":
60
+ # Call the tool using the call_tool method
61
+ import asyncio
62
+ result = asyncio.run(mcp.call_tool("type_text", {"text": args.text}))
63
+ print(result)
64
+
65
+ elif args.command == "screenshot":
66
+ if args.mode == "single_window" and not args.title:
67
+ print("Error: --title is required for single_window mode")
68
+ sys.exit(1)
69
+
70
+ # Call the tool using the call_tool method
71
+ import asyncio
72
+ result = asyncio.run(mcp.call_tool("take_screenshot", {
73
+ "mode": args.mode,
74
+ "title_pattern": args.title,
75
+ "use_regex": args.regex,
76
+ "save_to_downloads": not args.no_save
77
+ }))
78
+
79
+ if args.output:
80
+ # Save the screenshot to a specific file path provided by user
81
+ with open(args.output, "wb") as f:
82
+ f.write(result.image.data)
83
+ print(f"Screenshot saved to {args.output}")
84
+ elif hasattr(result, 'file_path'):
85
+ # If image was saved to downloads, show the path
86
+ print(f"Screenshot saved to {result.file_path}")
87
+ else:
88
+ print("Screenshot taken successfully")
89
+
90
+ # If we have multiple results (all_windows mode)
91
+ if args.mode == "all_windows" and isinstance(result, list):
92
+ print("\nAll screenshots:")
93
+ for i, item in enumerate(result):
94
+ if hasattr(item, 'file_path'):
95
+ window_title = item.window_info.title if hasattr(item, 'window_info') else f"Window {i+1}"
96
+ print(f"{i+1}. {window_title}: {item.file_path}")
97
+
98
+ elif args.command == "list-windows":
99
+ # Call the tool using the call_tool method
100
+ import asyncio
101
+ result = asyncio.run(mcp.call_tool("list_windows", {}))
102
+
103
+ # Parse the result
104
+ windows = []
105
+ for item in result:
106
+ if hasattr(item, 'text'):
107
+ try:
108
+ import json
109
+ window_info = json.loads(item.text)
110
+ windows.append(window_info)
111
+ except json.JSONDecodeError:
112
+ print(f"Failed to parse window info: {item.text}")
113
+
114
+ # Display the windows
115
+ for i, window in enumerate(windows):
116
+ print(f"{i+1}. {window.get('title')} ({window.get('width')}x{window.get('height')})")
117
+
118
+ elif args.command == "gui":
119
+ from computer_control_mcp.gui import main as run_gui
120
+ run_gui()
121
+
122
+ else:
123
+ # When no command is specified, run the server by default
124
+ print("MCP server started!")
125
+ run_server()
126
+
127
+ if __name__ == "__main__":
128
+ main()
@@ -18,6 +18,7 @@ import datetime
18
18
  from pathlib import Path
19
19
  import tempfile
20
20
  from typing import Union
21
+ import threading
21
22
 
22
23
  # --- Auto-install dependencies if needed ---
23
24
  import pyautogui
@@ -48,6 +49,14 @@ RELOAD_ENABLED = True # Set to False to disable auto-reload
48
49
  mcp = FastMCP("ComputerControlMCP")
49
50
 
50
51
 
52
+ # Try to import Windows Graphics Capture API
53
+ try:
54
+ from windows_capture import WindowsCapture, Frame, InternalCaptureControl
55
+ WGC_AVAILABLE = True
56
+ except ImportError:
57
+ WGC_AVAILABLE = False
58
+
59
+
51
60
  # Determine mode automatically
52
61
  IS_DEVELOPMENT = os.getenv("ENV") == "development"
53
62
 
@@ -64,7 +73,21 @@ def log(message: str) -> None:
64
73
 
65
74
 
66
75
  def get_downloads_dir() -> Path:
67
- """Get the OS downloads directory."""
76
+ """Get the directory for saving screenshots.
77
+
78
+ Checks for COMPUTER_CONTROL_MCP_SCREENSHOT_DIR environment variable first,
79
+ then falls back to the OS downloads directory.
80
+ """
81
+ # Check for custom directory from environment variable
82
+ custom_dir = os.getenv("COMPUTER_CONTROL_MCP_SCREENSHOT_DIR")
83
+ if custom_dir:
84
+ custom_path = Path(custom_dir)
85
+ if custom_path.exists() and custom_path.is_dir():
86
+ return custom_path
87
+ else:
88
+ log(f"Warning: COMPUTER_CONTROL_MCP_SCREENSHOT_DIR path '{custom_dir}' does not exist or is not a directory. Falling back to default.")
89
+
90
+ # Default: OS downloads directory
68
91
  if os.name == "nt": # Windows
69
92
  import winreg
70
93
 
@@ -77,6 +100,39 @@ def get_downloads_dir() -> Path:
77
100
  return Path.home() / "Downloads"
78
101
 
79
102
 
103
+ def _should_use_wgc_by_default(window_title: str) -> bool:
104
+ """Check if WGC should be used for a window based on environment variable patterns.
105
+
106
+ Checks the COMPUTER_CONTROL_MCP_WGC_PATTERNS environment variable, which should
107
+ contain comma-separated patterns. If any pattern matches the window title,
108
+ WGC will be used by default.
109
+
110
+ Args:
111
+ window_title: Title of the window to check
112
+
113
+ Returns:
114
+ True if WGC should be used by default for this window, False otherwise
115
+ """
116
+ # Get patterns from environment variable
117
+ patterns_str = os.getenv("COMPUTER_CONTROL_MCP_WGC_PATTERNS")
118
+ if not patterns_str:
119
+ return False
120
+
121
+ # Split patterns by comma and trim whitespace
122
+ patterns = [pattern.strip().lower() for pattern in patterns_str.split(",") if pattern.strip()]
123
+
124
+ # Convert window title to lowercase for case-insensitive matching
125
+ title_lower = window_title.lower()
126
+
127
+ # Check if any pattern matches
128
+ for pattern in patterns:
129
+ if pattern in title_lower:
130
+ log(f"Window '{window_title}' matches WGC pattern: {pattern}")
131
+ return True
132
+
133
+ return False
134
+
135
+
80
136
  def _mss_screenshot(region=None):
81
137
  """Take a screenshot using mss and return PIL Image.
82
138
 
@@ -107,6 +163,88 @@ def _mss_screenshot(region=None):
107
163
  )
108
164
 
109
165
 
166
+ def _wgc_screenshot(window_title: str) -> Optional[Tuple[bytes, int, int]]:
167
+ """Capture a window using Windows Graphics Capture API.
168
+
169
+ Args:
170
+ window_title: Title of the window to capture
171
+
172
+ Returns:
173
+ Tuple of (image_bytes, width, height) or None if failed
174
+ """
175
+ if not WGC_AVAILABLE:
176
+ log("Windows Graphics Capture API not available")
177
+ return None
178
+
179
+ captured_frame = {"data": None, "width": 0, "height": 0, "error": None}
180
+ capture_event = threading.Event()
181
+
182
+ try:
183
+ capture = WindowsCapture(
184
+ cursor_capture=False,
185
+ draw_border=False,
186
+ monitor_index=None,
187
+ window_name=window_title,
188
+ )
189
+
190
+ @capture.event
191
+ def on_frame_arrived(frame: Frame, capture_control: InternalCaptureControl):
192
+ try:
193
+ # Save frame to temp file, then read it back
194
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
195
+ tmp_path = tmp.name
196
+
197
+ frame.save_as_image(tmp_path)
198
+
199
+ with open(tmp_path, "rb") as f:
200
+ captured_frame["data"] = f.read()
201
+
202
+ # Get dimensions from the saved image
203
+ with PILImage.open(tmp_path) as img:
204
+ captured_frame["width"] = img.width
205
+ captured_frame["height"] = img.height
206
+
207
+ os.unlink(tmp_path)
208
+ except Exception as e:
209
+ captured_frame["error"] = str(e)
210
+ finally:
211
+ capture_control.stop()
212
+ capture_event.set()
213
+
214
+ @capture.event
215
+ def on_closed():
216
+ capture_event.set()
217
+
218
+ # Start capture in a thread
219
+ def run_capture():
220
+ try:
221
+ capture.start()
222
+ except Exception as e:
223
+ captured_frame["error"] = str(e)
224
+ capture_event.set()
225
+
226
+ thread = threading.Thread(target=run_capture, daemon=True)
227
+ thread.start()
228
+
229
+ # Wait for frame (with timeout)
230
+ if not capture_event.wait(timeout=5.0):
231
+ captured_frame["error"] = "Capture timed out"
232
+
233
+ if captured_frame["error"]:
234
+ log(f"WGC capture error: {captured_frame['error']}")
235
+ return None
236
+
237
+ if captured_frame["data"] is None:
238
+ log("No frame captured with WGC")
239
+ return None
240
+
241
+ return captured_frame["data"], captured_frame["width"], captured_frame["height"]
242
+
243
+ except Exception as e:
244
+ log(f"WGC capture failed: {e}")
245
+ return None
246
+
247
+
110
248
  def save_image_to_downloads(
111
249
  image, prefix: str = "screenshot", directory: Path = None
112
250
  ) -> Tuple[str, bytes]:
@@ -240,6 +378,7 @@ def take_screenshot(
240
378
  threshold: int = 10,
241
379
  scale_percent_for_ocr: int = None,
242
380
  save_to_downloads: bool = False,
381
+ use_wgc: bool = False,
243
382
  ) -> Image:
244
383
  """
245
384
  Get screenshot Image as MCP Image object. If no title pattern is provided, get screenshot of entire screen and all text on the screen.
@@ -250,6 +389,7 @@ def take_screenshot(
250
389
  threshold: Minimum score (0-100) required for a fuzzy match
251
390
  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
252
391
  save_to_downloads: If True, save the screenshot to the downloads directory and return the absolute path
392
+ use_wgc: If True, use Windows Graphics Capture API for window capture (recommended for GPU-accelerated windows)
253
393
 
254
394
  Returns:
255
395
  Returns a single screenshot as MCP Image object. "content type image not supported" means preview isnt supported but Image object is there and returned successfully.
@@ -305,22 +445,55 @@ def take_screenshot(
305
445
  current_active_window = gw.getActiveWindow()
306
446
  log(f"Taking screenshot of window: {window.title}")
307
447
 
308
- if sys.platform == "win32":
309
- force_activate(window)
448
+ # Determine if we should use WGC:
449
+ # 1. If explicitly requested via use_wgc parameter
450
+ # 2. If the window matches patterns defined in environment variable
451
+ should_use_wgc = use_wgc or _should_use_wgc_by_default(window.title)
452
+
453
+ # Try WGC capture first if requested or if it's likely a GPU-accelerated window
454
+ if should_use_wgc and WGC_AVAILABLE:
455
+ log("Attempting WGC capture")
456
+ wgc_result = _wgc_screenshot(window.title)
457
+ if wgc_result:
458
+ image_bytes, width, height = wgc_result
459
+ screenshot = PILImage.open(BytesIO(image_bytes))
460
+ log(f"WGC capture successful: {width}x{height}")
461
+ else:
462
+ log("WGC capture failed, falling back to MSS")
463
+ # Fall back to MSS if WGC fails
464
+ if sys.platform == "win32":
465
+ force_activate(window)
466
+ else:
467
+ window.activate()
468
+ pyautogui.sleep(0.5) # Give Windows time to focus
469
+
470
+ screen_width, screen_height = pyautogui.size()
471
+
472
+ screenshot = _mss_screenshot(
473
+ region=(
474
+ max(window.left, 0),
475
+ max(window.top, 0),
476
+ min(window.width, screen_width),
477
+ min(window.height, screen_height),
478
+ )
479
+ )
310
480
  else:
311
- window.activate()
312
- pyautogui.sleep(0.5) # Give Windows time to focus
313
-
314
- screen_width, screen_height = pyautogui.size()
315
-
316
- screenshot = _mss_screenshot(
317
- region=(
318
- max(window.left, 0),
319
- max(window.top, 0),
320
- min(window.width, screen_width),
321
- min(window.height, screen_height),
481
+ if sys.platform == "win32":
482
+ force_activate(window)
483
+ else:
484
+ window.activate()
485
+ pyautogui.sleep(0.5) # Give Windows time to focus
486
+
487
+ screen_width, screen_height = pyautogui.size()
488
+
489
+ screenshot = _mss_screenshot(
490
+ region=(
491
+ max(window.left, 0),
492
+ max(window.top, 0),
493
+ min(window.width, screen_width),
494
+ min(window.height, screen_height),
495
+ )
322
496
  )
323
- )
324
497
 
325
498
  # Restore previously active window
326
499
  if current_active_window and current_active_window != window:
@@ -737,6 +910,16 @@ def main():
737
910
  """Main entry point for the MCP server."""
738
911
  pyautogui.FAILSAFE = True
739
912
 
913
+ if WGC_AVAILABLE:
914
+ log("Windows Graphics Capture API is available for enhanced window capture")
915
+ # Check if any WGC patterns are configured
916
+ wgc_patterns = os.getenv("COMPUTER_CONTROL_MCP_WGC_PATTERNS")
917
+ if wgc_patterns:
918
+ patterns = [p.strip() for p in wgc_patterns.split(",") if p.strip()]
919
+ log(f"WGC patterns configured: {patterns}")
920
+ else:
921
+ log("Windows Graphics Capture API not available. Using standard capture methods.")
922
+
740
923
  try:
741
924
  # Run the server
742
925
  log("Computer Control MCP Server Started...")
@@ -749,4 +932,4 @@ def main():
749
932
 
750
933
 
751
934
  if __name__ == "__main__":
752
- main()
935
+ main()
@@ -1,15 +1,15 @@
1
- """
2
- Server module for Computer Control MCP.
3
-
4
- This module provides a simple way to run the MCP server.
5
- """
6
-
7
- from computer_control_mcp.core import main as run_server
8
-
9
- def main():
10
- """Run the MCP server."""
11
- print("Starting Computer Control MCP server...")
12
- run_server()
13
-
14
- if __name__ == "__main__":
15
- main()
1
+ """
2
+ Server module for Computer Control MCP.
3
+
4
+ This module provides a simple way to run the MCP server.
5
+ """
6
+
7
+ from computer_control_mcp.core import main as run_server
8
+
9
+ def main():
10
+ """Run the MCP server."""
11
+ print("Starting Computer Control MCP server...")
12
+ run_server()
13
+
14
+ if __name__ == "__main__":
15
+ main()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "computer-control-mcp"
7
- version = "0.3.7"
7
+ version = "0.3.8"
8
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.8"
@@ -31,7 +31,8 @@ dependencies = [
31
31
  "rapidocr_onnxruntime==1.2.3",
32
32
  "opencv-python==4.12.0.88",
33
33
  "python-Levenshtein>=0.20.9",
34
- "mss>=7.0.0"
34
+ "mss>=7.0.0",
35
+ "windows-capture>=1.0.0; sys_platform=='win32'"
35
36
  ]
36
37
 
37
38
  [project.urls]