#
tokens: 8882/50000 17/17 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── .python-version
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│   └── mcp_windows
│       ├── __init__.py
│       ├── appid.py
│       ├── audio.py
│       ├── clipboard.py
│       ├── keyboard.py
│       ├── main.py
│       ├── media.py
│       ├── monitors.py
│       ├── notifications.py
│       ├── screenshot.py
│       ├── startmenu.py
│       ├── theme.py
│       └── window_management.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.13
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # mcp-windows
 2 | 
 3 | MCP server for the windows API.
 4 | 
 5 | ## Installation
 6 | 
 7 | add this to your claude mcp config:
 8 | 
 9 | ```json
10 | {
11 |   "mcpServers": {
12 |     "windows": {
13 |       "command": "uvx",
14 |       "args": [
15 |         "mcp-windows"
16 |       ]
17 |     }
18 |   }
19 | }
20 | ```
21 | 
22 | or locally:
23 | 
24 | ```json
25 | {
26 |   "mcpServers": {
27 |     "windows": {
28 |       "command": "uv",
29 |       "args": [
30 |         "--directory",
31 |         "C:\\Users\\{name}\\Documents\\mcp-windows",
32 |         "run",
33 |         "mcp-windows"
34 |       ]
35 |     }
36 |   }
37 | }
38 | ```
39 | 
40 | ## Features
41 | 
42 | ### Media
43 | 
44 | - get_media_sessions
45 | - pause
46 | - play
47 | - next
48 | - previous
49 | 
50 | ### Notifications
51 | 
52 | - send_toast
53 | 
54 | ### Window Management
55 | 
56 | - get_foreground_window_info
57 | - get_window_list
58 | - focus_window
59 | - close_window
60 | - minimize_window
61 | 
62 | ### screenshot
63 | 
64 | - screenshot_window
65 | 
66 | ### Monitors
67 | 
68 | - sleep_monitors
69 | - wake_monitors
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 | 
86 | ## License
87 | 
88 | MIT
```

--------------------------------------------------------------------------------
/src/mcp_windows/__init__.py:
--------------------------------------------------------------------------------

```python
1 | from mcp_windows.main import mcp
2 | 
3 | def main() -> None:
4 |     mcp.run("stdio")
5 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/clipboard.py:
--------------------------------------------------------------------------------

```python
 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"
26 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/notifications.py:
--------------------------------------------------------------------------------

```python
 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"
```

--------------------------------------------------------------------------------
/src/mcp_windows/monitors.py:
--------------------------------------------------------------------------------

```python
 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}"
36 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/appid.py:
--------------------------------------------------------------------------------

```python
 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()
35 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-windows"
 3 | version = "0.1.0" 
 4 | description = "MCP server for the windows API."
 5 | readme = "README.md"
 6 | authors = [
 7 |     { name = "TerminalMan", email = "[email protected]" }
 8 | ]
 9 | requires-python = ">=3.13"
10 | dependencies = [
11 |     "comtypes>=1.4.10",
12 |     "fastmcp>=2.2.0",
13 |     "mcp>=1.6.0",
14 |     "pillow>=11.2.1",
15 |     "psutil>=7.0.0",
16 |     "pycaw>=20240210",
17 |     "pywin32>=310",
18 |     "winrt-runtime>=3.1.0",
19 |     "winrt-windows-data-xml-dom>=3.1.0",
20 |     "winrt-windows-foundation>=3.1.0",
21 |     "winrt-windows-foundation-collections>=3.1.0",
22 |     "winrt-windows-media-control>=3.1.0",
23 |     "winrt-windows-storage>=3.1.0",
24 |     "winrt-windows-system>=3.1.0",
25 |     "winrt-windows-ui-notifications>=3.1.0",
26 | ]
27 | 
28 | [project.scripts]
29 | mcp-windows = "mcp_windows:main"
30 | 
31 | [build-system]
32 | requires = ["hatchling"]
33 | build-backend = "hatchling.build"
34 | 
35 | [project.urls]
36 | Homepage = "https://github.com/SecretiveShell/mcp-windows"
37 | Documentation = "https://github.com/SecretiveShell/mcp-windows"
38 | Repository = "https://github.com/SecretiveShell/mcp-windows.git"
39 | Issues = "https://github.com/SecretiveShell/mcp-windows/issues"
40 | Changelog = "https://github.com/SecretiveShell/mcp-windows/commits/master/"
41 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/startmenu.py:
--------------------------------------------------------------------------------

```python
 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"
```

--------------------------------------------------------------------------------
/src/mcp_windows/main.py:
--------------------------------------------------------------------------------

```python
 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 | 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
12 | from mcp_windows.keyboard import mcp as keyboard_mcp
13 | from mcp_windows.audio import mcp as audio_mcp
14 | 
15 | sep = environ.get("FASTMCP_TOOL_SEPARATOR", "_")
16 | 
17 | mcp: FastMCP = FastMCP(
18 |     name="windows",
19 | )
20 | 
21 | mcp.mount("media", media_mcp, tool_separator=sep)
22 | mcp.mount("notifications", notifications_mcp, tool_separator=sep)
23 | mcp.mount("window_management", window_management_mcp, tool_separator=sep)
24 | mcp.mount("monitors", monitors_mcp, tool_separator=sep)
25 | mcp.mount("clipboard", clipboard_mcp, tool_separator=sep)
26 | mcp.mount("screenshot", screenshot_mcp, tool_separator=sep)
27 | mcp.mount("theme", theme_mcp, tool_separator=sep)
28 | mcp.mount("startmenu", startmenu_mcp, tool_separator=sep)
29 | mcp.mount("keyboard", keyboard_mcp, tool_separator=sep)
30 | mcp.mount("audio", audio_mcp, tool_separator=sep)
```

--------------------------------------------------------------------------------
/src/mcp_windows/theme.py:
--------------------------------------------------------------------------------

```python
 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"
42 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/audio.py:
--------------------------------------------------------------------------------

```python
 1 | from fastmcp import FastMCP
 2 | import asyncio
 3 | from ctypes import POINTER, cast
 4 | from comtypes import CLSCTX_ALL
 5 | from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
 6 | 
 7 | mcp: FastMCP = FastMCP(
 8 |     name="audio"
 9 | )
10 | 
11 | def _get_volume_interface():
12 |     devices = AudioUtilities.GetSpeakers()
13 |     interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
14 |     return cast(interface, POINTER(IAudioEndpointVolume))
15 | 
16 | async def run_in_executor(func, *args):
17 |     loop = asyncio.get_event_loop()
18 |     return await loop.run_in_executor(None, lambda: func(*args))
19 | 
20 | @mcp.tool("get_volume")
21 | async def get_volume() -> str:
22 |     """Return the master volume level as a percentage (0-100)."""
23 |     volume = await run_in_executor(_get_volume_interface)
24 |     level = await run_in_executor(volume.GetMasterVolumeLevelScalar)
25 |     return f"{int(level * 100)}"
26 | 
27 | @mcp.tool("set_volume")
28 | async def set_volume(level: int) -> str:
29 |     """Set the master volume level (0-100)."""
30 |     if not 0 <= level <= 100:
31 |         return "Volume must be between 0 and 100"
32 |     volume = await run_in_executor(_get_volume_interface)
33 |     await run_in_executor(volume.SetMasterVolumeLevelScalar, level / 100.0, None)
34 |     return f"Volume set to {level}%"
35 | 
36 | @mcp.tool("mute")
37 | async def mute() -> str:
38 |     volume = await run_in_executor(_get_volume_interface)
39 |     await run_in_executor(volume.SetMute, 1, None)
40 |     return "Muted"
41 | 
42 | @mcp.tool("unmute")
43 | async def unmute() -> str:
44 |     volume = await run_in_executor(_get_volume_interface)
45 |     await run_in_executor(volume.SetMute, 0, None)
46 |     return "Unmuted"
47 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/keyboard.py:
--------------------------------------------------------------------------------

```python
 1 | import win32api
 2 | import win32con
 3 | import asyncio
 4 | from fastmcp import FastMCP
 5 | 
 6 | mcp: FastMCP = FastMCP(
 7 |     name="keyboard",
 8 | )
 9 | 
10 | @mcp.tool("type_text")
11 | async def type_text(text: str, delay: float = 0.05):
12 |     """Simulate typing text with a delay between each character. ONLY USE THIS AS A LAST RESORT."""
13 |     for char in text:
14 |         vk = ord(char.upper())
15 |         needs_shift = char.isupper() or not char.isalnum()  # crude shift detection
16 | 
17 |         if needs_shift:
18 |             win32api.keybd_event(win32con.VK_SHIFT, 0, 0, 0)
19 | 
20 |         win32api.keybd_event(vk, 0, 0, 0)
21 |         win32api.keybd_event(vk, 0, win32con.KEYEVENTF_KEYUP, 0)
22 | 
23 |         if needs_shift:
24 |             win32api.keybd_event(win32con.VK_SHIFT, 0, win32con.KEYEVENTF_KEYUP, 0)
25 | 
26 |         await asyncio.sleep(delay)
27 | 
28 | 
29 | VK_LOOKUP = {
30 |     'ctrl': win32con.VK_CONTROL,
31 |     'shift': win32con.VK_SHIFT,
32 |     'alt': win32con.VK_MENU,
33 |     'esc': win32con.VK_ESCAPE,
34 |     'enter': win32con.VK_RETURN,
35 |     'tab': win32con.VK_TAB,
36 |     'space': win32con.VK_SPACE,
37 |     'left': win32con.VK_LEFT,
38 |     'right': win32con.VK_RIGHT,
39 |     'up': win32con.VK_UP,
40 |     'down': win32con.VK_DOWN,
41 | }
42 | 
43 | @mcp.tool("send_keyboard_shortcut")
44 | async def press_keys(keys: list[str]):
45 |     """Simulate pressing a combination of keys. Use any of the following special keys: ctrl, shift, alt, esc, enter, tab, space, left, right, up, down as well the regular characters. ONLY USE THIS AS A LAST RESORT."""
46 |     vk_keys = []
47 |     for k in keys:
48 |         k_lower = k.lower()
49 |         vk = VK_LOOKUP.get(k_lower, ord(k.upper()))
50 |         vk_keys.append(vk)
51 | 
52 |     for vk in vk_keys:
53 |         win32api.keybd_event(vk, 0, 0, 0)
54 |     await asyncio.sleep(0.05)
55 |     for vk in reversed(vk_keys):
56 |         win32api.keybd_event(vk, 0, win32con.KEYEVENTF_KEYUP, 0)
57 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/screenshot.py:
--------------------------------------------------------------------------------

```python
 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}"
```

--------------------------------------------------------------------------------
/src/mcp_windows/media.py:
--------------------------------------------------------------------------------

```python
  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"
132 | 
```

--------------------------------------------------------------------------------
/src/mcp_windows/window_management.py:
--------------------------------------------------------------------------------

```python
  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}"
141 | 
```