windows-mcp 0.5.6__py3-none-any.whl → 0.5.7__py3-none-any.whl

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.
windows_mcp/__main__.py CHANGED
@@ -6,9 +6,9 @@ from fastmcp.utilities.types import Image
6
6
  from mcp.types import ToolAnnotations
7
7
  from typing import Literal, Optional
8
8
  from humancursor import SystemCursor
9
+ from fastmcp import FastMCP, Context
9
10
  from dotenv import load_dotenv
10
11
  from textwrap import dedent
11
- from fastmcp import FastMCP
12
12
  import pyautogui as pg
13
13
  import asyncio
14
14
  import click
@@ -63,7 +63,7 @@ mcp=FastMCP(name='windows-mcp',instructions=instructions,lifespan=lifespan)
63
63
  )
64
64
  )
65
65
  @with_analytics(analytics, "App-Tool")
66
- def app_tool(mode:Literal['launch','resize','switch'],name:str|None=None,window_loc:list[int]|None=None,window_size:list[int]|None=None):
66
+ def app_tool(mode:Literal['launch','resize','switch'],name:str|None=None,window_loc:list[int]|None=None,window_size:list[int]|None=None, ctx: Context = None):
67
67
  return desktop.app(mode,name,window_loc,window_size)
68
68
 
69
69
  @mcp.tool(
@@ -78,7 +78,7 @@ def app_tool(mode:Literal['launch','resize','switch'],name:str|None=None,window_
78
78
  )
79
79
  )
80
80
  @with_analytics(analytics, "Powershell-Tool")
81
- def powershell_tool(command: str) -> str:
81
+ def powershell_tool(command: str, ctx: Context = None) -> str:
82
82
  response,status_code=desktop.execute_command(command)
83
83
  return f'Response: {response}\nStatus Code: {status_code}'
84
84
 
@@ -94,7 +94,7 @@ def powershell_tool(command: str) -> str:
94
94
  )
95
95
  )
96
96
  @with_analytics(analytics, "State-Tool")
97
- def state_tool(use_vision:bool=False,use_dom:bool=False):
97
+ def state_tool(use_vision:bool=False,use_dom:bool=False, ctx: Context = None):
98
98
  # Calculate scale factor to cap resolution at 1080p (1920x1080)
99
99
  max_width, max_height = 1920, 1080
100
100
  scale_width = max_width / screen_width if screen_width > max_width else 1.0
@@ -135,7 +135,7 @@ def state_tool(use_vision:bool=False,use_dom:bool=False):
135
135
  )
136
136
  )
137
137
  @with_analytics(analytics, "Click-Tool")
138
- def click_tool(loc:list[int],button:Literal['left','right','middle']='left',clicks:int=1)->str:
138
+ def click_tool(loc:list[int],button:Literal['left','right','middle']='left',clicks:int=1, ctx: Context = None)->str:
139
139
  if len(loc) != 2:
140
140
  raise ValueError("Location must be a list of exactly 2 integers [x, y]")
141
141
  x,y=loc[0],loc[1]
@@ -155,7 +155,7 @@ def click_tool(loc:list[int],button:Literal['left','right','middle']='left',clic
155
155
  )
156
156
  )
157
157
  @with_analytics(analytics, "Type-Tool")
158
- def type_tool(loc:list[int],text:str,clear:bool=False,press_enter:bool=False)->str:
158
+ def type_tool(loc:list[int],text:str,clear:bool=False,press_enter:bool=False, ctx: Context = None)->str:
159
159
  if len(loc) != 2:
160
160
  raise ValueError("Location must be a list of exactly 2 integers [x, y]")
161
161
  x,y=loc[0],loc[1]
@@ -174,7 +174,7 @@ def type_tool(loc:list[int],text:str,clear:bool=False,press_enter:bool=False)->s
174
174
  )
175
175
  )
176
176
  @with_analytics(analytics, "Scroll-Tool")
177
- def scroll_tool(loc:list[int]=None,type:Literal['horizontal','vertical']='vertical',direction:Literal['up','down','left','right']='down',wheel_times:int=1)->str:
177
+ def scroll_tool(loc:list[int]=None,type:Literal['horizontal','vertical']='vertical',direction:Literal['up','down','left','right']='down',wheel_times:int=1, ctx: Context = None)->str:
178
178
  if loc and len(loc) != 2:
179
179
  raise ValueError("Location must be a list of exactly 2 integers [x, y]")
180
180
  response=desktop.scroll(loc,type,direction,wheel_times)
@@ -194,7 +194,7 @@ def scroll_tool(loc:list[int]=None,type:Literal['horizontal','vertical']='vertic
194
194
  )
195
195
  )
196
196
  @with_analytics(analytics, "Drag-Tool")
197
- def drag_tool(to_loc:list[int])->str:
197
+ def drag_tool(to_loc:list[int], ctx: Context = None)->str:
198
198
  if len(to_loc) != 2:
199
199
  raise ValueError("to_loc must be a list of exactly 2 integers [x, y]")
200
200
  desktop.drag(to_loc)
@@ -213,7 +213,7 @@ def drag_tool(to_loc:list[int])->str:
213
213
  )
214
214
  )
215
215
  @with_analytics(analytics, "Move-Tool")
216
- def move_tool(to_loc:list[int])->str:
216
+ def move_tool(to_loc:list[int], ctx: Context = None)->str:
217
217
  if len(to_loc) != 2:
218
218
  raise ValueError("to_loc must be a list of exactly 2 integers [x, y]")
219
219
  x,y=to_loc[0],to_loc[1]
@@ -232,7 +232,7 @@ def move_tool(to_loc:list[int])->str:
232
232
  )
233
233
  )
234
234
  @with_analytics(analytics, "Shortcut-Tool")
235
- def shortcut_tool(shortcut:str):
235
+ def shortcut_tool(shortcut:str, ctx: Context = None):
236
236
  desktop.shortcut(shortcut)
237
237
  return f"Pressed {shortcut}."
238
238
 
@@ -248,7 +248,7 @@ def shortcut_tool(shortcut:str):
248
248
  )
249
249
  )
250
250
  @with_analytics(analytics, "Wait-Tool")
251
- def wait_tool(duration:int)->str:
251
+ def wait_tool(duration:int, ctx: Context = None)->str:
252
252
  pg.sleep(duration)
253
253
  return f'Waited for {duration} seconds.'
254
254
 
@@ -264,7 +264,7 @@ def wait_tool(duration:int)->str:
264
264
  )
265
265
  )
266
266
  @with_analytics(analytics, "Scrape-Tool")
267
- def scrape_tool(url:str,use_dom:bool=False)->str:
267
+ def scrape_tool(url:str,use_dom:bool=False, ctx: Context = None)->str:
268
268
  if not use_dom:
269
269
  content=desktop.scrape(url)
270
270
  return f'URL:{url}\nContent:\n{content}'
windows_mcp/analytics.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from typing import Optional, Dict, Any, TypeVar, Callable, Protocol, Awaitable
2
2
  from tempfile import TemporaryDirectory
3
3
  from uuid_extensions import uuid7str
4
+ from fastmcp import Context
4
5
  from functools import wraps
5
6
  from pathlib import Path
6
7
  import posthog
@@ -125,6 +126,21 @@ def with_analytics(analytics_instance: Optional[Analytics], tool_name: str):
125
126
  @wraps(func)
126
127
  async def wrapper(*args, **kwargs) -> T:
127
128
  start = time.time()
129
+
130
+ # Capture client info from Context passed as argument
131
+ client_data = {}
132
+ try:
133
+ ctx = next((arg for arg in args if isinstance(arg, Context)), None)
134
+ if not ctx:
135
+ ctx = next((val for val in kwargs.values() if isinstance(val, Context)), None)
136
+
137
+ if ctx and ctx.session and ctx.session.client_params and ctx.session.client_params.clientInfo:
138
+ info = ctx.session.client_params.clientInfo
139
+ client_data["client_name"] = info.name
140
+ client_data["client_version"] = info.version
141
+ except Exception:
142
+ pass
143
+
128
144
  try:
129
145
  if asyncio.iscoroutinefunction(func):
130
146
  result = await func(*args, **kwargs)
@@ -135,7 +151,11 @@ def with_analytics(analytics_instance: Optional[Analytics], tool_name: str):
135
151
  duration_ms = int((time.time() - start) * 1000)
136
152
 
137
153
  if analytics_instance:
138
- await analytics_instance.track_tool(tool_name, {"duration_ms": duration_ms, "success": True})
154
+ await analytics_instance.track_tool(tool_name, {
155
+ "duration_ms": duration_ms,
156
+ "success": True,
157
+ **client_data
158
+ })
139
159
 
140
160
  return result
141
161
  except Exception as error:
@@ -143,7 +163,8 @@ def with_analytics(analytics_instance: Optional[Analytics], tool_name: str):
143
163
  if analytics_instance:
144
164
  await analytics_instance.track_error(error, {
145
165
  "tool_name": tool_name,
146
- "duration_ms": duration_ms
166
+ "duration_ms": duration_ms,
167
+ **client_data
147
168
  })
148
169
  raise error
149
170
  return wrapper
@@ -53,7 +53,7 @@ class Desktop:
53
53
  sleep(0.1)
54
54
  apps=self.get_apps()
55
55
  active_app=self.get_active_app()
56
- if active_app is not None:
56
+ if active_app is not None and active_app in apps:
57
57
  apps.remove(active_app)
58
58
  logger.debug(f"Active app: {active_app}")
59
59
  logger.debug(f"Apps: {apps}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windows-mcp
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: Lightweight MCP Server for interacting with Windows Operating System.
5
5
  Project-URL: homepage, https://github.com/CursorTouch
6
6
  Author-email: Jeomon George <jeogeoalukka@gmail.com>
@@ -33,7 +33,7 @@ Requires-Dist: fastmcp>=2.8.1
33
33
  Requires-Dist: fuzzywuzzy>=0.18.0
34
34
  Requires-Dist: humancursor>=1.1.5
35
35
  Requires-Dist: ipykernel>=6.30.0
36
- Requires-Dist: live-inspect>=0.1.1
36
+ Requires-Dist: live-inspect>=0.1.2
37
37
  Requires-Dist: markdownify>=1.1.0
38
38
  Requires-Dist: pdfplumber>=0.11.7
39
39
  Requires-Dist: pillow>=11.2.1
@@ -387,7 +387,7 @@ Please read our [Security Policy](SECURITY.md).
387
387
 
388
388
  ## 📊 Telemetry
389
389
 
390
- Windows-MCP collects anonymized usage data to help improve the tool. No personal information, no tool arguments, no outputs are tracked.
390
+ Windows-MCP collects usage data to help improve the MCP server. No personal information, no tool arguments, no outputs are tracked.
391
391
 
392
392
  To disable telemetry, add the following to your MCP client configuration:
393
393
 
@@ -1,17 +1,17 @@
1
1
  windows_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- windows_mcp/__main__.py,sha256=fHuuwAakKCFV1-Wqc2vd079pu87mf3CYMuEINIA0CBA,13040
3
- windows_mcp/analytics.py,sha256=ZQmGlDJeB4pfHmRZXcPeFOxQh-LYrauBUqwEHOgS9Mg,5568
2
+ windows_mcp/__main__.py,sha256=TryfZevJMW53N3m8qcQXDqhcYu2KAGCbZ6ibrJjfS2o,13280
3
+ windows_mcp/analytics.py,sha256=kXQ2MEUaUnZUvq0nfm03YIN0kHcSq2z2dNFcFirO5Kc,6455
4
4
  windows_mcp/desktop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  windows_mcp/desktop/config.py,sha256=7rAb64pmC275PpNRXVOyOf0Psu089AOosRC8T5kVGWA,384
6
- windows_mcp/desktop/service.py,sha256=hJqTMmo7utBRRdtmVUxaWXMw8XyBD6Pc1Y6tCk6YAvU,18463
6
+ windows_mcp/desktop/service.py,sha256=gJO46Hs508tJrGKOaaFkx9SxtPhEucboI3eXeI3uro0,18486
7
7
  windows_mcp/desktop/views.py,sha256=_hZ5sfY1uWVi5mpaysVd-plwP_DT6SXpKa33Z8WT6gI,1523
8
8
  windows_mcp/tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  windows_mcp/tree/config.py,sha256=k-Mjo_yIn0d1AzcEW_bxiaXyBFxBZZSyy7hCNQ3XVp0,1010
10
10
  windows_mcp/tree/service.py,sha256=evK62AwhMwifpq6lRQCdrmC4DPt1-w_HSp8nUwXsCVQ,23566
11
11
  windows_mcp/tree/utils.py,sha256=6hbxdIQPrAY-I3jcHsRqodHlxboTQj2GnLA71bf1lqY,911
12
12
  windows_mcp/tree/views.py,sha256=K2hTBDicjP4p_tPIRTLZ8Sq3pGYhsDtZVIROAnMGTz4,3599
13
- windows_mcp-0.5.6.dist-info/METADATA,sha256=ENARm5l-oAjYUB88yjWCWZIH7_5qN4xPd1ceUh50y5o,14024
14
- windows_mcp-0.5.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- windows_mcp-0.5.6.dist-info/entry_points.txt,sha256=wW8NcVQ_OJK5e5GemZSE_nOKyxfUtBPq2acFLszRwaw,58
16
- windows_mcp-0.5.6.dist-info/licenses/LICENSE.md,sha256=U1UM4Xi_IX-jHnHjGT0rETNia-Ck8gd92iSQMqQ6a8Y,1089
17
- windows_mcp-0.5.6.dist-info/RECORD,,
13
+ windows_mcp-0.5.7.dist-info/METADATA,sha256=xbvl6qR69-KSMLqjga6gutc8goRD4IQ7jm_zyKPT2Ko,14019
14
+ windows_mcp-0.5.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ windows_mcp-0.5.7.dist-info/entry_points.txt,sha256=wW8NcVQ_OJK5e5GemZSE_nOKyxfUtBPq2acFLszRwaw,58
16
+ windows_mcp-0.5.7.dist-info/licenses/LICENSE.md,sha256=U1UM4Xi_IX-jHnHjGT0rETNia-Ck8gd92iSQMqQ6a8Y,1089
17
+ windows_mcp-0.5.7.dist-info/RECORD,,