/* Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later * * Tools: windows_theme_get (#52), windows_theme_set (#53), * windows_focus_mode (#55), windows_default_apps (#56) */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { runPowerShell } from '../shell.js'; export function registerThemeTools(server: McpServer): void { server.tool( 'windows_theme_get', 'Get current Windows theme: dark/light mode, accent color, wallpaper, transparency, taskbar alignment.', {}, async () => { const ps = ` $personalize = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' $accent = 'HKCU:\\Software\\Microsoft\\Windows\\DWM' $taskbar = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced' $wallpaper = (Get-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name Wallpaper -ErrorAction SilentlyContinue).Wallpaper $appsDark = (Get-ItemProperty -Path $personalize -Name AppsUseLightTheme -ErrorAction SilentlyContinue).AppsUseLightTheme -eq 0 $systemDark = (Get-ItemProperty -Path $personalize -Name SystemUsesLightTheme -ErrorAction SilentlyContinue).SystemUsesLightTheme -eq 0 $transparency = (Get-ItemProperty -Path $personalize -Name EnableTransparency -ErrorAction SilentlyContinue).EnableTransparency -eq 1 $accentColor = (Get-ItemProperty -Path $accent -Name AccentColor -ErrorAction SilentlyContinue).AccentColor $taskbarAlign = (Get-ItemProperty -Path $taskbar -Name TaskbarAl -ErrorAction SilentlyContinue).TaskbarAl $accentHex = if ($accentColor) { $b = ($accentColor -band 0xFF0000) -shr 16 $g = ($accentColor -band 0x00FF00) -shr 8 $r = ($accentColor -band 0x0000FF) '#{0:X2}{1:X2}{2:X2}' -f $r, $g, $b } else { 'Unknown' } [PSCustomObject]@{ AppsDarkMode = $appsDark SystemDarkMode = $systemDark AccentColor = $accentHex Transparency = $transparency Wallpaper = $wallpaper TaskbarAlignment = if ($taskbarAlign -eq 0) { 'Left' } else { 'Center' } } | ConvertTo-Json -Compress`; const result = await runPowerShell(ps, { timeout: 10000 }); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const t = JSON.parse(result.stdout); return { content: [{ type: 'text', text: [ `Dark mode: Apps=${t.AppsDarkMode}, System=${t.SystemDarkMode}`, `Accent color: ${t.AccentColor}`, `Transparency: ${t.Transparency ? 'On' : 'Off'}`, `Taskbar: ${t.TaskbarAlignment}`, `Wallpaper: ${t.Wallpaper || '(none)'}`, ].join('\n'), }], }; }, ); server.tool( 'windows_theme_set', 'Set dark/light mode, accent color, wallpaper, transparency, or taskbar alignment.', { dark_mode: z.enum(['on', 'off']).optional().describe('Set dark mode for apps and system'), wallpaper: z.string().optional().describe('Wallpaper file path'), wallpaper_fit: z.enum(['fill', 'fit', 'stretch', 'tile', 'center', 'span']).optional().describe('Wallpaper fit mode'), transparency: z.enum(['on', 'off']).optional().describe('Transparency effects'), taskbar_align: z.enum(['left', 'center']).optional().describe('Taskbar alignment'), }, async ({ dark_mode, wallpaper, wallpaper_fit, transparency, taskbar_align }) => { const commands: string[] = []; if (dark_mode) { const val = dark_mode === 'on' ? 0 : 1; commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name AppsUseLightTheme -Value ${val}`); commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name SystemUsesLightTheme -Value ${val}`); commands.push(`"Dark mode: ${dark_mode}"`); } if (transparency) { const val = transparency === 'on' ? 1 : 0; commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name EnableTransparency -Value ${val}`); commands.push(`"Transparency: ${transparency}"`); } if (taskbar_align) { const val = taskbar_align === 'left' ? 0 : 1; commands.push(`Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced' -Name TaskbarAl -Value ${val}`); commands.push(`"Taskbar: ${taskbar_align}"`); } if (wallpaper) { const fitMap: Record = { fill: '10', fit: '6', stretch: '2', tile: '0', center: '0', span: '22' }; const fit = wallpaper_fit || 'fill'; commands.push(` Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name WallpaperStyle -Value '${fitMap[fit]}' Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name TileWallpaper -Value '${fit === 'tile' ? '1' : '0'}' Add-Type -TypeDefinition 'using System.Runtime.InteropServices; public class Wallpaper { [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); }' [Wallpaper]::SystemParametersInfo(0x0014, 0, '${wallpaper.replace(/'/g, "''")}', 0x01 -bor 0x02) | Out-Null "Wallpaper set: ${wallpaper} (${fit})" `); } if (commands.length === 0) { return { content: [{ type: 'text', text: 'No changes specified.' }], isError: true }; } const result = await runPowerShell(commands.join('\n'), { timeout: 15000 }); return { content: [{ type: 'text', text: result.stdout || result.stderr }], isError: result.exitCode !== 0, }; }, ); server.tool( 'windows_focus_mode', 'Get or set Windows Focus Assist / Do Not Disturb mode.', { action: z.enum(['get', 'priority', 'alarms', 'off']).default('get').describe('Get status or set mode'), }, async ({ action }) => { if (action === 'get') { const ps = ` $regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.notifications.quiethourssettings\\windows.data.notifications.quiethourssettings' $mode = 'Unknown' try { $val = (Get-ItemProperty -Path $regPath -ErrorAction Stop).Data if ($val) { # The focus assist state is encoded in the binary blob $mode = 'Check via Settings app (binary registry format)' } } catch { $mode = 'Off (or unable to read)' } "Focus Assist: $mode"`; const result = await runPowerShell(ps, { timeout: 10000 }); return { content: [{ type: 'text', text: result.stdout || result.stderr }] }; } // Setting focus assist requires ms-settings URI const ps = `Start-Process 'ms-settings:quiethours'; "Opened Focus Assist settings. Mode requested: ${action}"`; const result = await runPowerShell(ps, { timeout: 10000 }); return { content: [{ type: 'text', text: result.stdout || result.stderr }] }; }, ); server.tool( 'windows_default_apps', 'Get default applications or open the default apps settings page.', { action: z.enum(['get', 'open_settings']).default('get').describe('Get defaults or open settings'), extension: z.string().optional().describe('File extension to check (e.g. ".pdf")'), }, async ({ action, extension }) => { if (action === 'open_settings') { await runPowerShell('Start-Process "ms-settings:defaultapps"'); return { content: [{ type: 'text', text: 'Opened Default Apps settings.' }] }; } if (extension) { const ps = ` $assoc = cmd /c assoc ${extension} 2>$null $ftype = if ($assoc) { $type = ($assoc -split '=')[1]; cmd /c ftype $type 2>$null } else { $null } [PSCustomObject]@{ Extension = '${extension}' FileType = if ($assoc) { ($assoc -split '=')[1] } else { 'Not associated' } OpensWith = if ($ftype) { ($ftype -split '=')[1] } else { 'Unknown' } } | ConvertTo-Json -Compress`; const result = await runPowerShell(ps, { timeout: 10000 }); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const info = JSON.parse(result.stdout); return { content: [{ type: 'text', text: `${info.Extension} -> ${info.FileType} -> ${info.OpensWith}` }] }; } // List common defaults const ps = ` $defaults = @('.html','.pdf','.txt','.jpg','.png','.mp3','.mp4','.zip') | ForEach-Object { $ext = $_ $assoc = cmd /c assoc $ext 2>$null $type = if ($assoc) { ($assoc -split '=')[1] } else { 'N/A' } [PSCustomObject]@{ Extension = $ext; FileType = $type } } $defaults | ConvertTo-Json -Depth 3 -Compress`; const result = await runPowerShell(ps, { timeout: 15000 }); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const defaults = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)]; const lines = defaults.map((d: { Extension: string; FileType: string }) => ` ${d.Extension.padEnd(8)} ${d.FileType}`, ); return { content: [{ type: 'text', text: `Default file associations:\n${lines.join('\n')}` }] }; }, ); }