pctr 0.3.2__tar.gz → 0.3.4__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,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pctr
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
5
  Author: Space-lab515
6
6
  License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Space-lab515/pctr
8
+ Project-URL: Repository, https://github.com/Space-lab515/pctr
9
+ Project-URL: Issues, https://github.com/Space-lab515/pctr/issues
7
10
  Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
11
  Classifier: Development Status :: 4 - Beta
9
12
  Classifier: Environment :: Win32 (MS Windows)
@@ -26,6 +29,8 @@ Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
29
  Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
30
  Provides-Extra: vision
28
31
  Requires-Dist: ultralytics>=8.2; extra == "vision"
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp>=2; extra == "mcp"
29
34
  Dynamic: license-file
30
35
 
31
36
  # pctr
@@ -50,6 +55,28 @@ pip install "pctr[vision]" # adds ultralytics for `pctr look`
50
55
 
51
56
  Windows only.
52
57
 
58
+ ## MCP server
59
+
60
+ `pctr` ships a Model Context Protocol server so MCP clients (Claude Desktop,
61
+ Cursor, opencode, ...) can drive the Windows desktop directly:
62
+
63
+ ```bash
64
+ pip install "pctr[mcp]"
65
+ ```
66
+
67
+ Add it to your client config (stdio):
68
+
69
+ ```json
70
+ { "mcpServers": { "pctr": { "command": "pctr-mcp" } } }
71
+ ```
72
+
73
+ (or `"command": "python", "args": ["-m", "pctr.mcp_server"]`)
74
+
75
+ Tools exposed: `pctr_windows`, `pctr_tree`, `pctr_find`, `pctr_click`,
76
+ `pctr_set`, `pctr_type`, `pctr_keys`, `pctr_hotkey`, `pctr_focus`, `pctr_wait`,
77
+ `pctr_shot`, `pctr_ocr`, `pctr_ocrfind`, `pctr_ocrclick`, `pctr_look`,
78
+ `pctr_desktop`.
79
+
53
80
  ## Commands
54
81
 
55
82
  | Command | What it does |
@@ -20,6 +20,28 @@ pip install "pctr[vision]" # adds ultralytics for `pctr look`
20
20
 
21
21
  Windows only.
22
22
 
23
+ ## MCP server
24
+
25
+ `pctr` ships a Model Context Protocol server so MCP clients (Claude Desktop,
26
+ Cursor, opencode, ...) can drive the Windows desktop directly:
27
+
28
+ ```bash
29
+ pip install "pctr[mcp]"
30
+ ```
31
+
32
+ Add it to your client config (stdio):
33
+
34
+ ```json
35
+ { "mcpServers": { "pctr": { "command": "pctr-mcp" } } }
36
+ ```
37
+
38
+ (or `"command": "python", "args": ["-m", "pctr.mcp_server"]`)
39
+
40
+ Tools exposed: `pctr_windows`, `pctr_tree`, `pctr_find`, `pctr_click`,
41
+ `pctr_set`, `pctr_type`, `pctr_keys`, `pctr_hotkey`, `pctr_focus`, `pctr_wait`,
42
+ `pctr_shot`, `pctr_ocr`, `pctr_ocrfind`, `pctr_ocrclick`, `pctr_look`,
43
+ `pctr_desktop`.
44
+
23
45
  ## Commands
24
46
 
25
47
  | Command | What it does |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pctr"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -34,9 +34,16 @@ dependencies = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  vision = ["ultralytics>=8.2"]
37
+ mcp = ["mcp>=2"]
37
38
 
38
39
  [project.scripts]
39
40
  pctr = "pctr.cli:main"
41
+ pctr-mcp = "pctr.mcp_server:main"
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/Space-lab515/pctr"
45
+ Repository = "https://github.com/Space-lab515/pctr"
46
+ Issues = "https://github.com/Space-lab515/pctr/issues"
40
47
 
41
48
  [tool.setuptools.packages.find]
42
49
  where = ["src"]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.4"
@@ -0,0 +1,205 @@
1
+ """pctr MCP server - exposes pctr's Windows UI automation as MCP tools.
2
+
3
+ Run over stdio (default). Add it to an MCP client (Claude Desktop, Cursor,
4
+ opencode, ...) as:
5
+
6
+ { "command": "pctr-mcp" }
7
+
8
+ or:
9
+
10
+ { "command": "python", "args": ["-m", "pctr.mcp_server"] }
11
+
12
+ Needs the MCP SDK: pip install "pctr[mcp]"
13
+ """
14
+
15
+ import subprocess
16
+ import sys
17
+
18
+ try:
19
+ from mcp.server.mcpserver import MCPServer
20
+ except Exception as e: # pragma: no cover
21
+ raise SystemExit('pctr-mcp needs the MCP SDK: pip install "pctr[mcp]" (%s)' % e)
22
+
23
+ try:
24
+ from pctr import __version__ as _VERSION
25
+ except Exception: # pragma: no cover
26
+ _VERSION = ""
27
+
28
+ server = MCPServer(
29
+ "pctr",
30
+ version=_VERSION or "0.3.3",
31
+ instructions=(
32
+ "Element-based Windows UI automation. Find controls by name / control "
33
+ "type / automation id instead of pixel coordinates, then click/type/etc. "
34
+ "Also: OCR (ocr/ocrfind/ocrclick), YOLO-World (look), and virtual desktops "
35
+ "(desktop). Windows only."
36
+ ),
37
+ )
38
+
39
+
40
+ def _run(args):
41
+ """Run the pctr CLI and return stdout (+ stderr and exit code on failure)."""
42
+ cmd = [sys.executable, "-m", "pctr"] + [str(a) for a in args]
43
+ p = subprocess.run(cmd, capture_output=True, text=True)
44
+ out = (p.stdout or "").strip()
45
+ err = (p.stderr or "").strip()
46
+ if p.returncode != 0:
47
+ parts = ["exit=%d" % p.returncode]
48
+ if out:
49
+ parts.append(out)
50
+ if err:
51
+ parts.append(err)
52
+ return "\n".join(parts)
53
+ return out or "(no output)"
54
+
55
+
56
+ def _selectors(title=None, process=None, name=None, control_type=None, auto_id=None, nth=None):
57
+ a = []
58
+ if title:
59
+ a += ["--title", title]
60
+ if process is not None:
61
+ a += ["--process", str(process)]
62
+ if name:
63
+ a += ["--name", name]
64
+ if control_type:
65
+ a += ["--control-type", control_type]
66
+ if auto_id:
67
+ a += ["--auto-id", auto_id]
68
+ if nth is not None:
69
+ a += ["--nth", str(nth)]
70
+ return a
71
+
72
+
73
+ @server.tool(description="List top-level windows (pid | title). Filter by --filter regex or process id.")
74
+ def pctr_windows(filter: str | None = None, process: int | None = None) -> str:
75
+ a = []
76
+ if filter:
77
+ a += ["--filter", filter]
78
+ if process is not None:
79
+ a += ["--process", str(process)]
80
+ return _run(["windows"] + a)
81
+
82
+
83
+ @server.tool(description="Dump the UIA control tree of a window. Provide --title (or process). Optional depth/limit.")
84
+ def pctr_tree(title: str | None = None, process: int | None = None, depth: int | None = None, limit: int | None = None) -> str:
85
+ a = ["tree"] + _selectors(title=title, process=process)
86
+ if depth is not None:
87
+ a += ["--depth", str(depth)]
88
+ if limit is not None:
89
+ a += ["--limit", str(limit)]
90
+ return _run(a)
91
+
92
+
93
+ @server.tool(description="Find elements in a window and print name/type/id/rect. Any selector works; tree/find need title or process.")
94
+ def pctr_find(title: str | None = None, process: int | None = None, name: str | None = None,
95
+ control_type: str | None = None, auto_id: str | None = None,
96
+ nth: int | None = None, limit: int | None = None) -> str:
97
+ a = ["find"] + _selectors(title=title, process=process, name=name,
98
+ control_type=control_type, auto_id=auto_id, nth=nth)
99
+ if limit is not None:
100
+ a += ["--limit", str(limit)]
101
+ return _run(a)
102
+
103
+
104
+ @server.tool(description="Click an element. method: auto|invoke|mouse (invoke works cross-desktop).")
105
+ def pctr_click(title: str | None = None, process: int | None = None, name: str | None = None,
106
+ control_type: str | None = None, auto_id: str | None = None, nth: int | None = None,
107
+ method: str = "auto", button: str = "left", dbl: bool = False) -> str:
108
+ a = ["click"] + _selectors(title=title, process=process, name=name,
109
+ control_type=control_type, auto_id=auto_id, nth=nth)
110
+ a += ["--method", method, "--button", button]
111
+ if dbl:
112
+ a.append("--dbl")
113
+ return _run(a)
114
+
115
+
116
+ @server.tool(description="Set a field's text via the UIA ValuePattern (exact; prefer over type).")
117
+ def pctr_set(text: str, title: str | None = None, process: int | None = None, name: str | None = None,
118
+ control_type: str | None = None, auto_id: str | None = None, nth: int | None = None) -> str:
119
+ a = ["set"] + _selectors(title=title, process=process, name=name,
120
+ control_type=control_type, auto_id=auto_id, nth=nth)
121
+ a += ["--text", text]
122
+ return _run(a)
123
+
124
+
125
+ @server.tool(description="Send keystrokes (global or to an element). NOTE: pywinauto key syntax - { } + ^ % ~ ( ) are interpreted, not literal.")
126
+ def pctr_type(text: str, title: str | None = None, name: str | None = None,
127
+ delay: float = 0.03, chunk: int = 1) -> str:
128
+ a = ["type", "--text", text, "--delay", str(delay), "--chunk", str(chunk)]
129
+ if title:
130
+ a += ["--title", title]
131
+ if name:
132
+ a += ["--name", name]
133
+ return _run(a)
134
+
135
+
136
+ @server.tool(description="Send a global key combo in pywinauto syntax, e.g. {ENTER}. Prefer hotkey for modifiers.")
137
+ def pctr_keys(keys: str) -> str:
138
+ return _run(["keys", "--keys", keys])
139
+
140
+
141
+ @server.tool(description="Send a modifier combo, e.g. ctrl+s, win+shift+s.")
142
+ def pctr_hotkey(keys: str) -> str:
143
+ return _run(["hotkey", "--keys", keys])
144
+
145
+
146
+ @server.tool(description="Bring a window to the foreground.")
147
+ def pctr_focus(title: str | None = None, process: int | None = None) -> str:
148
+ return _run(["focus"] + _selectors(title=title, process=process))
149
+
150
+
151
+ @server.tool(description="Wait until an element exists.")
152
+ def pctr_wait(title: str | None = None, process: int | None = None, name: str | None = None,
153
+ control_type: str | None = None, auto_id: str | None = None, timeout: float = 10.0) -> str:
154
+ a = ["wait"] + _selectors(title=title, process=process, name=name,
155
+ control_type=control_type, auto_id=auto_id)
156
+ a += ["--timeout", str(timeout)]
157
+ return _run(a)
158
+
159
+
160
+ @server.tool(description="Screenshot the screen (or a window) to a file path.")
161
+ def pctr_shot(out: str, title: str | None = None, process: int | None = None) -> str:
162
+ a = ["shot", "--out", out] + _selectors(title=title, process=process)
163
+ return _run(a)
164
+
165
+
166
+ @server.tool(description="OCR the screen (or a window); list recognised words with boxes.")
167
+ def pctr_ocr(title: str | None = None, process: int | None = None) -> str:
168
+ return _run(["ocr"] + _selectors(title=title, process=process))
169
+
170
+
171
+ @server.tool(description="OCR then list words matching --text (single words).")
172
+ def pctr_ocrfind(text: str, title: str | None = None, process: int | None = None) -> str:
173
+ return _run(["ocrfind", "--text", text] + _selectors(title=title, process=process))
174
+
175
+
176
+ @server.tool(description="OCR then click the word matching --text.")
177
+ def pctr_ocrclick(text: str, title: str | None = None, process: int | None = None, nth: int = 0) -> str:
178
+ return _run(["ocrclick", "--text", text, "--nth", str(nth)] + _selectors(title=title, process=process))
179
+
180
+
181
+ @server.tool(description="YOLO-World open-vocabulary detection by text prompt (e.g. 'a blue folder icon').")
182
+ def pctr_look(for_: str, title: str | None = None, process: int | None = None, conf: float | None = None) -> str:
183
+ a = ["look", "--for", for_] + _selectors(title=title, process=process)
184
+ if conf is not None:
185
+ a += ["--conf", str(conf)]
186
+ return _run(a)
187
+
188
+
189
+ @server.tool(description="Virtual desktops: action = list | windows | where | on-current | next | prev | new | close | move-window.")
190
+ def pctr_desktop(action: str, title: str | None = None, process: int | None = None,
191
+ desktop: int | None = None, to: int | None = None) -> str:
192
+ a = ["desktop", action] + _selectors(title=title, process=process)
193
+ if desktop is not None:
194
+ a += ["--desktop", str(desktop)]
195
+ if to is not None:
196
+ a += ["--to", str(to)]
197
+ return _run(a)
198
+
199
+
200
+ def main():
201
+ server.run(transport="stdio")
202
+
203
+
204
+ if __name__ == "__main__":
205
+ main()
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pctr
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Element-based Windows UI Automation CLI for AI agents. Find controls by name/type/id and click, type, hold, drag, and screenshot - no pixel coordinates.
5
5
  Author: Space-lab515
6
6
  License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Space-lab515/pctr
8
+ Project-URL: Repository, https://github.com/Space-lab515/pctr
9
+ Project-URL: Issues, https://github.com/Space-lab515/pctr/issues
7
10
  Keywords: automation,windows,uia,pywinauto,desktop,agent,computer-use,rpa
8
11
  Classifier: Development Status :: 4 - Beta
9
12
  Classifier: Environment :: Win32 (MS Windows)
@@ -26,6 +29,8 @@ Requires-Dist: pydirectinput>=1.0.4; platform_system == "Windows"
26
29
  Requires-Dist: pillow>=9.0; platform_system == "Windows"
27
30
  Provides-Extra: vision
28
31
  Requires-Dist: ultralytics>=8.2; extra == "vision"
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp>=2; extra == "mcp"
29
34
  Dynamic: license-file
30
35
 
31
36
  # pctr
@@ -50,6 +55,28 @@ pip install "pctr[vision]" # adds ultralytics for `pctr look`
50
55
 
51
56
  Windows only.
52
57
 
58
+ ## MCP server
59
+
60
+ `pctr` ships a Model Context Protocol server so MCP clients (Claude Desktop,
61
+ Cursor, opencode, ...) can drive the Windows desktop directly:
62
+
63
+ ```bash
64
+ pip install "pctr[mcp]"
65
+ ```
66
+
67
+ Add it to your client config (stdio):
68
+
69
+ ```json
70
+ { "mcpServers": { "pctr": { "command": "pctr-mcp" } } }
71
+ ```
72
+
73
+ (or `"command": "python", "args": ["-m", "pctr.mcp_server"]`)
74
+
75
+ Tools exposed: `pctr_windows`, `pctr_tree`, `pctr_find`, `pctr_click`,
76
+ `pctr_set`, `pctr_type`, `pctr_keys`, `pctr_hotkey`, `pctr_focus`, `pctr_wait`,
77
+ `pctr_shot`, `pctr_ocr`, `pctr_ocrfind`, `pctr_ocrclick`, `pctr_look`,
78
+ `pctr_desktop`.
79
+
53
80
  ## Commands
54
81
 
55
82
  | Command | What it does |
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  src/pctr/__init__.py
5
5
  src/pctr/__main__.py
6
6
  src/pctr/cli.py
7
+ src/pctr/mcp_server.py
7
8
  src/pctr.egg-info/PKG-INFO
8
9
  src/pctr.egg-info/SOURCES.txt
9
10
  src/pctr.egg-info/dependency_links.txt
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  pctr = pctr.cli:main
3
+ pctr-mcp = pctr.mcp_server:main
@@ -6,5 +6,8 @@ pyautogui>=0.9.54
6
6
  pydirectinput>=1.0.4
7
7
  pillow>=9.0
8
8
 
9
+ [mcp]
10
+ mcp>=2
11
+
9
12
  [vision]
10
13
  ultralytics>=8.2
@@ -1 +0,0 @@
1
- __version__ = "0.3.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes