windows-mcp 0.6.0__py3-none-any.whl → 0.6.2__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
@@ -56,7 +56,7 @@ mcp=FastMCP(name='windows-mcp',instructions=instructions,lifespan=lifespan)
56
56
 
57
57
  @mcp.tool(
58
58
  name="App",
59
- description="Manages Windows applications with three modes: 'launch' (start app by name), 'resize' (set window position/size using window_loc=[x,y] and window_size=[width,height]), 'switch' (activate app by name). Essential for application lifecycle management.",
59
+ description="Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts active window size/position), 'switch' (brings specific window into focus).",
60
60
  annotations=ToolAnnotations(
61
61
  title="App",
62
62
  readOnlyHint=False,
@@ -87,7 +87,7 @@ def powershell_tool(command: str,timeout:int=10, ctx: Context = None) -> str:
87
87
 
88
88
  @mcp.tool(
89
89
  name='Snapshot',
90
- description='Captures complete desktop state including: system language, focused/opened apps, interactive elements (buttons, text fields, links, menus with coordinates), and scrollable areas. Set use_vision=True to include screenshot. Set use_dom=True for browser content to get web page elements instead of browser UI. Always call this first to understand the current desktop state before taking actions.',
90
+ description='Captures complete desktop state including: system language, focused/opened windows, interactive elements (buttons, text fields, links, menus with coordinates), and scrollable areas. Set use_vision=True to include screenshot. Set use_dom=True for browser content to get web page elements instead of browser UI. Always call this first to understand the current desktop state before taking actions.',
91
91
  annotations=ToolAnnotations(
92
92
  title="Snapshot",
93
93
  readOnlyHint=True,
@@ -97,7 +97,10 @@ def powershell_tool(command: str,timeout:int=10, ctx: Context = None) -> str:
97
97
  )
98
98
  )
99
99
  @with_analytics(analytics, "State-Tool")
100
- def state_tool(use_vision:bool=False,use_dom:bool=False, ctx: Context = None):
100
+ def state_tool(use_vision:bool|str=False,use_dom:bool|str=False, ctx: Context = None):
101
+ use_vision = use_vision is True or (isinstance(use_vision, str) and use_vision.lower() == 'true')
102
+ use_dom = use_dom is True or (isinstance(use_dom, str) and use_dom.lower() == 'true')
103
+
101
104
  # Calculate scale factor to cap resolution at 1080p (1920x1080)
102
105
  scale_width = MAX_IMAGE_WIDTH / screen_size.width if screen_size.width > MAX_IMAGE_WIDTH else 1.0
103
106
  scale_height = MAX_IMAGE_HEIGHT / screen_size.height if screen_size.height > MAX_IMAGE_HEIGHT else 1.0
@@ -106,14 +109,22 @@ def state_tool(use_vision:bool=False,use_dom:bool=False, ctx: Context = None):
106
109
  desktop_state=desktop.get_state(use_vision=use_vision,use_dom=use_dom,as_bytes=True,scale=scale)
107
110
  interactive_elements=desktop_state.tree_state.interactive_elements_to_string()
108
111
  scrollable_elements=desktop_state.tree_state.scrollable_elements_to_string()
109
- apps=desktop_state.apps_to_string()
110
- active_app=desktop_state.active_app_to_string()
111
- return [dedent(f'''
112
- Focused App:
113
- {active_app}
112
+ windows=desktop_state.windows_to_string()
113
+ active_window=desktop_state.active_window_to_string()
114
+ active_desktop=desktop_state.active_desktop_to_string()
115
+ all_desktops=desktop_state.desktops_to_string()
116
+ return [dedent(f'''
117
+ Active Desktop:
118
+ {active_desktop}
119
+
120
+ All Desktops:
121
+ {all_desktops}
122
+
123
+ Focused Window:
124
+ {active_window}
114
125
 
115
- Opened Apps:
116
- {apps}
126
+ Opened Windows:
127
+ {windows}
117
128
 
118
129
  List of Interactive Elements:
119
130
  {interactive_elements or 'No interactive elements found.'}
@@ -124,7 +135,7 @@ def state_tool(use_vision:bool=False,use_dom:bool=False, ctx: Context = None):
124
135
 
125
136
  @mcp.tool(
126
137
  name='Click',
127
- description='Performs mouse clicks at specified coordinates [x, y]. Supports button types: left (default), right (context menu), middle. Supports clicks: 1 (single), 2 (double), 3 (triple). Always use coordinates from State-Tool output to ensure accuracy.',
138
+ description="Performs mouse clicks at specified coordinates [x, y]. Supports button types: 'left' for selection/activation, 'right' for context menus, 'middle'. Supports clicks: 0=hover only (no click), 1=single click (select/focus), 2=double click (open/activate).",
128
139
  annotations=ToolAnnotations(
129
140
  title="Click",
130
141
  readOnlyHint=False,
@@ -139,12 +150,12 @@ def click_tool(loc:list[int],button:Literal['left','right','middle']='left',clic
139
150
  raise ValueError("Location must be a list of exactly 2 integers [x, y]")
140
151
  x,y=loc[0],loc[1]
141
152
  desktop.click(loc=loc,button=button,clicks=clicks)
142
- num_clicks={1:'Single',2:'Double',3:'Triple'}
153
+ num_clicks={0:'Hover',1:'Single',2:'Double'}
143
154
  return f'{num_clicks.get(clicks)} {button} clicked at ({x},{y}).'
144
155
 
145
156
  @mcp.tool(
146
157
  name='Type',
147
- description='Types text at specified coordinates [x, y]. Set clear=True to clear existing text first (Ctrl+A then type), clear=False to append. Set press_enter=True to submit after typing. Always click on the target input field first to ensure focus.',
158
+ description="Types text at specified coordinates [x, y]. Set clear=True to clear existing text first, False to append. Set press_enter=True to submit after typing. Set caret_position to 'start' (beginning), 'end' (end), or 'idle' (default).",
148
159
  annotations=ToolAnnotations(
149
160
  title="Type",
150
161
  readOnlyHint=False,
@@ -154,11 +165,11 @@ def click_tool(loc:list[int],button:Literal['left','right','middle']='left',clic
154
165
  )
155
166
  )
156
167
  @with_analytics(analytics, "Type-Tool")
157
- def type_tool(loc:list[int],text:str,clear:bool=False,press_enter:bool=False, ctx: Context = None)->str:
168
+ def type_tool(loc:list[int],text:str,clear:bool|str=False,caret_position:Literal['start', 'idle', 'end']='idle',press_enter:bool|str=False, ctx: Context = None)->str:
158
169
  if len(loc) != 2:
159
170
  raise ValueError("Location must be a list of exactly 2 integers [x, y]")
160
171
  x,y=loc[0],loc[1]
161
- desktop.type(loc=loc,text=text,clear=clear,press_enter=press_enter)
172
+ desktop.type(loc=loc,text=text,caret_position=caret_position,clear=clear,press_enter=press_enter)
162
173
  return f'Typed {text} at ({x},{y}).'
163
174
 
164
175
  @mcp.tool(
@@ -193,7 +204,8 @@ def scroll_tool(loc:list[int]=None,type:Literal['horizontal','vertical']='vertic
193
204
  )
194
205
  )
195
206
  @with_analytics(analytics, "Move-Tool")
196
- def move_tool(loc:list[int], drag:bool=False, ctx: Context = None)->str:
207
+ def move_tool(loc:list[int], drag:bool|str=False, ctx: Context = None)->str:
208
+ drag = drag is True or (isinstance(drag, str) and drag.lower() == 'true')
197
209
  if len(loc) != 2:
198
210
  raise ValueError("loc must be a list of exactly 2 integers [x, y]")
199
211
  x,y=loc[0],loc[1]
@@ -248,7 +260,8 @@ def wait_tool(duration:int, ctx: Context = None)->str:
248
260
  )
249
261
  )
250
262
  @with_analytics(analytics, "Scrape-Tool")
251
- def scrape_tool(url:str,use_dom:bool=False, ctx: Context = None)->str:
263
+ def scrape_tool(url:str,use_dom:bool|str=False, ctx: Context = None)->str:
264
+ use_dom = use_dom is True or (isinstance(use_dom, str) and use_dom.lower() == 'true')
252
265
  if not use_dom:
253
266
  content=desktop.scrape(url)
254
267
  return f'URL:{url}\nContent:\n{content}'
@@ -262,7 +275,42 @@ def scrape_tool(url:str,use_dom:bool=False, ctx: Context = None)->str:
262
275
  content='\n'.join([node.text for node in tree_state.dom_informative_nodes])
263
276
  header_status = "Reached top" if vertical_scroll_percent <= 0 else "Scroll up to see more"
264
277
  footer_status = "Reached bottom" if vertical_scroll_percent >= 100 else "Scroll down to see more"
265
- return f'URL:{url}\nContent:\n[{header_status}]\n{content}\n[{footer_status}]'
278
+ return f'URL:{url}\nContent:\n{header_status}\n{content}\n{footer_status}'
279
+
280
+ @mcp.tool(
281
+ name='MultiSelect',
282
+ description="Selects multiple items such as files, folders, or checkboxes if press_ctrl=True, or performs multiple clicks if False.",
283
+ annotations=ToolAnnotations(
284
+ title="MultiSelect",
285
+ readOnlyHint=False,
286
+ destructiveHint=True,
287
+ idempotentHint=False,
288
+ openWorldHint=False
289
+ )
290
+ )
291
+ @with_analytics(analytics, "Multi-Select-Tool")
292
+ def multi_select_tool(locs:list[list[int]], press_ctrl:bool|str=True, ctx: Context = None)->str:
293
+ press_ctrl = press_ctrl is True or (isinstance(press_ctrl, str) and press_ctrl.lower() == 'true')
294
+ desktop.multi_select(press_ctrl,locs)
295
+ elements_str = '\n'.join([f"({loc[0]},{loc[1]})" for loc in locs])
296
+ return f"Multi-selected elements at:\n{elements_str}"
297
+
298
+ @mcp.tool(
299
+ name='MultiEdit',
300
+ description="Enters text into multiple input fields at specified coordinates [[x,y,text], ...].",
301
+ annotations=ToolAnnotations(
302
+ title="MultiEdit",
303
+ readOnlyHint=False,
304
+ destructiveHint=True,
305
+ idempotentHint=False,
306
+ openWorldHint=False
307
+ )
308
+ )
309
+ @with_analytics(analytics, "Multi-Edit-Tool")
310
+ def multi_edit_tool(locs:list[list], ctx: Context = None)->str:
311
+ desktop.multi_edit(locs)
312
+ elements_str = ', '.join([f"({e[0]},{e[1]}) with text '{e[2]}'" for e in locs])
313
+ return f"Multi-edited elements at: {elements_str}"
266
314
 
267
315
 
268
316
  @click.command()