Skip to main content
Glama
nickbeentjes

mac-audio-router-mcp

by nickbeentjes

mac-audio-router-mcp

An MCP server that gives AI agents full control over macOS audio routing, device management, volume, and multi-zone playback.

Built for environments where an AI assistant needs to manage audio across multiple outputs (HDMI TVs, Bluetooth speakers, AirPlay devices, satellite speakers) and multiple microphone inputs — without any manual intervention.

Status: Early release. Tested on macOS 15 (Sequoia) with Apple Silicon. Contributions welcome.

Installation

npm install mac-audio-router-mcp

For full device switching (recommended):

brew install switchaudio-osx

Prerequisites

  • macOS 12+ (Monterey or later)

  • Node.js 18+

  • Optional: SwitchAudioSource for device switching beyond built-in outputs

Related MCP server: Automation MCP

Quickstart

Add to your MCP client configuration:

{
  "mcpServers": {
    "audio": {
      "command": "npx",
      "args": ["mac-audio-router-mcp"]
    }
  }
}

For Claude Desktop, add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "audio": {
      "command": "npx",
      "args": ["mac-audio-router-mcp"]
    }
  }
}

For OpenClaw, add to your gateway config:

{
  tools: [{
    type: "mcp",
    command: "npx",
    args: ["mac-audio-router-mcp"]
  }]
}

The agent can now discover and call audio tools. Try:

"What audio devices are connected?"

"Route the audio output to the Bluetooth speaker."

"Set the volume to 40%."

"Say 'dinner is ready' on the salon speaker, then switch back to HDMI."

Tools

Status & Discovery

Tool

Description

get_audio_status

Full system snapshot: devices, zones, processes, volume, routing

list_audio_devices

All connected input/output devices with transport type (Bluetooth, HDMI, USB, AirPlay, built-in)

list_audio_zones

Configured audio zones and their assignments

list_audio_processes

Processes currently using audio hardware

Routing

Tool

Parameters

Description

set_output_device

device_name

Route system output to a named device

set_input_device

device_name

Set the active microphone

activate_zone

zone_id

Apply a pre-configured zone's routing, volume, and device settings

Volume

Tool

Parameters

Description

get_volume

Current volume level (0–100)

set_volume

level

Set volume (0–100)

mute

muted

Mute or unmute output

Playback

Tool

Parameters

Description

play_audio

file_path, volume?

Play a WAV/MP3/AAC/AIFF file

speak_text

text, voice?, rate?

Text-to-speech via macOS say

route_and_play

device_name, action, content, volume?, restore_device?

Atomic: switch device, play/speak, optionally restore

Native Daemon (low-latency)

These tools require the audiod native daemon (CoreAudio HAL in C, sub-millisecond response):

Tool

Parameters

Description

hog_device

device_name, release?

Take/release exclusive access to a device (prevents other apps using it)

set_device_volume

device_name, level

Set volume on a specific device, not just the system default

Zone Management

Tool

Parameters

Description

configure_zone

zone_id, name, description?, output_device?, input_device?, volume?

Create or update a named audio zone

activate_zone

zone_id

Switch all routing to match a zone's configuration

Native Daemon

For sub-millisecond audio control, build and run the native audiod daemon. It talks directly to CoreAudio HAL in C — no AppleScript, no SwitchAudioSource, no subprocess spawning.

cd native
make
./audiod              # listens on /tmp/audiod.sock

The MCP server auto-detects the daemon at startup. When connected, all device operations go through the Unix socket instead of system commands.

Latency comparison:

Operation

System commands

Native daemon

List devices

~2,000ms

~0.2ms

Switch output

~200ms

~0.2ms

Get volume

~150ms

~0.1ms

Set volume

~150ms

~0.1ms

The daemon protocol is newline-delimited JSON over a Unix socket:

# List all devices
echo '{"cmd":"list_devices"}' | nc -U /tmp/audiod.sock

# Switch output
echo '{"cmd":"set_output","name":"SAMSUNG"}' | nc -U /tmp/audiod.sock

# Set volume (0-100)
echo '{"cmd":"set_volume","level":60}' | nc -U /tmp/audiod.sock

# Per-device volume
echo '{"cmd":"set_volume","device":"Mac mini Speakers","level":40}' | nc -U /tmp/audiod.sock

# Take exclusive mic access
echo '{"cmd":"hog","device":"AIWA","release":false}' | nc -U /tmp/audiod.sock

Every response includes _us (microseconds elapsed) for profiling.

Architecture

┌─────────────────────────────────────────────────────┐
│  AI Agent (Claude, GPT, etc.)                       │
│  "Route TTS to the Bluetooth speaker in the salon"  │
└──────────────┬──────────────────────────────────────┘
               │ MCP (stdio / JSON-RPC)
┌──────────────▼──────────────────────────────────────┐
│  mac-audio-router-mcp         (TypeScript)          │
│                                                     │
│  Tools:                                             │
│  ├─ get_audio_status    (system snapshot)            │
│  ├─ list_audio_devices  (CoreAudio enumeration)     │
│  ├─ set_output_device   (route output)              │
│  ├─ set_input_device    (select mic)                │
│  ├─ set_volume / mute   (volume control)            │
│  ├─ configure_zone      (multi-room setup)          │
│  ├─ activate_zone       (switch routing preset)     │
│  ├─ play_audio          (file playback)             │
│  ├─ speak_text          (TTS)                       │
│  └─ route_and_play      (atomic route + play)       │
│                                                     │
│  Primary: Unix socket to audiod daemon               │
│  Fallback: system commands (osascript, afplay, say)  │
└──────────────┬──────────────────────────────────────┘
               │ Unix domain socket (/tmp/audiod.sock)
┌──────────────▼──────────────────────────────────────┐
│  audiod                           (C, ~500 lines)   │
│                                                     │
│  CoreAudio HAL direct access:                       │
│  ├─ AudioObjectGetPropertyData    (enumeration)     │
│  ├─ AudioObjectSetPropertyData    (routing)         │
│  ├─ kAudioDevicePropertyVolumeScalar (volume)       │
│  ├─ kAudioDevicePropertyHogMode   (exclusive lock)  │
│  └─ Device change notifications   (auto-refresh)    │
│                                                     │
│  Response times: 0.1–0.3ms typical                  │
└──────────────┬──────────────────────────────────────┘
               │
┌──────────────▼──────────────────────────────────────┐
│  macOS CoreAudio                                    │
│                                                     │
│  Devices:                                           │
│  ├─ Built-in speakers / headphone jack              │
│  ├─ HDMI / DisplayPort (TVs, monitors)              │
│  ├─ Bluetooth (speakers, headphones)                │
│  ├─ AirPlay (HomePod, Apple TV, smart speakers)     │
│  ├─ USB audio interfaces                            │
│  └─ Virtual (Aggregate, BlackHole, Loopback)        │
└─────────────────────────────────────────────────────┘

Multi-Zone Example

Configure zones for a vessel, smart home, or studio — then let the agent switch between them:

// The agent can do this via tool calls:

// 1. Configure zones
configure_zone({ zone_id: "salon", name: "Salon", output_device: "AIWA AWWS01", volume: 60 })
configure_zone({ zone_id: "bridge", name: "Bridge", output_device: "Samsung TV", volume: 40 })
configure_zone({ zone_id: "cockpit", name: "Cockpit", output_device: "JBL Clip", volume: 80 })

// 2. Route TTS to a specific zone
route_and_play({
  device_name: "AIWA AWWS01",
  action: "speak",
  content: "Anchor watch: wind has shifted to 15 knots from the northwest.",
  restore_device: "Samsung TV"
})

// 3. Switch zones
activate_zone({ zone_id: "bridge" })

Extending

Custom Device Matching

The server identifies device transport types (Bluetooth, HDMI, etc.) by name pattern matching. To add custom patterns, edit the inferTransportType function in src/audio.ts.

Persistent Zone Configuration

Zones are stored in memory by default. To persist across restarts, set the MAC_AUDIO_ROUTER_ZONES environment variable to a JSON file path:

{
  "mcpServers": {
    "audio": {
      "command": "npx",
      "args": ["mac-audio-router-mcp"],
      "env": {
        "MAC_AUDIO_ROUTER_ZONES": "/path/to/zones.json"
      }
    }
  }
}

AirPlay & Apple TV

AirPlay devices appear as standard output devices in macOS. The agent can route to them using set_output_device with the AirPlay device name. For Apple TV, ensure the Mac is connected via AirPlay in System Settings first.

Satellite / Multi-Room

For complex multi-room setups:

  1. Use macOS Aggregate Devices or Multi-Output Devices (via Audio MIDI Setup) to create virtual devices that span multiple physical outputs

  2. Configure each as a zone

  3. The agent can then activate zones to control entire room groupings

Development

git clone https://github.com/nickbeentjes/mac-audio-router-mcp.git
cd mac-audio-router-mcp
npm install
npm run build

Test with the MCP Inspector:

npm run inspect

Run directly:

node build/index.js

Troubleshooting

Issue

Solution

set_output_device fails

Install SwitchAudioSource: brew install switchaudio-osx

Bluetooth device not listed

Pair the device in System Settings > Bluetooth first

AirPlay device not listed

Connect to it once via System Settings > Sound > Output

Volume doesn't change

Some HDMI devices control volume independently

get_audio_status is slow

system_profiler can take 2-3s; install SwitchAudioSource for faster enumeration

Contributing

See CONTRIBUTING.md.

License

Released under the MIT License.

A
license - permissive license
-
quality - not tested
D
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nickbeentjes/mac-audio-router-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server