/* Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later * * Tools: windows_audio_get (#6), windows_audio_set (#7) * Uses compiled SetMute.exe for reliable COM audio control. */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { runPowerShell } from '../shell.js'; const AUDIO_PS = ` Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public class WinAudio { [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDeviceEnumerator { int EnumAudioEndpoints(int dataFlow, int dwStateMask, out IntPtr ppDevices); int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice ppEndpoint); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); } [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioEndpointVolume { int RegisterControlChangeNotify(IntPtr pNotify); int UnregisterControlChangeNotify(IntPtr pNotify); int GetChannelCount(out int pnChannelCount); int SetMasterVolumeLevel(float fLevelDB, ref Guid pguidEventContext); int SetMasterVolumeLevelScalar(float fLevel, ref Guid pguidEventContext); int GetMasterVolumeLevel(out float pfLevelDB); int GetMasterVolumeLevelScalar(out float pfLevel); int SetChannelVolumeLevel(int nChannel, float fLevelDB, ref Guid pguidEventContext); int SetChannelVolumeLevelScalar(int nChannel, float fLevel, ref Guid pguidEventContext); int GetChannelVolumeLevel(int nChannel, out float pfLevelDB); int GetChannelVolumeLevelScalar(int nChannel, out float pfLevel); int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, ref Guid pguidEventContext); int GetMute([MarshalAs(UnmanagedType.Bool)] out bool pbMute); } private static IAudioEndpointVolume GetVolume() { var type = Type.GetTypeFromCLSID(new Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")); var enumerator = (IMMDeviceEnumerator)Activator.CreateInstance(type); IMMDevice device; enumerator.GetDefaultAudioEndpoint(0, 1, out device); var iid = new Guid("5CDF2C82-841E-4546-9722-0CF74078229A"); object obj; device.Activate(ref iid, 0x17, IntPtr.Zero, out obj); return (IAudioEndpointVolume)obj; } public static float GetVolumeLevel() { var vol = GetVolume(); float level; vol.GetMasterVolumeLevelScalar(out level); return level; } public static bool GetMute() { var vol = GetVolume(); bool muted; vol.GetMute(out muted); return muted; } public static void SetVolumeLevel(float level) { var vol = GetVolume(); var ctx = Guid.Empty; vol.SetMasterVolumeLevelScalar(level, ref ctx); } public static void SetMute(bool mute) { var vol = GetVolume(); var ctx = Guid.Empty; vol.SetMute(mute, ref ctx); } } '@ -ErrorAction Stop `; export function registerAudioTools(server: McpServer): void { server.tool( 'windows_audio_get', 'Get current audio state: volume level (0-100), mute status, and default playback device.', {}, async () => { const ps = ` ${AUDIO_PS} $volume = [math]::Round([WinAudio]::GetVolumeLevel() * 100) $muted = [WinAudio]::GetMute() $device = (Get-CimInstance Win32_SoundDevice | Where-Object { $_.Status -eq 'OK' } | Select-Object -First 1).Name [PSCustomObject]@{ volume = $volume muted = $muted device = $device } | ConvertTo-Json -Compress`; const result = await runPowerShell(ps, { timeout: 60000 }); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const state = JSON.parse(result.stdout); const muteIcon = state.muted ? '🔇' : (state.volume > 50 ? '🔊' : '🔉'); return { content: [{ type: 'text', text: `${muteIcon} Volume: ${state.volume}%${state.muted ? ' (MUTED)' : ''}\nDevice: ${state.device || 'Unknown'}`, }], }; }, ); server.tool( 'windows_audio_set', 'Set audio volume (0-100), mute/unmute, or toggle mute.', { volume: z.number().min(0).max(100).optional().describe('Volume level 0-100'), mute: z.enum(['true', 'false', 'toggle']).optional().describe('Mute state: true, false, or toggle'), }, async ({ volume, mute }) => { const commands: string[] = [AUDIO_PS]; if (volume !== undefined) { commands.push(`[WinAudio]::SetVolumeLevel(${volume / 100})`); } if (mute === 'toggle') { commands.push(`[WinAudio]::SetMute(-not [WinAudio]::GetMute())`); } else if (mute === 'true') { commands.push(`[WinAudio]::SetMute($true)`); } else if (mute === 'false') { commands.push(`[WinAudio]::SetMute($false)`); } // Read back state commands.push(` $vol = [math]::Round([WinAudio]::GetVolumeLevel() * 100) $m = [WinAudio]::GetMute() [PSCustomObject]@{ volume = $vol; muted = $m } | ConvertTo-Json -Compress`); const result = await runPowerShell(commands.join('\n')); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } const state = JSON.parse(result.stdout); return { content: [{ type: 'text', text: `Volume set to ${state.volume}%${state.muted ? ' (MUTED)' : ''}`, }], }; }, ); }