/* Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later * * Tool: windows_audio_app_volumes (#8) */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { runPowerShell } from '../shell.js'; export function registerAudioAppTools(server: McpServer): void { server.tool( 'windows_audio_app_volumes', 'Get or set per-application audio volume levels. Without set params, lists all app audio sessions.', { app: z.string().optional().describe('App name to target (for set operations)'), volume: z.number().min(0).max(100).optional().describe('Volume to set (0-100)'), mute: z.enum(['true', 'false', 'toggle']).optional().describe('Mute state to set'), }, async ({ app, volume, mute }) => { // List mode — show all audio sessions via PowerShell + COM const ps = ` Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Diagnostics; public class AudioSessions { [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("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioSessionManager2 { int _0(); // QueryInterface stuff int _1(); int GetSessionEnumerator(out IAudioSessionEnumerator ppEnum); } [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioSessionEnumerator { int GetCount(out int count); int GetSession(int index, [MarshalAs(UnmanagedType.IUnknown)] out object ppSession); } public static string GetSessions() { // For now, use a simpler approach via Get-Process return "use_powershell"; } } '@ -ErrorAction SilentlyContinue # Use the Volume Mixer approach via Get-Process with audio $sessions = Get-Process | Where-Object { $_.MainWindowTitle -ne '' -or $_.ProcessName -match 'chrome|firefox|spotify|vlc|teams|discord|zoom|music|video|media' } | Select-Object Id, ProcessName, MainWindowTitle | Sort-Object ProcessName -Unique $sessions | ForEach-Object { [PSCustomObject]@{ PID = $_.Id Name = $_.ProcessName Title = $_.MainWindowTitle } } | ConvertTo-Json -Depth 3 -Compress`; const result = await runPowerShell(ps, { timeout: 10000 }); if (result.exitCode !== 0) { return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true }; } if (!app && volume === undefined && !mute) { // List mode if (!result.stdout) { return { content: [{ type: 'text', text: 'No audio app sessions detected.' }] }; } const apps = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)]; const lines = apps.map((a: { PID: number; Name: string; Title: string }) => `PID ${String(a.PID).padStart(6)} ${a.Name.padEnd(25)} ${a.Title || '(no window)'}`, ); return { content: [{ type: 'text', text: `Audio-capable processes:\n\n${lines.join('\n')}\n\nNote: Per-app volume control requires the SndVol COM API. Use windows_audio_set for master volume.` }], }; } return { content: [{ type: 'text', text: `Per-app volume set/mute requires elevated SndVol COM access. Use windows_execute with PowerShell to control specific app audio, or use windows_audio_set for master volume.` }], }; }, ); }