windows-mcp 0.5.4__tar.gz → 0.5.5__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.
Files changed (29) hide show
  1. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/.gitignore +1 -1
  2. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/PKG-INFO +1 -1
  3. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/pyproject.toml +1 -1
  4. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/server.json +1 -1
  5. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/__main__.py +4 -4
  6. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/tree/service.py +12 -42
  7. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/tree/views.py +8 -2
  8. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/uv.lock +1483 -1483
  9. windows_mcp-0.5.4/notebook.ipynb +0 -187
  10. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/.python-version +0 -0
  11. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/CONTRIBUTING.md +0 -0
  12. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/LICENSE.md +0 -0
  13. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/README.md +0 -0
  14. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/SECURITY.md +0 -0
  15. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/demo1.mov +0 -0
  16. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/demo2.mov +0 -0
  17. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/logo.png +0 -0
  18. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/screenshots/screenshot_1.png +0 -0
  19. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/screenshots/screenshot_2.png +0 -0
  20. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/assets/screenshots/screenshot_3.png +0 -0
  21. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/manifest.json +0 -0
  22. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/__init__.py +0 -0
  23. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/desktop/__init__.py +0 -0
  24. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/desktop/config.py +0 -0
  25. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/desktop/service.py +0 -0
  26. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/desktop/views.py +0 -0
  27. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/tree/__init__.py +0 -0
  28. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/tree/config.py +0 -0
  29. {windows_mcp-0.5.4 → windows_mcp-0.5.5}/src/windows_mcp/tree/utils.py +0 -0
@@ -165,5 +165,5 @@ cython_debug/
165
165
  .mcpregistry_github_token
166
166
  .mcpregistry_registry_token
167
167
  sandbox
168
- # *.ipynb
168
+ *.ipynb
169
169
  *.mcpb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windows-mcp
3
- Version: 0.5.4
3
+ Version: 0.5.5
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "windows-mcp"
3
- version = "0.5.4"
3
+ version = "0.5.5"
4
4
  description = "Lightweight MCP Server for interacting with Windows Operating System."
5
5
  authors = [
6
6
  { name = "Jeomon George", email = "jeogeoalukka@gmail.com" }
@@ -13,7 +13,7 @@
13
13
  "registry_type": "pypi",
14
14
  "registry_base_url": "https://pypi.org",
15
15
  "identifier": "windows_mcp",
16
- "version": "0.5.0",
16
+ "version": "0.5.4",
17
17
  "runtime_hint": "uvx",
18
18
  "transport": {
19
19
  "type": "stdio"
@@ -243,10 +243,10 @@ def wait_tool(duration:int)->str:
243
243
  def scrape_tool(url:str)->str:
244
244
  desktop_state=desktop.desktop_state
245
245
  tree_state=desktop_state.tree_state
246
- if not tree_state.dom_node:
247
- return f'Unable to scrape URL: {url}. No DOM node found.'
248
- dom_node=tree_state.dom
249
- vertical_scroll_percent=dom_node.vertical_scroll_percent
246
+ if not tree_state.dom_info:
247
+ return f'No DOM information found. Please open {url} in browser first.'
248
+ dom_info=tree_state.dom_info
249
+ vertical_scroll_percent=dom_info.vertical_scroll_percent
250
250
  content='\n'.join([node.text for node in tree_state.dom_informative_nodes])
251
251
  header_status = "Reached top" if vertical_scroll_percent <= 0 else "Scroll up to see more"
252
252
  footer_status = "Reached bottom" if vertical_scroll_percent >= 100 else "Scroll down to see more"
@@ -1,6 +1,6 @@
1
1
  from windows_mcp.tree.config import INTERACTIVE_CONTROL_TYPE_NAMES,DOCUMENT_CONTROL_TYPE_NAMES,INFORMATIVE_CONTROL_TYPE_NAMES, DEFAULT_ACTIONS, THREAD_MAX_RETRIES
2
+ from windows_mcp.tree.views import TreeElementNode, ScrollElementNode, TextElementNode, Center, BoundingBox, TreeState, DOMInfo
2
3
  from uiautomation import Control,ImageControl,ScrollPattern,WindowControl,Rect,GetRootControl,PatternId
3
- from windows_mcp.tree.views import TreeElementNode, ScrollElementNode, TextElementNode, Center, BoundingBox, TreeState
4
4
  from concurrent.futures import ThreadPoolExecutor, as_completed
5
5
  from windows_mcp.tree.utils import random_point_within_bounding_box
6
6
  from PIL import Image, ImageFont, ImageDraw
@@ -24,58 +24,22 @@ class Tree:
24
24
  def __init__(self,desktop:'Desktop'):
25
25
  self.desktop=desktop
26
26
  self.screen_size=self.desktop.get_screen_size()
27
- self.dom:Optional[Control]=None
27
+ self.dom_info:Optional[DOMInfo]=None
28
28
  self.dom_bounding_box:BoundingBox=None
29
29
  self.screen_box=BoundingBox(
30
30
  top=0, left=0, bottom=self.screen_size.height, right=self.screen_size.width,
31
31
  width=self.screen_size.width, height=self.screen_size.height
32
32
  )
33
- self.root:Optional[TreeElementNode]=None
34
33
 
35
34
  def get_state(self,active_app:App,other_apps:list[App],use_dom:bool=False)->TreeState:
36
- self.root=GetRootControl()
35
+ root=GetRootControl()
37
36
  other_apps_handle=set(map(lambda other_app: other_app.handle,other_apps))
38
- apps=list(filter(lambda app:app.NativeWindowHandle not in other_apps_handle,self.root.GetChildren()))
37
+ apps=list(filter(lambda app:app.NativeWindowHandle not in other_apps_handle,root.GetChildren()))
39
38
  del other_apps_handle
40
39
  if active_app:
41
40
  apps=list(filter(lambda app:app.ClassName!='Progman',apps))
42
41
  interactive_nodes,scrollable_nodes,dom_informative_nodes=self.get_appwise_nodes(apps=apps,use_dom=use_dom)
43
- root=TreeElementNode(**{
44
- 'name':'Desktop',
45
- 'control_type':'PaneControl',
46
- 'app_name':'Desktop',
47
- 'value':'',
48
- 'shortcut':'',
49
- 'bounding_box':self.screen_box,
50
- 'center':Center(x=self.screen_box.left+self.screen_box.width//2,y=self.screen_box.top+self.screen_box.height//2),
51
- 'xpath':'',
52
- 'is_focused':False
53
- })
54
- dom=None
55
- if self.dom:
56
- scroll_pattern=self.dom.GetPattern(PatternId.ScrollPattern)
57
- bounding_box=self.dom.BoundingRectangle
58
- dom=ScrollElementNode(**{
59
- 'name':"DOM",
60
- 'control_type':'DocumentControl',
61
- 'app_name':"DOM",
62
- 'bounding_box':BoundingBox(
63
- left=bounding_box.left,
64
- top=bounding_box.top,
65
- right=bounding_box.right,
66
- bottom=bounding_box.bottom,
67
- width=bounding_box.width(),
68
- height=bounding_box.height()
69
- ),
70
- 'center':Center(x=bounding_box.left+bounding_box.width()//2,y=bounding_box.top+bounding_box.height()//2),
71
- 'horizontal_scrollable':scroll_pattern.HorizontallyScrollable,
72
- 'horizontal_scroll_percent':scroll_pattern.HorizontalScrollPercent if scroll_pattern.HorizontallyScrollable else 0,
73
- 'vertical_scrollable':scroll_pattern.VerticallyScrollable,
74
- 'vertical_scroll_percent':scroll_pattern.VerticalScrollPercent if scroll_pattern.VerticallyScrollable else 0,
75
- 'xpath':'',
76
- 'is_focused':False
77
- })
78
- return TreeState(root=root,dom=dom,interactive_nodes=interactive_nodes,scrollable_nodes=scrollable_nodes,dom_informative_nodes=dom_informative_nodes)
42
+ return TreeState(dom_info=self.dom_info,interactive_nodes=interactive_nodes,scrollable_nodes=scrollable_nodes,dom_informative_nodes=dom_informative_nodes)
79
43
 
80
44
  def get_appwise_nodes(self,apps:list[Control],use_dom:bool=False)-> tuple[list[TreeElementNode],list[ScrollElementNode],list[TextElementNode]]:
81
45
  interactive_nodes, scrollable_nodes,dom_informative_nodes = [], [], []
@@ -388,7 +352,13 @@ class Tree:
388
352
  self.dom_bounding_box=BoundingBox(left=bounding_box.left,top=bounding_box.top,
389
353
  right=bounding_box.right,bottom=bounding_box.bottom,width=bounding_box.width(),
390
354
  height=bounding_box.height())
391
- self.dom=child
355
+ scroll_pattern=child.GetPattern(PatternId.ScrollPattern)
356
+ self.dom_info=DOMInfo(
357
+ horizontal_scrollable=scroll_pattern.HorizontallyScrollable,
358
+ horizontal_scroll_percent=scroll_pattern.HorizontalScrollPercent if scroll_pattern.HorizontallyScrollable else 0,
359
+ vertical_scrollable=scroll_pattern.VerticallyScrollable,
360
+ vertical_scroll_percent=scroll_pattern.VerticalScrollPercent if scroll_pattern.VerticallyScrollable else 0
361
+ )
392
362
  # enter DOM subtree
393
363
  tree_traversal(child, is_dom=True, is_dialog=is_dialog)
394
364
  # Check if the child is a dialog
@@ -2,13 +2,19 @@ from dataclasses import dataclass,field
2
2
  from tabulate import tabulate
3
3
  from typing import Optional
4
4
 
5
+ @dataclass
6
+ class DOMInfo:
7
+ horizontal_scrollable: bool
8
+ horizontal_scroll_percent: float
9
+ vertical_scrollable: bool
10
+ vertical_scroll_percent: float
11
+
5
12
  @dataclass
6
13
  class TreeState:
7
- root:Optional['TreeElementNode']=None
8
- dom:Optional['ScrollElementNode']=None
9
14
  interactive_nodes:list['TreeElementNode']=field(default_factory=list)
10
15
  scrollable_nodes:list['ScrollElementNode']=field(default_factory=list)
11
16
  dom_informative_nodes:list['TextElementNode']=field(default_factory=list)
17
+ dom_info:Optional['DOMInfo']=None
12
18
 
13
19
  def interactive_elements_to_string(self) -> str:
14
20
  if not self.interactive_nodes: