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 +66 -18
- windows_mcp/desktop/service.py +189 -101
- windows_mcp/desktop/views.py +32 -15
- windows_mcp/tree/service.py +29 -29
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +9 -9
- windows_mcp/uia/controls.py +0 -42
- windows_mcp/uia/core.py +19 -929
- windows_mcp/vdm/core.py +147 -135
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/METADATA +6 -5
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/RECORD +14 -14
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/WHEEL +0 -0
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/licenses/LICENSE.md +0 -0
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' (
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
116
|
-
{
|
|
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=
|
|
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'
|
|
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=
|
|
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
|
|
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()
|