windows-mcp 0.5.7__py3-none-any.whl → 0.5.8__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 +314 -312
- windows_mcp/analytics.py +175 -171
- windows_mcp/desktop/config.py +20 -20
- windows_mcp/desktop/service.py +457 -457
- windows_mcp/desktop/views.py +57 -57
- windows_mcp/tree/config.py +50 -50
- windows_mcp/tree/service.py +600 -466
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +115 -115
- windows_mcp/uia/__init__.py +4 -0
- windows_mcp/uia/controls.py +4781 -0
- windows_mcp/uia/core.py +3269 -0
- windows_mcp/uia/enums.py +1963 -0
- windows_mcp/uia/events.py +83 -0
- windows_mcp/uia/patterns.py +2106 -0
- windows_mcp/watchdog/__init__.py +1 -0
- windows_mcp/watchdog/event_handlers.py +51 -0
- windows_mcp/watchdog/service.py +188 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/METADATA +4 -4
- windows_mcp-0.5.8.dist-info/RECORD +26 -0
- windows_mcp-0.5.7.dist-info/RECORD +0 -17
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/WHEEL +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/analytics.py
CHANGED
|
@@ -1,171 +1,175 @@
|
|
|
1
|
-
from typing import Optional, Dict, Any, TypeVar, Callable, Protocol, Awaitable
|
|
2
|
-
from tempfile import TemporaryDirectory
|
|
3
|
-
from uuid_extensions import uuid7str
|
|
4
|
-
from fastmcp import Context
|
|
5
|
-
from functools import wraps
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import posthog
|
|
8
|
-
import asyncio
|
|
9
|
-
import logging
|
|
10
|
-
import time
|
|
11
|
-
import os
|
|
12
|
-
|
|
13
|
-
logging.
|
|
14
|
-
logger
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
async def
|
|
24
|
-
"""Tracks
|
|
25
|
-
...
|
|
26
|
-
|
|
27
|
-
async def
|
|
28
|
-
"""
|
|
29
|
-
...
|
|
30
|
-
|
|
31
|
-
async def
|
|
32
|
-
"""
|
|
33
|
-
...
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if
|
|
61
|
-
self._user_id
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if self.client:
|
|
94
|
-
self.client.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1
|
+
from typing import Optional, Dict, Any, TypeVar, Callable, Protocol, Awaitable
|
|
2
|
+
from tempfile import TemporaryDirectory
|
|
3
|
+
from uuid_extensions import uuid7str
|
|
4
|
+
from fastmcp import Context
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import posthog
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
logger.setLevel(logging.INFO)
|
|
15
|
+
handler = logging.StreamHandler()
|
|
16
|
+
formatter = logging.Formatter('[%(levelname)s] %(message)s')
|
|
17
|
+
handler.setFormatter(formatter)
|
|
18
|
+
logger.addHandler(handler)
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
class Analytics(Protocol):
|
|
23
|
+
async def track_tool(self, tool_name: str, result: Dict[str, Any]) -> None:
|
|
24
|
+
"""Tracks the execution of a tool."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def track_error(self, error: Exception, context: Dict[str, Any]) -> None:
|
|
28
|
+
"""Tracks an error that occurred during the execution of a tool."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
async def is_feature_enabled(self, feature: str) -> bool:
|
|
32
|
+
"""Checks if a feature flag is enabled."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
async def close(self) -> None:
|
|
36
|
+
"""Closes the analytics client."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
class PostHogAnalytics:
|
|
40
|
+
TEMP_FOLDER = Path(TemporaryDirectory().name).parent
|
|
41
|
+
API_KEY = 'phc_uxdCItyVTjXNU0sMPr97dq3tcz39scQNt3qjTYw5vLV'
|
|
42
|
+
HOST = 'https://us.i.posthog.com'
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self.client = posthog.Posthog(
|
|
46
|
+
self.API_KEY,
|
|
47
|
+
host=self.HOST,
|
|
48
|
+
disable_geoip=False,
|
|
49
|
+
enable_exception_autocapture=True,
|
|
50
|
+
debug=False
|
|
51
|
+
)
|
|
52
|
+
self._user_id = None
|
|
53
|
+
self.mcp_interaction_id = f"mcp_{int(time.time()*1000)}_{os.getpid()}"
|
|
54
|
+
|
|
55
|
+
if self.client:
|
|
56
|
+
logger.debug(f"Initialized with user ID: {self.user_id} and session ID: {self.mcp_interaction_id}")
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def user_id(self) -> str:
|
|
60
|
+
if self._user_id:
|
|
61
|
+
return self._user_id
|
|
62
|
+
|
|
63
|
+
user_id_file = self.TEMP_FOLDER / '.windows-mcp-user-id'
|
|
64
|
+
if user_id_file.exists():
|
|
65
|
+
self._user_id = user_id_file.read_text(encoding='utf-8').strip()
|
|
66
|
+
else:
|
|
67
|
+
self._user_id = uuid7str()
|
|
68
|
+
try:
|
|
69
|
+
user_id_file.write_text(self._user_id, encoding='utf-8')
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning(f"Could not persist user ID: {e}")
|
|
72
|
+
|
|
73
|
+
return self._user_id
|
|
74
|
+
|
|
75
|
+
async def track_tool(self, tool_name: str, result: Dict[str, Any]) -> None:
|
|
76
|
+
if self.client:
|
|
77
|
+
self.client.capture(
|
|
78
|
+
distinct_id=self.user_id,
|
|
79
|
+
event="tool_executed",
|
|
80
|
+
properties={
|
|
81
|
+
"tool_name": tool_name,
|
|
82
|
+
"session_id": self.mcp_interaction_id,
|
|
83
|
+
"process_person_profile": True,
|
|
84
|
+
**result
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
duration = result.get("duration_ms", 0)
|
|
89
|
+
success_mark = "SUCCESS" if result.get("success") else "FAILED"
|
|
90
|
+
# Using print for immediate visibility in console during debugging
|
|
91
|
+
print(f"[Analytics] {tool_name}: {success_mark} ({duration}ms)")
|
|
92
|
+
logger.info(f"{tool_name}: {success_mark} ({duration}ms)")
|
|
93
|
+
if self.client:
|
|
94
|
+
self.client.flush()
|
|
95
|
+
|
|
96
|
+
async def track_error(self, error: Exception, context: Dict[str, Any]) -> None:
|
|
97
|
+
if self.client:
|
|
98
|
+
self.client.capture(
|
|
99
|
+
distinct_id=self.user_id,
|
|
100
|
+
event="exception",
|
|
101
|
+
properties={
|
|
102
|
+
"exception": str(error),
|
|
103
|
+
"traceback": str(error) if not hasattr(error, '__traceback__') else str(error),
|
|
104
|
+
"session_id": self.mcp_interaction_id,
|
|
105
|
+
"process_person_profile": True,
|
|
106
|
+
**context
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if self.client:
|
|
111
|
+
self.client.flush()
|
|
112
|
+
|
|
113
|
+
logger.error(f"ERROR in {context.get('tool_name')}: {error}")
|
|
114
|
+
|
|
115
|
+
async def is_feature_enabled(self, feature: str) -> bool:
|
|
116
|
+
if not self.client:
|
|
117
|
+
return False
|
|
118
|
+
return self.client.is_feature_enabled(feature, self.user_id)
|
|
119
|
+
|
|
120
|
+
async def close(self) -> None:
|
|
121
|
+
if self.client:
|
|
122
|
+
self.client.shutdown()
|
|
123
|
+
logger.debug("Closed analytics")
|
|
124
|
+
|
|
125
|
+
def with_analytics(analytics_instance: Optional[Analytics], tool_name: str):
|
|
126
|
+
"""
|
|
127
|
+
Decorator to wrap tool functions with analytics tracking.
|
|
128
|
+
"""
|
|
129
|
+
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
|
|
130
|
+
@wraps(func)
|
|
131
|
+
async def wrapper(*args, **kwargs) -> T:
|
|
132
|
+
start = time.time()
|
|
133
|
+
|
|
134
|
+
# Capture client info from Context passed as argument
|
|
135
|
+
client_data = {}
|
|
136
|
+
try:
|
|
137
|
+
ctx = next((arg for arg in args if isinstance(arg, Context)), None)
|
|
138
|
+
if not ctx:
|
|
139
|
+
ctx = next((val for val in kwargs.values() if isinstance(val, Context)), None)
|
|
140
|
+
|
|
141
|
+
if ctx and ctx.session and ctx.session.client_params and ctx.session.client_params.clientInfo:
|
|
142
|
+
info = ctx.session.client_params.clientInfo
|
|
143
|
+
client_data["client_name"] = info.name
|
|
144
|
+
client_data["client_version"] = info.version
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
if asyncio.iscoroutinefunction(func):
|
|
150
|
+
result = await func(*args, **kwargs)
|
|
151
|
+
else:
|
|
152
|
+
# Run sync function in thread to avoid blocking loop
|
|
153
|
+
result = await asyncio.to_thread(func, *args, **kwargs)
|
|
154
|
+
|
|
155
|
+
duration_ms = int((time.time() - start) * 1000)
|
|
156
|
+
|
|
157
|
+
if analytics_instance:
|
|
158
|
+
await analytics_instance.track_tool(tool_name, {
|
|
159
|
+
"duration_ms": duration_ms,
|
|
160
|
+
"success": True,
|
|
161
|
+
**client_data
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return result
|
|
165
|
+
except Exception as error:
|
|
166
|
+
duration_ms = int((time.time() - start) * 1000)
|
|
167
|
+
if analytics_instance:
|
|
168
|
+
await analytics_instance.track_error(error, {
|
|
169
|
+
"tool_name": tool_name,
|
|
170
|
+
"duration_ms": duration_ms,
|
|
171
|
+
**client_data
|
|
172
|
+
})
|
|
173
|
+
raise error
|
|
174
|
+
return wrapper
|
|
175
|
+
return decorator
|
windows_mcp/desktop/config.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
from typing import Set
|
|
2
|
-
|
|
3
|
-
BROWSER_NAMES=set([
|
|
4
|
-
'msedge.exe',
|
|
5
|
-
'chrome.exe',
|
|
6
|
-
'firefox.exe'
|
|
7
|
-
])
|
|
8
|
-
|
|
9
|
-
AVOIDED_APPS:Set[str]=set([
|
|
10
|
-
'AgentUI'
|
|
11
|
-
])
|
|
12
|
-
|
|
13
|
-
EXCLUDED_APPS:Set[str]=set([
|
|
14
|
-
'Progman',
|
|
15
|
-
'Shell_TrayWnd',
|
|
16
|
-
'Shell_SecondaryTrayWnd',
|
|
17
|
-
'Microsoft.UI.Content.PopupWindowSiteBridge',
|
|
18
|
-
'Windows.UI.Core.CoreWindow',
|
|
19
|
-
])
|
|
20
|
-
|
|
1
|
+
from typing import Set
|
|
2
|
+
|
|
3
|
+
BROWSER_NAMES=set([
|
|
4
|
+
'msedge.exe',
|
|
5
|
+
'chrome.exe',
|
|
6
|
+
'firefox.exe'
|
|
7
|
+
])
|
|
8
|
+
|
|
9
|
+
AVOIDED_APPS:Set[str]=set([
|
|
10
|
+
'AgentUI'
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
EXCLUDED_APPS:Set[str]=set([
|
|
14
|
+
'Progman',
|
|
15
|
+
'Shell_TrayWnd',
|
|
16
|
+
'Shell_SecondaryTrayWnd',
|
|
17
|
+
'Microsoft.UI.Content.PopupWindowSiteBridge',
|
|
18
|
+
'Windows.UI.Core.CoreWindow',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
21
|
PROCESS_PER_MONITOR_DPI_AWARE = 2
|