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
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
206 lines
8.8 KiB
TypeScript
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')}` }] };
|
|
},
|
|
);
|
|
}
|