hammerspoon-mcp
Give AI agents deep control over macOS — windows, audio, Bluetooth, Spaces, Focus mode, and 200+ OS APIs — through one MCP server.
README
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
<details> <summary><strong>Claude Desktop</strong></summary>
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
</details>
<details> <summary><strong>Claude Code</strong></summary>
claude mcp add hammerspoon -- npx hammerspoon-mcp
</details>
<details> <summary><strong>Cursor / VS Code / Copilot</strong></summary>
Add to .vscode/mcp.json:
{
"servers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
</details>
<details> <summary><strong>Kiro</strong></summary>
Add to ~/.aws/amazonq/mcp.json:
{
"mcpServers": {
"hammerspoon": {
"command": "npx",
"args": ["hammerspoon-mcp"]
}
}
}
</details>
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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.