hammerspoon-mcp
Give AI agents deep control over macOS — windows, audio, Bluetooth, Spaces, Focus mode, and 200+ OS APIs — through one MCP server.
What is this?
An MCP server that bridges AI agents to Hammerspoon — the most powerful macOS automation tool available. Unlike screenshot-and-click tools, this gives agents programmatic OS-level access: manage windows by ID, switch audio devices by name, move apps between Spaces, toggle Focus mode, and subscribe to real-time OS events.
75 tools. 12 subscribable resources. Zero native dependencies.
Quick Start
# 1. Install Hammerspoon (if you haven't)
brew install --cask hammerspoon
# 2. Enable the IPC module (one-time setup)
# Add this line to ~/.hammerspoon/init.lua:
# require("hs.ipc")
# Then reload your Hammerspoon config (Cmd+Alt+Ctrl+R or restart the app)
# 3. (Optional) Install the hs CLI symlink for convenience:
# Run in Hammerspoon console: hs.ipc.cliInstall()
# Or manually: ln -sf /Applications/Hammerspoon.app/Contents/Frameworks/hs/hs ~/.local/bin/hs
# 4. Run the MCP server
npx hammerspoon-mcp
Note: The server auto-discovers the
hsbinary — it checks~/.local/bin/hs,/usr/local/bin/hs,/opt/homebrew/bin/hs, and the app bundle path. You don't needhson your PATH if Hammerspoon is installed to/Applications.
Architecture
┌─────────────────────────────────────────────────────────┐
│ MCP Client (Claude, Cursor, MeshClaw, etc.) │
└──────────────────────────┬──────────────────────────────┘
│ stdio (JSON-RPC)
┌──────────────────────────▼──────────────────────────────┐
│ hammerspoon-mcp (Node.js) │
│ ├─ tools → execSync("hs -c '...'") (request/response)│
│ └─ watchers → named pipe reader (push events) │
└──────────────────────────┬──────────────────────────────┘
│ hs CLI / named pipe
┌──────────────────────────▼──────────────────────────────┐
│ Hammerspoon.app (Lua runtime + ObjC bridge) │
│ 200+ modules: window, screen, spaces, audio, wifi, │
│ bluetooth, battery, USB, filesystem, accessibility... │
└──────────────────────────┬──────────────────────────────┘
│
macOS APIs (CoreGraphics, IOKit, etc.)
MCP Client Configuration
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
Claude Code
claude mcp add hammerspoon -- npx hammerspoon-mcp
Cursor / VS Code / Copilot
Add to .vscode/mcp.json:
{
"servers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
Kiro
Add to ~/.aws/amazonq/mcp.json:
{
"mcpServers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
Tools
🪟 Window Management (7)
| Tool | Description |
|---|---|
hs_list_windows | All visible windows with app, title, frame, id |
hs_focus_window | Bring window to front by id or title match |
hs_move_window | Move/resize window by id |
hs_window_layout | 16 presets (halves, thirds, quarters, grid, cascade, center…) + custom fractional layouts |
hs_save_layout | Snapshot all window positions as a named layout |
hs_restore_layout | Restore a saved layout (matches by app + title) |
hs_list_layouts | List saved layout names |
🖥️ Spaces / Virtual Desktops (9)
| Tool | Description |
|---|---|
hs_spaces_list | All spaces per screen with active markers |
hs_spaces_active | Currently focused space + screen |
hs_spaces_goto | Switch by ID or index |
hs_spaces_move_window | Move window to another space (optionally follow) |
hs_spaces_add | Create a new space |
hs_spaces_remove | Remove a space (safety checks) |
hs_spaces_windows | Windows on a specific space |
hs_spaces_window_spaces | Which space(s) a window is on |
hs_spaces_mission_control | Toggle Mission Control / Exposé / Launchpad / Show Desktop |
📱 Application Control (3)
| Tool | Description |
|---|---|
hs_list_apps | Running applications with bundle ID, PID, hidden state |
hs_launch_app | Launch or activate an app by name |
hs_kill_app | Quit an app |
⌨️ Input (3)
| Tool | Description |
|---|---|
hs_click | Click at coordinates with button + modifier options |
hs_type | Type text (Unicode, CJK, emoji) |
hs_hotkey | Press key combo (e.g. "cmd+shift+4") |
🖼️ Screen (3)
| Tool | Description |
|---|---|
hs_screenshot | Capture full screen or region (base64 PNG) |
hs_screens | Display info (resolution, name, rotation) |
hs_mouse_position | Current cursor coordinates |
🔊 Audio Devices (6)
| Tool | Description |
|---|---|
hs_audio_devices | List all input/output with volume, mute, transport type |
hs_audio_default | Get/set default devices (fuzzy name match) |
hs_audio_volume | Volume on any specific device (0–100) |
hs_audio_mute | Mute/unmute/toggle any device |
hs_audio_datasources | List/switch data sources (speakers vs headphones) |
hs_audio_watch | Watch for device changes |
🔵 Bluetooth (5)
| Tool | Description |
|---|---|
hs_bluetooth_status | Power state and discoverability |
hs_bluetooth_power | On / off / toggle |
hs_bluetooth_devices | List paired or connected devices |
hs_bluetooth_connect | Connect by name (fuzzy) or MAC address |
hs_bluetooth_disconnect | Disconnect a device |
Requires blueutil:
brew install blueutil
🌙 Focus / Do Not Disturb (6)
| Tool | Description |
|---|---|
hs_focus_status | Check if DND/Focus is active + profile name |
hs_focus_toggle | Toggle via Control Center (no setup needed) |
hs_focus_shortcut | Enable/disable via macOS Shortcuts (most reliable) |
hs_focus_install_shortcut | One-time setup guide |
hs_focus_schedule | DND for N minutes with auto-disable |
hs_focus_cancel_timer | Cancel scheduled disable |
🔋 System State (6)
| Tool | Description |
|---|---|
hs_battery | Charge %, power source, time remaining |
hs_wifi | Current SSID and interface details |
hs_volume | System volume get/set |
hs_dark_mode | Get/toggle dark mode |
hs_notify | Post a macOS notification |
hs_clipboard | Read/write system pasteboard |
🔆 Display (1)
| Tool | Description |
|---|---|
hs_brightness | Get/set screen brightness (0–100) |
⏱️ Timers (3)
| Tool | Description |
|---|---|
hs_timer_start | Named countdown with notification on fire |
hs_timer_cancel | Cancel by name |
hs_timer_list | List active timers |
📁 Finder / Files (4)
| Tool | Description |
|---|---|
hs_finder_selection | Get currently selected Finder items |
hs_finder_open | Open/reveal path in Finder |
hs_trash | Move to Trash (reversible) |
hs_quicklook | Quick Look preview |
🛠️ Utilities (5)
| Tool | Description |
|---|---|
hs_system_info | CPU, memory, thermal state, uptime |
hs_caffeinate | Prevent sleep for N minutes |
hs_execute | Run shell command (user environment) |
hs_open_url | Open URL in browser or specific app |
hs_alert | HUD-style on-screen overlay message |
👁️ Watchers — Live OS Event Streams (13)
| Tool | Events |
|---|---|
hs_watch_apps | App activated, launched, terminated, hidden |
hs_watch_wifi | Network changes |
hs_watch_usb | Device connect/disconnect |
hs_watch_battery | Charge level, power source |
hs_watch_screens | Display configuration |
hs_watch_paths | Filesystem changes |
hs_watch_sleep | Sleep/wake/lock/unlock |
hs_watch_clipboard | Every pasteboard change |
hs_watch_clipboard_history | Ring buffer with source app tracking |
hs_get_clipboard_history | Read the full buffer |
hs_watch_urls | Incoming hammerspoon://mcp/... URLs |
hs_unwatch | Stop a watcher |
hs_list_watchers | List active watchers |
🧪 Escape Hatch (1)
| Tool | Description |
|---|---|
hs_eval | Execute arbitrary Lua in the Hammerspoon runtime |
This gives access to all 200+ Hammerspoon modules not covered by dedicated tools — USB details, serial ports, network interfaces, Canvas drawing, menubar items, etc.
Resources (Subscriptions)
MCP clients can subscribe to resource URIs and receive push notifications when OS events fire:
| URI | Triggers |
|---|---|
watcher://apps | App lifecycle events |
watcher://wifi | Network changes |
watcher://usb | USB device events |
watcher://battery | Battery/power changes |
watcher://screens | Display reconfiguration |
watcher://paths | Filesystem changes |
watcher://sleep | Sleep/wake/lock events |
watcher://clipboard | Pasteboard changes |
watcher://clipboard_history | Clipboard ring buffer updates |
watcher://urls | Incoming URL events |
watcher://audio | Audio device changes |
hammerspoon://system | System info snapshot |
How event streaming works
Agent calls: hs_watch_wifi
→ Server injects Lua watcher into Hammerspoon runtime
→ macOS WiFi change fires
→ Watcher writes JSON to named pipe (/tmp/hs-mcp-events.pipe)
→ Node reader emits event
→ Server calls sendResourceUpdated("watcher://wifi")
→ Client reads resource → gets { ssid: "NewNetwork", ... }
Examples
"Tile my editor and terminal side by side"
hs_window_layout({ preset: "side-by-side" })
"Switch my audio to AirPods"
hs_audio_default({ output: "airpods" })
→ "set default output: AirPods Pro"
"Move Slack to Space 3"
hs_list_windows({ app: "Slack" }) → { id: 4523, ... }
hs_spaces_move_window({ windowId: 4523, spaceIndex: 3 })
"Start a 25-minute focus session"
hs_focus_schedule({ minutes: 25 })
hs_timer_start({ name: "pomodoro", minutes: 25, message: "Break time!" })
"Notify me when I switch away from my IDE"
hs_watch_apps()
→ Events stream to watcher://apps whenever focus changes
"Connect my headphones and set volume to 40%"
hs_bluetooth_connect({ device: "WH-1000XM5" })
hs_audio_volume({ device: "WH-1000XM5", volume: 40 })
Compared to Alternatives
| Capability | hammerspoon-mcp | mac-use-mcp | macos-automator-mcp |
|---|---|---|---|
| Window management by ID | ✅ | ❌ | ❌ |
| Virtual desktop (Spaces) control | ✅ | ❌ | ❌ |
| Audio device switching | ✅ | ❌ | ❌ |
| Bluetooth control | ✅ | ❌ | ❌ |
| Focus / DND mode | ✅ | ❌ | ❌ |
| Real-time OS event subscriptions | ✅ | ❌ | ❌ |
| Clipboard history | ✅ | ✅ | ❌ |
| Arbitrary OS scripting | ✅ (Lua) | ❌ | ✅ (AppleScript) |
| Screenshot | ✅ | ✅ | ❌ |
| Click / type / hotkeys | ✅ | ✅ | ❌ |
| Accessibility tree queries | via hs_eval | ✅ | ❌ |
| Zero-install (no prerequisites) | ❌ | ✅ | ✅ |
| Total tools | 75 | 18 | ~10 |
Prerequisites
| Requirement | Install |
|---|---|
| macOS 13+ | — |
| Node.js 22+ | brew install node |
| Hammerspoon | brew install --cask hammerspoon |
hs.ipc module | Add require("hs.ipc") to ~/.hammerspoon/init.lua |
| blueutil (optional) | brew install blueutil (for Bluetooth tools) |
The
hsCLI symlink (hs.ipc.cliInstall()) is optional — the server auto-discovers the binary inside the Hammerspoon app bundle.
Verify setup
# If hs is on your PATH:
hs -c 'return "ok"'
# Or directly via the app bundle:
/Applications/Hammerspoon.app/Contents/Frameworks/hs/hs -c 'return "ok"'
# Should print: ok
If this works, npx hammerspoon-mcp will connect successfully.
Permissions
| Permission | Required for | How to grant |
|---|---|---|
| Accessibility | Window, input, app control | System Settings → Privacy & Security → Accessibility → add your terminal |
| Screen Recording | hs_screenshot only | System Settings → Privacy & Security → Screen Recording |
Project Structure
hammerspoon-mcp/
├── src/
│ ├── index.ts MCP server entry point + resource registration
│ ├── bridge.ts hs CLI bridge (auto-discovers binary, eval, evalJson)
│ ├── tools.ts Core tools: window, app, input, screen, system, eval (23)
│ ├── watcher-tools.ts Watcher management tools (13)
│ ├── watchers.ts Named pipe event bridge
│ ├── spaces-tools.ts Spaces / virtual desktop tools (9)
│ ├── audio-tools.ts Audio device management (6)
│ ├── focus-tools.ts DND / Focus mode (6)
│ ├── bluetooth-tools.ts Bluetooth via blueutil (5)
│ └── extra-tools.ts Brightness, timers, system info, Finder, utils (13)
├── package.json
├── tsconfig.json
├── LICENSE MIT
└── README.md
Development
git clone <repo>
cd hammerspoon-mcp
npm install
npm run dev # Run with tsx (hot reload)
npm run build # Compile to dist/
npm start # Run compiled version
How It Works
- Startup — the bridge auto-discovers the
hsbinary (checks~/.local/bin,/usr/local/bin,/opt/homebrew/bin, app bundle) and validates connectivity - Tools execute synchronously via
hs -c '<lua>'— typically 30–80ms per call. Stdout noise (-- Loading extension: ...) is stripped automatically - Watchers inject Lua code that writes JSON lines to a FIFO pipe; a Node reader process emits them as MCP resource updates
- Resources buffer the last 50 events per watcher; clients read on-demand after receiving update notifications
- Graceful shutdown stops all watchers and cleans up the pipe on SIGINT/SIGTERM
Known Limitations
- Node.js 22+ is specified in
enginesbut the server builds and runs fine on Node 20 (tested) hs_spaces_goto,addSpaceToScreen,removeSpacebriefly flash Mission Control (macOS limitation — enable "Reduce motion" to minimize)- Bluetooth tools require blueutil (
brew install blueutil) - Focus/DND reading uses heuristics (no public Apple API exists)
hs_screenshotreturns base64 — large for full-screen captures- Saved layouts persist only in Hammerspoon's runtime (lost on HS reload)
- Named pipe path is
/tmp/hs-mcp-events.pipe— only one server instance at a time
License
MIT © 2026 Mohammed Babur