mcp-windows 0.0.1__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.
@@ -0,0 +1,4 @@
1
+ from mcp_windows.main import mcp
2
+
3
+ def main() -> None:
4
+ mcp.run("stdio")
mcp_windows/appid.py ADDED
@@ -0,0 +1,34 @@
1
+ """this script registers a start menu shortcut for the MCP Windows app with a custom AppUserModelID.
2
+ This is necessary for the app to be able to send windows toast notifications due to some legacy UWP API
3
+ limitations.
4
+
5
+ If you press the windows key and type "mcp" in the start menu, you should see the MCP Windows app icon."""
6
+
7
+ import os
8
+ import sys
9
+ from win32com.client import Dispatch
10
+ import pythoncom
11
+
12
+ APP_ID = "mcp-windows"
13
+ SHORTCUT_PATH = os.path.join(
14
+ os.environ["APPDATA"],
15
+ r"Microsoft\Windows\Start Menu\Programs\MCP Windows.lnk"
16
+ )
17
+
18
+ STGM_READWRITE = 0x00000002
19
+
20
+ def register_app_id():
21
+ shell = Dispatch("WScript.Shell")
22
+ shortcut = shell.CreateShortcut(SHORTCUT_PATH)
23
+ shortcut.TargetPath = sys.executable
24
+ shortcut.WorkingDirectory = os.getcwd()
25
+ shortcut.IconLocation = sys.executable
26
+ shortcut.Save()
27
+
28
+ # Add AppUserModelID
29
+ from win32com.propsys import propsys, pscon
30
+ property_store = propsys.SHGetPropertyStoreFromParsingName(SHORTCUT_PATH, None, STGM_READWRITE)
31
+ property_store.SetValue(pscon.PKEY_AppUserModel_ID, propsys.PROPVARIANTType(APP_ID, pythoncom.VT_LPWSTR))
32
+ property_store.Commit()
33
+
34
+ register_app_id()
mcp_windows/main.py ADDED
@@ -0,0 +1,18 @@
1
+ from fastmcp import FastMCP
2
+ from os import environ
3
+
4
+ from mcp_windows.media import mcp as media_mcp
5
+ from mcp_windows.notifications import mcp as notifications_mcp
6
+ from mcp_windows.window_management import mcp as window_management_mcp
7
+ from mcp_windows.monitors import mcp as monitors_mcp
8
+
9
+ sep = environ.get("FASTMCP_TOOL_SEPARATOR", "_")
10
+
11
+ mcp: FastMCP = FastMCP(
12
+ name="windows",
13
+ )
14
+
15
+ mcp.mount("media", media_mcp, tool_separator=sep)
16
+ mcp.mount("notifications", notifications_mcp, tool_separator=sep)
17
+ mcp.mount("window_management", window_management_mcp, tool_separator=sep)
18
+ mcp.mount("monitors", monitors_mcp, tool_separator=sep)
mcp_windows/media.py ADDED
@@ -0,0 +1,70 @@
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"
@@ -0,0 +1,35 @@
1
+ import ctypes
2
+ import win32con
3
+ import win32gui
4
+
5
+ from fastmcp import FastMCP
6
+
7
+ mcp: FastMCP = FastMCP(
8
+ name="monitors",
9
+ )
10
+
11
+ @mcp.tool("sleep_monitors")
12
+ async def sleep_monitors() -> str:
13
+ """Put all monitors to sleep."""
14
+ try:
15
+ ctypes.windll.user32.SendMessageW(
16
+ win32con.HWND_BROADCAST,
17
+ win32con.WM_SYSCOMMAND,
18
+ win32con.SC_MONITORPOWER,
19
+ 2 # 2 = power off
20
+ )
21
+ return "Monitors put to sleep"
22
+ except Exception as e:
23
+ return f"Failed to sleep monitors: {type(e).__name__}: {e}"
24
+
25
+ @mcp.tool("wake_monitors")
26
+ async def wake_monitors() -> str:
27
+ """Wake up sleeping monitors."""
28
+ try:
29
+ # This is dumb, but moving the mouse 1px wakes monitors
30
+ x, y = win32gui.GetCursorPos()
31
+ ctypes.windll.user32.SetCursorPos(x, y + 1)
32
+ ctypes.windll.user32.SetCursorPos(x, y)
33
+ return "Monitors woken up"
34
+ except Exception as e:
35
+ return f"Failed to wake monitors: {type(e).__name__}: {e}"
@@ -0,0 +1,38 @@
1
+ import asyncio
2
+ from fastmcp import FastMCP
3
+
4
+ from mcp_windows.appid import APP_ID
5
+
6
+ from winrt.windows.ui.notifications import ToastNotificationManager, ToastNotification
7
+ from winrt.windows.data.xml.dom import XmlDocument
8
+
9
+ mcp: FastMCP = FastMCP(
10
+ name="notifications",
11
+ )
12
+
13
+ @mcp.tool("send_toast")
14
+ async def send_toast(title: str, message: str) -> str:
15
+ """Send a windows toast notification to the user."""
16
+
17
+
18
+ toast_xml_string = f"""
19
+ <toast>
20
+ <visual>
21
+ <binding template="ToastGeneric">
22
+ <text>{title}</text>
23
+ <text>{message}</text>
24
+ </binding>
25
+ </visual>
26
+ </toast>
27
+ """
28
+
29
+ xml_doc = XmlDocument()
30
+ xml_doc.load_xml(toast_xml_string)
31
+
32
+ toast = ToastNotification(xml_doc)
33
+
34
+ notifier = ToastNotificationManager.create_toast_notifier_with_id(APP_ID)
35
+
36
+ notifier.show(toast)
37
+
38
+ return "Toast notification sent"
@@ -0,0 +1,140 @@
1
+ import asyncio
2
+ import json
3
+ import win32gui
4
+ import win32con
5
+ import win32process
6
+ import win32api
7
+ import psutil
8
+
9
+ from fastmcp import FastMCP
10
+
11
+ mcp: FastMCP = FastMCP(
12
+ name="window_management"
13
+ )
14
+
15
+ def get_process_info(pid: int) -> dict:
16
+ try:
17
+ proc = psutil.Process(pid)
18
+ return {
19
+ "pid": pid,
20
+ "exe": proc.name(),
21
+ }
22
+ except psutil.NoSuchProcess:
23
+ return {
24
+ "pid": pid,
25
+ "exe": "<terminated>"
26
+ }
27
+
28
+ @mcp.tool("get_foreground_window_info")
29
+ async def get_foreground_window_info() -> str:
30
+ """Return information about the currently focused (foreground) window."""
31
+ hwnd = win32gui.GetForegroundWindow()
32
+ if hwnd == 0:
33
+ return json.dumps({"error": "No active window"})
34
+
35
+ _, pid = win32process.GetWindowThreadProcessId(hwnd)
36
+ info = get_process_info(pid)
37
+ info.update({
38
+ "hwnd": hwnd,
39
+ "title": win32gui.GetWindowText(hwnd),
40
+ "class": win32gui.GetClassName(hwnd),
41
+ })
42
+ return json.dumps(info, ensure_ascii=False)
43
+
44
+ @mcp.tool("get_window_list")
45
+ async def list_open_windows() -> str:
46
+ """Return a list of all top-level visible windows."""
47
+ windows = []
48
+
49
+ def callback(hwnd, _):
50
+ if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd):
51
+ _, pid = win32process.GetWindowThreadProcessId(hwnd)
52
+ info = get_process_info(pid)
53
+ info.update({
54
+ "hwnd": hwnd,
55
+ "title": win32gui.GetWindowText(hwnd),
56
+ "class": win32gui.GetClassName(hwnd),
57
+ })
58
+ windows.append(info)
59
+
60
+ win32gui.EnumWindows(callback, None)
61
+ return json.dumps(windows, ensure_ascii=False)
62
+
63
+ @mcp.tool("focus_window")
64
+ async def focus_window(hwnd: int) -> str:
65
+ """Force focus a window using all known safe tricks (thread attach, fake input, fallback restore)."""
66
+ try:
67
+ hwnd = int(hwnd)
68
+
69
+ if not win32gui.IsWindow(hwnd):
70
+ return "Invalid HWND"
71
+
72
+ # Step 1: Only restore if minimized (prevent resizing)
73
+ if win32gui.IsIconic(hwnd):
74
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
75
+
76
+ # Step 2: Try normal focus via thread attach
77
+ fg_hwnd = win32gui.GetForegroundWindow()
78
+ fg_thread = win32process.GetWindowThreadProcessId(fg_hwnd)[0]
79
+ current_thread = win32api.GetCurrentThreadId()
80
+
81
+ if fg_thread != current_thread:
82
+ win32process.AttachThreadInput(fg_thread, current_thread, True)
83
+
84
+ try:
85
+ win32gui.SetForegroundWindow(hwnd)
86
+ except Exception:
87
+ pass
88
+
89
+ if fg_thread != current_thread:
90
+ win32process.AttachThreadInput(fg_thread, current_thread, False)
91
+
92
+ # Step 3: Check if it worked
93
+ if win32gui.GetForegroundWindow() == hwnd:
94
+ return "Focused window successfully"
95
+
96
+ # Step 4: Fallback — simulate user input (to defeat foreground lock)
97
+ win32api.keybd_event(0, 0, 0, 0)
98
+ await asyncio.sleep(0.05)
99
+
100
+ # Step 5: Try again
101
+ try:
102
+ win32gui.SetForegroundWindow(hwnd)
103
+ except Exception:
104
+ pass
105
+
106
+ if win32gui.GetForegroundWindow() == hwnd:
107
+ return "Focused window (after simulating input)"
108
+
109
+ # Step 6: Hard fallback — minimize + restore
110
+ win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
111
+ await asyncio.sleep(0.2)
112
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
113
+ win32gui.SetForegroundWindow(hwnd)
114
+
115
+ if win32gui.GetForegroundWindow() == hwnd:
116
+ return "Focused window (after minimize/restore trick)"
117
+
118
+ return "Could not focus window: OS restrictions"
119
+
120
+ except Exception as e:
121
+ return f"Could not focus window: {type(e).__name__}: {e}"
122
+
123
+
124
+ @mcp.tool("close_window")
125
+ async def close_window(hwnd: int) -> str:
126
+ """Close the specified window."""
127
+ try:
128
+ win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
129
+ return "Closed window"
130
+ except Exception as e:
131
+ return f"Could not close window: {type(e).__name__}: {e}"
132
+
133
+ @mcp.tool("minimize_window")
134
+ async def minimize_window(hwnd: int) -> str:
135
+ """Minimize the specified window."""
136
+ try:
137
+ win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
138
+ return "Minimized window"
139
+ except Exception as e:
140
+ return f"Could not minimize window: {type(e).__name__}: {e}"
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-windows
3
+ Version: 0.0.1
4
+ Summary: Add your description here
5
+ Author-email: TerminalMan <84923604+SecretiveShell@users.noreply.github.com>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: fastmcp>=2.2.0
8
+ Requires-Dist: mcp>=1.6.0
9
+ Requires-Dist: psutil>=7.0.0
10
+ Requires-Dist: pywin32>=310
11
+ Requires-Dist: winrt-runtime>=3.1.0
12
+ Requires-Dist: winrt-windows-data-xml-dom>=3.1.0
13
+ Requires-Dist: winrt-windows-foundation-collections>=3.1.0
14
+ Requires-Dist: winrt-windows-foundation>=3.1.0
15
+ Requires-Dist: winrt-windows-media-control>=3.1.0
16
+ Requires-Dist: winrt-windows-ui-notifications>=3.1.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # mcp-windows
20
+
21
+ MCP server for the windows API.
22
+
23
+ ## Installation
24
+
25
+ add this to your claude mcp config:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "windows": {
31
+ "command": "uvx",
32
+ "args": [
33
+ "mcp-windows"
34
+ ]
35
+ }
36
+ }
37
+ }
38
+
39
+ or locally:
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "windows": {
45
+ "command": "uv",
46
+ "args": [
47
+ "--directory",
48
+ "C:\\Users\\{name}\\Documents\\mcp-windows",
49
+ "run",
50
+ "mcp-windows"
51
+ ]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Features
58
+
59
+ ### Media
60
+
61
+ - get_media_sessions
62
+ - pause
63
+ - play
64
+
65
+ ### Notifications
66
+
67
+ - send_toast
68
+
69
+ ### Window Management
70
+
71
+ - get_foreground_window_info
72
+ - get_window_list
73
+ - focus_window
74
+ - close_window
75
+ - minimize_window
76
+
77
+ ### Monitors
78
+
79
+ - sleep_monitors
80
+ - wake_monitors
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,11 @@
1
+ mcp_windows/__init__.py,sha256=csYpM8A238IdEljN4FLTcQUFLfNDM7xOAI4ZlyyMTrY,75
2
+ mcp_windows/appid.py,sha256=gJN9Ug4S-mdZkpe1447_E7N33pzhPiKnR24dIHMHyZo,1201
3
+ mcp_windows/main.py,sha256=p8MddWAGvZfhgLu_lklzfqfC2f9qbC6FsCWWSKIOqzk,648
4
+ mcp_windows/media.py,sha256=Kf8bvNpd15kt3yZQJ8hUKnGl0ZLq2lg4ximZZZHPpX8,2518
5
+ mcp_windows/monitors.py,sha256=ffFMvp6qEEd84wyPKrEipNSfL81jLAOPPYMORF5wBug,1037
6
+ mcp_windows/notifications.py,sha256=UPsqsmiOvIVak4BC6ol6SrcSl1IC17oGo7h4oXluRO8,940
7
+ mcp_windows/window_management.py,sha256=j_aNIdviNf75xZAPP0DacDAokjUrz17bFRQ_iXa-6sQ,4515
8
+ mcp_windows-0.0.1.dist-info/METADATA,sha256=IQtr3HerrCvSZiefDETiQIpx_S7Dym7Z-ljKEisj9NE,1380
9
+ mcp_windows-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ mcp_windows-0.0.1.dist-info/entry_points.txt,sha256=aTryo6W9hFBSP_7zE0aL0IWlIJ2eDJX7d4qIqHQ76W4,49
11
+ mcp_windows-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcp-windows = mcp_windows:main