mcp-windows 0.0.1__tar.gz → 0.1.0__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.
- mcp_windows-0.1.0/LICENSE +21 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/PKG-INFO +33 -2
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/README.md +22 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/pyproject.toml +12 -2
- mcp_windows-0.1.0/src/mcp_windows/clipboard.py +25 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/main.py +8 -0
- mcp_windows-0.1.0/src/mcp_windows/media.py +131 -0
- mcp_windows-0.1.0/src/mcp_windows/screenshot.py +89 -0
- mcp_windows-0.1.0/src/mcp_windows/startmenu.py +44 -0
- mcp_windows-0.1.0/src/mcp_windows/theme.py +41 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/uv.lock +64 -0
- mcp_windows-0.0.1/src/mcp_windows/media.py +0 -70
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/.gitignore +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/.python-version +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/__init__.py +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/appid.py +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/monitors.py +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/notifications.py +0 -0
- {mcp_windows-0.0.1 → mcp_windows-0.1.0}/src/mcp_windows/window_management.py +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 TerminalMan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -1,11 +1,18 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-windows
|
3
|
-
Version: 0.0
|
4
|
-
Summary:
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: MCP server for the windows API.
|
5
|
+
Project-URL: Homepage, https://github.com/SecretiveShell/mcp-windows
|
6
|
+
Project-URL: Documentation, https://github.com/SecretiveShell/mcp-windows
|
7
|
+
Project-URL: Repository, https://github.com/SecretiveShell/mcp-windows.git
|
8
|
+
Project-URL: Issues, https://github.com/SecretiveShell/mcp-windows/issues
|
9
|
+
Project-URL: Changelog, https://github.com/SecretiveShell/mcp-windows/commits/master/
|
5
10
|
Author-email: TerminalMan <84923604+SecretiveShell@users.noreply.github.com>
|
11
|
+
License-File: LICENSE
|
6
12
|
Requires-Python: >=3.13
|
7
13
|
Requires-Dist: fastmcp>=2.2.0
|
8
14
|
Requires-Dist: mcp>=1.6.0
|
15
|
+
Requires-Dist: pillow>=11.2.1
|
9
16
|
Requires-Dist: psutil>=7.0.0
|
10
17
|
Requires-Dist: pywin32>=310
|
11
18
|
Requires-Dist: winrt-runtime>=3.1.0
|
@@ -13,6 +20,8 @@ Requires-Dist: winrt-windows-data-xml-dom>=3.1.0
|
|
13
20
|
Requires-Dist: winrt-windows-foundation-collections>=3.1.0
|
14
21
|
Requires-Dist: winrt-windows-foundation>=3.1.0
|
15
22
|
Requires-Dist: winrt-windows-media-control>=3.1.0
|
23
|
+
Requires-Dist: winrt-windows-storage>=3.1.0
|
24
|
+
Requires-Dist: winrt-windows-system>=3.1.0
|
16
25
|
Requires-Dist: winrt-windows-ui-notifications>=3.1.0
|
17
26
|
Description-Content-Type: text/markdown
|
18
27
|
|
@@ -35,6 +44,7 @@ add this to your claude mcp config:
|
|
35
44
|
}
|
36
45
|
}
|
37
46
|
}
|
47
|
+
```
|
38
48
|
|
39
49
|
or locally:
|
40
50
|
|
@@ -61,6 +71,8 @@ or locally:
|
|
61
71
|
- get_media_sessions
|
62
72
|
- pause
|
63
73
|
- play
|
74
|
+
- next
|
75
|
+
- previous
|
64
76
|
|
65
77
|
### Notifications
|
66
78
|
|
@@ -74,11 +86,30 @@ or locally:
|
|
74
86
|
- close_window
|
75
87
|
- minimize_window
|
76
88
|
|
89
|
+
### screenshot
|
90
|
+
|
91
|
+
- screenshot_window
|
92
|
+
|
77
93
|
### Monitors
|
78
94
|
|
79
95
|
- sleep_monitors
|
80
96
|
- wake_monitors
|
81
97
|
|
98
|
+
### Theme
|
99
|
+
|
100
|
+
- set_theme_mode (light, dark)
|
101
|
+
- get_theme_mode
|
102
|
+
|
103
|
+
### Start Menu
|
104
|
+
|
105
|
+
- open_file
|
106
|
+
- open_url
|
107
|
+
|
108
|
+
### Clipboard
|
109
|
+
|
110
|
+
- get_clipboard
|
111
|
+
- set_clipboard
|
112
|
+
|
82
113
|
## License
|
83
114
|
|
84
115
|
MIT
|
@@ -17,6 +17,7 @@ add this to your claude mcp config:
|
|
17
17
|
}
|
18
18
|
}
|
19
19
|
}
|
20
|
+
```
|
20
21
|
|
21
22
|
or locally:
|
22
23
|
|
@@ -43,6 +44,8 @@ or locally:
|
|
43
44
|
- get_media_sessions
|
44
45
|
- pause
|
45
46
|
- play
|
47
|
+
- next
|
48
|
+
- previous
|
46
49
|
|
47
50
|
### Notifications
|
48
51
|
|
@@ -56,11 +59,30 @@ or locally:
|
|
56
59
|
- close_window
|
57
60
|
- minimize_window
|
58
61
|
|
62
|
+
### screenshot
|
63
|
+
|
64
|
+
- screenshot_window
|
65
|
+
|
59
66
|
### Monitors
|
60
67
|
|
61
68
|
- sleep_monitors
|
62
69
|
- wake_monitors
|
63
70
|
|
71
|
+
### Theme
|
72
|
+
|
73
|
+
- set_theme_mode (light, dark)
|
74
|
+
- get_theme_mode
|
75
|
+
|
76
|
+
### Start Menu
|
77
|
+
|
78
|
+
- open_file
|
79
|
+
- open_url
|
80
|
+
|
81
|
+
### Clipboard
|
82
|
+
|
83
|
+
- get_clipboard
|
84
|
+
- set_clipboard
|
85
|
+
|
64
86
|
## License
|
65
87
|
|
66
88
|
MIT
|
@@ -1,7 +1,7 @@
|
|
1
1
|
[project]
|
2
2
|
name = "mcp-windows"
|
3
|
-
version = "0.0
|
4
|
-
description = "
|
3
|
+
version = "0.1.0"
|
4
|
+
description = "MCP server for the windows API."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [
|
7
7
|
{ name = "TerminalMan", email = "84923604+SecretiveShell@users.noreply.github.com" }
|
@@ -10,6 +10,7 @@ requires-python = ">=3.13"
|
|
10
10
|
dependencies = [
|
11
11
|
"fastmcp>=2.2.0",
|
12
12
|
"mcp>=1.6.0",
|
13
|
+
"pillow>=11.2.1",
|
13
14
|
"psutil>=7.0.0",
|
14
15
|
"pywin32>=310",
|
15
16
|
"winrt-runtime>=3.1.0",
|
@@ -17,6 +18,8 @@ dependencies = [
|
|
17
18
|
"winrt-windows-foundation>=3.1.0",
|
18
19
|
"winrt-windows-foundation-collections>=3.1.0",
|
19
20
|
"winrt-windows-media-control>=3.1.0",
|
21
|
+
"winrt-windows-storage>=3.1.0",
|
22
|
+
"winrt-windows-system>=3.1.0",
|
20
23
|
"winrt-windows-ui-notifications>=3.1.0",
|
21
24
|
]
|
22
25
|
|
@@ -26,3 +29,10 @@ mcp-windows = "mcp_windows:main"
|
|
26
29
|
[build-system]
|
27
30
|
requires = ["hatchling"]
|
28
31
|
build-backend = "hatchling.build"
|
32
|
+
|
33
|
+
[project.urls]
|
34
|
+
Homepage = "https://github.com/SecretiveShell/mcp-windows"
|
35
|
+
Documentation = "https://github.com/SecretiveShell/mcp-windows"
|
36
|
+
Repository = "https://github.com/SecretiveShell/mcp-windows.git"
|
37
|
+
Issues = "https://github.com/SecretiveShell/mcp-windows/issues"
|
38
|
+
Changelog = "https://github.com/SecretiveShell/mcp-windows/commits/master/"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import win32clipboard
|
2
|
+
import win32con
|
3
|
+
from fastmcp import FastMCP
|
4
|
+
|
5
|
+
|
6
|
+
mcp: FastMCP = FastMCP(
|
7
|
+
name="clipboard",
|
8
|
+
)
|
9
|
+
|
10
|
+
@mcp.tool("get_clipboard")
|
11
|
+
async def get_clipboard() -> str:
|
12
|
+
"""Get the current clipboard contents."""
|
13
|
+
win32clipboard.OpenClipboard()
|
14
|
+
data = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
|
15
|
+
win32clipboard.CloseClipboard()
|
16
|
+
return data
|
17
|
+
|
18
|
+
@mcp.tool("set_clipboard")
|
19
|
+
async def set_clipboard(text: str) -> str:
|
20
|
+
"""Set the clipboard contents."""
|
21
|
+
win32clipboard.OpenClipboard()
|
22
|
+
win32clipboard.EmptyClipboard()
|
23
|
+
win32clipboard.SetClipboardText(text, win32con.CF_UNICODETEXT)
|
24
|
+
win32clipboard.CloseClipboard()
|
25
|
+
return "Clipboard set"
|
@@ -5,6 +5,10 @@ from mcp_windows.media import mcp as media_mcp
|
|
5
5
|
from mcp_windows.notifications import mcp as notifications_mcp
|
6
6
|
from mcp_windows.window_management import mcp as window_management_mcp
|
7
7
|
from mcp_windows.monitors import mcp as monitors_mcp
|
8
|
+
from mcp_windows.clipboard import mcp as clipboard_mcp
|
9
|
+
from mcp_windows.screenshot import mcp as screenshot_mcp
|
10
|
+
from mcp_windows.theme import mcp as theme_mcp
|
11
|
+
from mcp_windows.startmenu import mcp as startmenu_mcp
|
8
12
|
|
9
13
|
sep = environ.get("FASTMCP_TOOL_SEPARATOR", "_")
|
10
14
|
|
@@ -16,3 +20,7 @@ mcp.mount("media", media_mcp, tool_separator=sep)
|
|
16
20
|
mcp.mount("notifications", notifications_mcp, tool_separator=sep)
|
17
21
|
mcp.mount("window_management", window_management_mcp, tool_separator=sep)
|
18
22
|
mcp.mount("monitors", monitors_mcp, tool_separator=sep)
|
23
|
+
mcp.mount("clipboard", clipboard_mcp, tool_separator=sep)
|
24
|
+
mcp.mount("screenshot", screenshot_mcp, tool_separator=sep)
|
25
|
+
mcp.mount("theme", theme_mcp, tool_separator=sep)
|
26
|
+
mcp.mount("startmenu", startmenu_mcp, tool_separator=sep)
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
from fastmcp import FastMCP
|
4
|
+
from winrt.windows.foundation import IAsyncOperation
|
5
|
+
from winrt.windows.media.control import (
|
6
|
+
GlobalSystemMediaTransportControlsSessionManager as MediaManager,
|
7
|
+
GlobalSystemMediaTransportControlsSessionMediaProperties as MediaProperties,
|
8
|
+
GlobalSystemMediaTransportControlsSessionPlaybackInfo as PlaybackInfo,
|
9
|
+
)
|
10
|
+
|
11
|
+
mcp: FastMCP = FastMCP(
|
12
|
+
name="Media",
|
13
|
+
)
|
14
|
+
|
15
|
+
PLAYBACK_STATUS = {
|
16
|
+
0: "closed",
|
17
|
+
1: "opened",
|
18
|
+
2: "changing",
|
19
|
+
3: "stopped",
|
20
|
+
4: "playing",
|
21
|
+
5: "paused",
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
@mcp.tool("get_media_sessions")
|
26
|
+
async def get_media_sessions() -> str:
|
27
|
+
"""List all media playback sessions with metadata and control capability info."""
|
28
|
+
|
29
|
+
manager_op: IAsyncOperation = MediaManager.request_async()
|
30
|
+
manager = await manager_op
|
31
|
+
sessions = manager.get_sessions()
|
32
|
+
|
33
|
+
output = {}
|
34
|
+
for session in sessions:
|
35
|
+
props_op = session.try_get_media_properties_async()
|
36
|
+
props: MediaProperties = await props_op
|
37
|
+
playback_info: PlaybackInfo = session.get_playback_info()
|
38
|
+
controls = playback_info.controls
|
39
|
+
|
40
|
+
app_id = session.source_app_user_model_id
|
41
|
+
|
42
|
+
output[app_id] = {
|
43
|
+
"title": props.title or "unknown",
|
44
|
+
"artist": props.artist or "unknown",
|
45
|
+
"album_title": props.album_title or "unknown",
|
46
|
+
"playback_status": str(PLAYBACK_STATUS.get(playback_info.playback_status)),
|
47
|
+
"is_play_enabled": controls.is_play_enabled,
|
48
|
+
"is_pause_enabled": controls.is_pause_enabled,
|
49
|
+
"is_next_enabled": controls.is_next_enabled,
|
50
|
+
"is_previous_enabled": controls.is_previous_enabled,
|
51
|
+
}
|
52
|
+
|
53
|
+
return json.dumps(output)
|
54
|
+
|
55
|
+
|
56
|
+
@mcp.tool("pause")
|
57
|
+
async def pause(app_id: str) -> str:
|
58
|
+
"""Pause the media playback for a given app_id using windows media control API."""
|
59
|
+
|
60
|
+
manager_op: IAsyncOperation[MediaManager] = MediaManager.request_async()
|
61
|
+
manager: MediaManager = await manager_op
|
62
|
+
|
63
|
+
sessions = manager.get_sessions()
|
64
|
+
for session in sessions:
|
65
|
+
if session.source_app_user_model_id.lower() == app_id.lower():
|
66
|
+
playback_info = session.get_playback_info()
|
67
|
+
if playback_info.controls.is_pause_enabled:
|
68
|
+
await session.try_pause_async()
|
69
|
+
return "Paused"
|
70
|
+
else:
|
71
|
+
return "Pause not available"
|
72
|
+
|
73
|
+
return "Session not found"
|
74
|
+
|
75
|
+
|
76
|
+
@mcp.tool("play")
|
77
|
+
async def play(app_id: str) -> str:
|
78
|
+
"""Play the media playback for a given app_id using windows media control API."""
|
79
|
+
|
80
|
+
manager_op: IAsyncOperation[MediaManager] = MediaManager.request_async()
|
81
|
+
manager: MediaManager = await manager_op
|
82
|
+
|
83
|
+
sessions = manager.get_sessions()
|
84
|
+
for session in sessions:
|
85
|
+
if session.source_app_user_model_id.lower() == app_id.lower():
|
86
|
+
playback_info = session.get_playback_info()
|
87
|
+
if playback_info.controls.is_play_enabled:
|
88
|
+
await session.try_play_async()
|
89
|
+
return "Playing"
|
90
|
+
else:
|
91
|
+
return "Play not available"
|
92
|
+
|
93
|
+
return "Session not found"
|
94
|
+
|
95
|
+
|
96
|
+
@mcp.tool("next")
|
97
|
+
async def next(app_id: str) -> str:
|
98
|
+
"""Skip to the next media item for the given app_id."""
|
99
|
+
|
100
|
+
manager = await MediaManager.request_async()
|
101
|
+
sessions = manager.get_sessions()
|
102
|
+
|
103
|
+
for session in sessions:
|
104
|
+
if session.source_app_user_model_id.lower() == app_id.lower():
|
105
|
+
playback_info = session.get_playback_info()
|
106
|
+
if playback_info.controls.is_next_enabled:
|
107
|
+
await session.try_skip_next_async()
|
108
|
+
return "Skipped to next track"
|
109
|
+
else:
|
110
|
+
return "Next track not available"
|
111
|
+
|
112
|
+
return "Session not found"
|
113
|
+
|
114
|
+
|
115
|
+
@mcp.tool("previous")
|
116
|
+
async def previous(app_id: str) -> str:
|
117
|
+
"""Skip to the previous media item for the given app_id."""
|
118
|
+
|
119
|
+
manager = await MediaManager.request_async()
|
120
|
+
sessions = manager.get_sessions()
|
121
|
+
|
122
|
+
for session in sessions:
|
123
|
+
if session.source_app_user_model_id.lower() == app_id.lower():
|
124
|
+
playback_info = session.get_playback_info()
|
125
|
+
if playback_info.controls.is_previous_enabled:
|
126
|
+
await session.try_skip_previous_async()
|
127
|
+
return "Skipped to previous track"
|
128
|
+
else:
|
129
|
+
return "Previous track not available"
|
130
|
+
|
131
|
+
return "Session not found"
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import io
|
2
|
+
import win32gui
|
3
|
+
import win32ui
|
4
|
+
import win32api
|
5
|
+
from PIL import Image
|
6
|
+
import ctypes
|
7
|
+
|
8
|
+
from fastmcp import FastMCP, Image as FastMCPImage
|
9
|
+
|
10
|
+
mcp: FastMCP = FastMCP(
|
11
|
+
name="screencapture",
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
# this was mostly llm generated so if it doesn't work, blame the ai
|
16
|
+
@mcp.tool("screenshot_window")
|
17
|
+
async def screenshot_window(hwnd: int) -> str | FastMCPImage:
|
18
|
+
"""Capture a screenshot of the specified window handle (hwnd). Does not require the window to be visible."""
|
19
|
+
try:
|
20
|
+
hwnd = int(hwnd)
|
21
|
+
if not win32gui.IsWindow(hwnd):
|
22
|
+
return "Invalid window handle"
|
23
|
+
|
24
|
+
# Get window rect
|
25
|
+
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
|
26
|
+
width = right - left
|
27
|
+
height = bottom - top
|
28
|
+
|
29
|
+
# Check for valid dimensions
|
30
|
+
if width <= 0 or height <= 0:
|
31
|
+
return "Window has invalid dimensions"
|
32
|
+
|
33
|
+
# Get window device context
|
34
|
+
hwndDC = win32gui.GetWindowDC(hwnd)
|
35
|
+
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
|
36
|
+
saveDC = mfcDC.CreateCompatibleDC()
|
37
|
+
|
38
|
+
saveBitMap = win32ui.CreateBitmap()
|
39
|
+
saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)
|
40
|
+
saveDC.SelectObject(saveBitMap)
|
41
|
+
|
42
|
+
# Change PrintWindow flags to capture entire window content including child windows
|
43
|
+
# PW_RENDERFULLCONTENT = 0x00000002
|
44
|
+
result = ctypes.windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 2)
|
45
|
+
|
46
|
+
# Add a small delay to ensure the content is captured
|
47
|
+
win32api.Sleep(100)
|
48
|
+
|
49
|
+
bmpinfo = saveBitMap.GetInfo()
|
50
|
+
bmpstr = saveBitMap.GetBitmapBits(True)
|
51
|
+
|
52
|
+
# Check if we have valid bitmap data
|
53
|
+
if not bmpstr or len(bmpstr) <= 0:
|
54
|
+
return "Failed to capture window content"
|
55
|
+
|
56
|
+
img = Image.frombuffer(
|
57
|
+
"RGB",
|
58
|
+
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
|
59
|
+
bmpstr, 'raw', 'BGRX', 0, 1
|
60
|
+
)
|
61
|
+
|
62
|
+
# Cleanup
|
63
|
+
win32gui.DeleteObject(saveBitMap.GetHandle())
|
64
|
+
saveDC.DeleteDC()
|
65
|
+
mfcDC.DeleteDC()
|
66
|
+
win32gui.ReleaseDC(hwnd, hwndDC)
|
67
|
+
|
68
|
+
if result:
|
69
|
+
# Preserve aspect ratio when resizing
|
70
|
+
if width > 0 and height > 0:
|
71
|
+
target_width = 1024
|
72
|
+
target_height = int(height * (target_width / width))
|
73
|
+
|
74
|
+
# Make sure we don't exceed maximum height
|
75
|
+
if target_height > 2048:
|
76
|
+
target_height = 2048
|
77
|
+
target_width = int(width * (target_height / height))
|
78
|
+
|
79
|
+
img = img.resize((target_width, target_height), Image.LANCZOS)
|
80
|
+
|
81
|
+
buffer = io.BytesIO()
|
82
|
+
img.save(buffer, format="PNG")
|
83
|
+
buffer.seek(0)
|
84
|
+
|
85
|
+
return FastMCPImage(data=buffer.read(), format="png")
|
86
|
+
else:
|
87
|
+
return "Screenshot may be partial or failed due to permissions"
|
88
|
+
except Exception as e:
|
89
|
+
return f"Failed to capture screenshot: {type(e).__name__}: {e}"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from fastmcp import FastMCP
|
2
|
+
import os
|
3
|
+
from winrt.windows.foundation import Uri
|
4
|
+
from winrt.windows.system import Launcher
|
5
|
+
from winrt.windows.storage import StorageFile
|
6
|
+
|
7
|
+
mcp: FastMCP = FastMCP(
|
8
|
+
name="startmenu",
|
9
|
+
)
|
10
|
+
|
11
|
+
@mcp.tool("open_file")
|
12
|
+
async def open_file(path: str) -> str:
|
13
|
+
"""Open a file or folder in the default application."""
|
14
|
+
path = os.path.expanduser(path)
|
15
|
+
path = os.path.expandvars(path)
|
16
|
+
path = os.path.abspath(path)
|
17
|
+
if not os.path.exists(path):
|
18
|
+
return f"Path does not exist: {path}"
|
19
|
+
|
20
|
+
file = await StorageFile.get_file_from_path_async(path)
|
21
|
+
success = await Launcher.launch_file_async(file)
|
22
|
+
|
23
|
+
if success:
|
24
|
+
return "Opened file"
|
25
|
+
|
26
|
+
# Fallback to os.startfile if the above fails
|
27
|
+
os.startfile(path)
|
28
|
+
return "Opened file"
|
29
|
+
|
30
|
+
@mcp.tool("open_url")
|
31
|
+
async def open_url(url: str) -> str:
|
32
|
+
"""Open a URL in the default browser."""
|
33
|
+
try:
|
34
|
+
uri = Uri(url)
|
35
|
+
success = await Launcher.launch_uri_async(uri)
|
36
|
+
if success:
|
37
|
+
return "Opened URL"
|
38
|
+
except Exception:
|
39
|
+
pass
|
40
|
+
|
41
|
+
# Fallback to webbrowser if the above fails
|
42
|
+
import webbrowser
|
43
|
+
webbrowser.open(url)
|
44
|
+
return "Opened URL"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
import winreg
|
3
|
+
from fastmcp import FastMCP
|
4
|
+
|
5
|
+
mcp: FastMCP = FastMCP(
|
6
|
+
name="theme",
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
def _set_theme_key(name: str, value: int):
|
11
|
+
key_path = r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
|
12
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE) as key:
|
13
|
+
winreg.SetValueEx(key, name, 0, winreg.REG_DWORD, value)
|
14
|
+
|
15
|
+
@mcp.tool("set_theme_mode")
|
16
|
+
async def set_theme_mode(mode: Literal["dark", "light"]) -> str:
|
17
|
+
"""Set Windows UI theme to 'dark' or 'light'."""
|
18
|
+
|
19
|
+
if mode.lower() not in {"dark", "light"}:
|
20
|
+
return "Invalid mode. Use 'dark' or 'light'."
|
21
|
+
|
22
|
+
val = 0 if mode == "dark" else 1
|
23
|
+
try:
|
24
|
+
_set_theme_key("AppsUseLightTheme", val)
|
25
|
+
_set_theme_key("SystemUsesLightTheme", val)
|
26
|
+
return f"Set theme to {mode}"
|
27
|
+
except Exception as e:
|
28
|
+
return f"Failed to set theme: {type(e).__name__}: {e}"
|
29
|
+
|
30
|
+
|
31
|
+
@mcp.tool("get_theme_mode")
|
32
|
+
async def get_theme_mode() -> str:
|
33
|
+
"""Get the current Windows UI theme."""
|
34
|
+
|
35
|
+
key_path = r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
|
36
|
+
try:
|
37
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:
|
38
|
+
val, _ = winreg.QueryValueEx(key, "AppsUseLightTheme")
|
39
|
+
return "light" if val else "dark"
|
40
|
+
except Exception:
|
41
|
+
return "unknown"
|
@@ -185,6 +185,7 @@ source = { editable = "." }
|
|
185
185
|
dependencies = [
|
186
186
|
{ name = "fastmcp" },
|
187
187
|
{ name = "mcp" },
|
188
|
+
{ name = "pillow" },
|
188
189
|
{ name = "psutil" },
|
189
190
|
{ name = "pywin32" },
|
190
191
|
{ name = "winrt-runtime" },
|
@@ -192,6 +193,8 @@ dependencies = [
|
|
192
193
|
{ name = "winrt-windows-foundation" },
|
193
194
|
{ name = "winrt-windows-foundation-collections" },
|
194
195
|
{ name = "winrt-windows-media-control" },
|
196
|
+
{ name = "winrt-windows-storage" },
|
197
|
+
{ name = "winrt-windows-system" },
|
195
198
|
{ name = "winrt-windows-ui-notifications" },
|
196
199
|
]
|
197
200
|
|
@@ -199,6 +202,7 @@ dependencies = [
|
|
199
202
|
requires-dist = [
|
200
203
|
{ name = "fastmcp", specifier = ">=2.2.0" },
|
201
204
|
{ name = "mcp", specifier = ">=1.6.0" },
|
205
|
+
{ name = "pillow", specifier = ">=11.2.1" },
|
202
206
|
{ name = "psutil", specifier = ">=7.0.0" },
|
203
207
|
{ name = "pywin32", specifier = ">=310" },
|
204
208
|
{ name = "winrt-runtime", specifier = ">=3.1.0" },
|
@@ -206,6 +210,8 @@ requires-dist = [
|
|
206
210
|
{ name = "winrt-windows-foundation", specifier = ">=3.1.0" },
|
207
211
|
{ name = "winrt-windows-foundation-collections", specifier = ">=3.1.0" },
|
208
212
|
{ name = "winrt-windows-media-control", specifier = ">=3.1.0" },
|
213
|
+
{ name = "winrt-windows-storage", specifier = ">=3.1.0" },
|
214
|
+
{ name = "winrt-windows-system", specifier = ">=3.1.0" },
|
209
215
|
{ name = "winrt-windows-ui-notifications", specifier = ">=3.1.0" },
|
210
216
|
]
|
211
217
|
|
@@ -230,6 +236,36 @@ wheels = [
|
|
230
236
|
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381 },
|
231
237
|
]
|
232
238
|
|
239
|
+
[[package]]
|
240
|
+
name = "pillow"
|
241
|
+
version = "11.2.1"
|
242
|
+
source = { registry = "https://pypi.org/simple" }
|
243
|
+
sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 }
|
244
|
+
wheels = [
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 },
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 },
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 },
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 },
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 },
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 },
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 },
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 },
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 },
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 },
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 },
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 },
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 },
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 },
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 },
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 },
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 },
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 },
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 },
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 },
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 },
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 },
|
267
|
+
]
|
268
|
+
|
233
269
|
[[package]]
|
234
270
|
name = "psutil"
|
235
271
|
version = "7.0.0"
|
@@ -525,6 +561,34 @@ wheels = [
|
|
525
561
|
{ url = "https://files.pythonhosted.org/packages/11/3a/fbd05ed7057613cf605f7eb0dab05d112d3d4ac473df443467fdebb880bf/winrt_windows_media_control-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ebaa57ab082c0e95eec05f932fe53916000c8e73effb67b8d63936b294b4fa96", size = 58813 },
|
526
562
|
]
|
527
563
|
|
564
|
+
[[package]]
|
565
|
+
name = "winrt-windows-storage"
|
566
|
+
version = "3.1.0"
|
567
|
+
source = { registry = "https://pypi.org/simple" }
|
568
|
+
dependencies = [
|
569
|
+
{ name = "winrt-runtime" },
|
570
|
+
]
|
571
|
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/f4/bfa6375633ad4fc3b6dc4feedf641461db1e035d78875baca5caff83f3d5/winrt_windows_storage-3.1.0.tar.gz", hash = "sha256:59f75cdfa12146ab0a35ac7cd861ba39d471b752d7c182e9812a4d957f019a3d", size = 48226 }
|
572
|
+
wheels = [
|
573
|
+
{ url = "https://files.pythonhosted.org/packages/ce/44/1df4b295d5da9562552199768fad84f08e24d7145429a3ef3ffaf47927e6/winrt_windows_storage-3.1.0-cp313-cp313-win32.whl", hash = "sha256:cf575f718db8a42729a461d8eb7870501030ab8219ecaf55a0f0663914e0148b", size = 236832 },
|
574
|
+
{ url = "https://files.pythonhosted.org/packages/d4/4b/2f0ee7d691aeb7eda9444292b934636adb1426a0326c600bf9e3d60690ab/winrt_windows_storage-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac5a3d7db1cf36b533207cfb05ef2e22c09d9c5b4c8c8ceae5b4cedec2d14e3d", size = 261900 },
|
575
|
+
{ url = "https://files.pythonhosted.org/packages/f0/fc/ee955abafe3b46c7dc9cafef82a31916b76e1d9201c32ff917ba5c86776d/winrt_windows_storage-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:f6af035cfdbecbabaa06be6ef061535386824a724eb282c6dc4b7522ac76b63a", size = 256146 },
|
576
|
+
]
|
577
|
+
|
578
|
+
[[package]]
|
579
|
+
name = "winrt-windows-system"
|
580
|
+
version = "3.1.0"
|
581
|
+
source = { registry = "https://pypi.org/simple" }
|
582
|
+
dependencies = [
|
583
|
+
{ name = "winrt-runtime" },
|
584
|
+
]
|
585
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c4/f8/c446ec89a16d9dcbd8310b8e3fc0a3145add9c499e70f4a642524f1be77b/winrt_windows_system-3.1.0.tar.gz", hash = "sha256:baf089c672740734ddf12c99504d19e3395e2ff366bd483d2c0722a414067371", size = 37925 }
|
586
|
+
wheels = [
|
587
|
+
{ url = "https://files.pythonhosted.org/packages/0b/7e/6380384ae6cfc2c1aa4c30920bb6da25fe7a2b9321d79f8457e92b46a007/winrt_windows_system-3.1.0-cp313-cp313-win32.whl", hash = "sha256:f3c9c6f73ee801a9063380662a8a02ead1ed78484f7fff620b7aaf140d97dad7", size = 213718 },
|
588
|
+
{ url = "https://files.pythonhosted.org/packages/1a/68/e6d65b70a2edb8640b02e933ecb027b93432d5953dff2a628f70b6f560d9/winrt_windows_system-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a008e0c05af6ae9a5ef930ae88f2ebec74c87ab8c68e280631782d158f39290", size = 227679 },
|
589
|
+
{ url = "https://files.pythonhosted.org/packages/f9/3b/96ba1c1c9de07f578e2e33545dfbc0702f882584cccaf5634bc32609c18f/winrt_windows_system-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:57226119cb3702cdd49f08431a0579ea67a89865a1b69e285befba36b0bf4409", size = 226608 },
|
590
|
+
]
|
591
|
+
|
528
592
|
[[package]]
|
529
593
|
name = "winrt-windows-ui-notifications"
|
530
594
|
version = "3.1.0"
|
@@ -1,70 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from fastmcp import FastMCP
|
3
|
-
from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager, GlobalSystemMediaTransportControlsSessionMediaProperties as MediaProperties
|
4
|
-
from winrt.windows.foundation import IAsyncOperation
|
5
|
-
|
6
|
-
mcp: FastMCP = FastMCP(
|
7
|
-
name="Media",
|
8
|
-
)
|
9
|
-
|
10
|
-
@mcp.tool("get_media_sessions")
|
11
|
-
async def get_media_sessions() -> str:
|
12
|
-
"""List all media playback sessions using windows media control API."""
|
13
|
-
|
14
|
-
manager_op: IAsyncOperation = MediaManager.request_async()
|
15
|
-
manager = await manager_op
|
16
|
-
sessions = manager.get_sessions()
|
17
|
-
|
18
|
-
output = {}
|
19
|
-
for session in sessions:
|
20
|
-
props_op = session.try_get_media_properties_async()
|
21
|
-
props: MediaProperties = await props_op
|
22
|
-
app_id = session.source_app_user_model_id
|
23
|
-
|
24
|
-
output[app_id] = {
|
25
|
-
"title": props.title or "unknown",
|
26
|
-
"artist": props.artist or "unknown",
|
27
|
-
"album_title": props.album_title or "unknown",
|
28
|
-
}
|
29
|
-
|
30
|
-
return json.dumps(output)
|
31
|
-
|
32
|
-
@mcp.tool("pause")
|
33
|
-
async def pause(app_id: str) -> str:
|
34
|
-
"""Pause the media playback for a given app_id using windows media control API."""
|
35
|
-
|
36
|
-
manager_op: IAsyncOperation[MediaManager] = \
|
37
|
-
MediaManager.request_async()
|
38
|
-
manager: MediaManager = await manager_op
|
39
|
-
|
40
|
-
sessions = manager.get_sessions()
|
41
|
-
for session in sessions:
|
42
|
-
if session.source_app_user_model_id.lower() == app_id.lower():
|
43
|
-
playback_info = session.get_playback_info()
|
44
|
-
if playback_info.controls.is_pause_enabled:
|
45
|
-
await session.try_pause_async()
|
46
|
-
return "Paused"
|
47
|
-
else:
|
48
|
-
return "Pause not available"
|
49
|
-
|
50
|
-
return "Session not found"
|
51
|
-
|
52
|
-
@mcp.tool("play")
|
53
|
-
async def play(app_id: str) -> str:
|
54
|
-
"""Play the media playback for a given app_id using windows media control API."""
|
55
|
-
|
56
|
-
manager_op: IAsyncOperation[MediaManager] = \
|
57
|
-
MediaManager.request_async()
|
58
|
-
manager: MediaManager = await manager_op
|
59
|
-
|
60
|
-
sessions = manager.get_sessions()
|
61
|
-
for session in sessions:
|
62
|
-
if session.source_app_user_model_id.lower() == app_id.lower():
|
63
|
-
playback_info = session.get_playback_info()
|
64
|
-
if playback_info.controls.is_play_enabled:
|
65
|
-
await session.try_play_async()
|
66
|
-
return "Playing"
|
67
|
-
else:
|
68
|
-
return "Play not available"
|
69
|
-
|
70
|
-
return "Session not found"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|