Files
mcp-windows/src/tools/theme.ts
T
Jonathan Miller f996c7a788
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
feat: implement v2.1 appearance + v2.2 security/maintenance (12 new tools)
v2.1 — Appearance & Desktop:
- tools/theme.ts: windows_theme_get, _set, _focus_mode, _default_apps (#52-56)
- tools/virtual_desktop.ts: windows_virtual_desktop (#54)

v2.2 — Security & Maintenance:
- tools/firewall.ts: windows_firewall_get, _manage (#57, #58)
- tools/maintenance.ts: windows_updates, _event_log, _restore_point,
  _certificate_list, _performance_monitor (#59-63)

Total: 63 tools registered (v1.0-v1.4 + v2.0-v2.2 complete)

Authored-by: Moko Consulting
2026-05-25 22:51:14 -05:00

206 lines
8.8 KiB
TypeScript

/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* 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<string, string> = { 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')}` }] };
},
);
}