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.
@@ -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.1
4
- Summary: Add your description here
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.1"
4
- description = "Add your description here"
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