{
  "markdown": "<div align=\"center\">\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/vukvukovich/hammerspoon-mcp/main/docs/assets/banner-dark.svg\">\n  <img src=\"https://raw.githubusercontent.com/vukvukovich/hammerspoon-mcp/main/docs/assets/banner-light.svg\" alt=\"Hammerspoon MCP\" width=\"680\">\n</picture>\n\n[![npm](https://img.shields.io/npm/v/%40vukvukovich%2Fhammerspoon-mcp?style=flat-square&label=npm&color=ffcc66)](https://www.npmjs.com/package/@vukvukovich/hammerspoon-mcp)\n[![CI](https://img.shields.io/github/actions/workflow/status/vukvukovich/hammerspoon-mcp/ci.yml?branch=main&style=flat-square&label=ci)](https://github.com/vukvukovich/hammerspoon-mcp/actions/workflows/ci.yml)\n[![node](https://img.shields.io/node/v/%40vukvukovich%2Fhammerspoon-mcp?style=flat-square&label=node&color=5fa04e)](https://nodejs.org)\n[![license](https://img.shields.io/badge/license-MIT-4c72b0?style=flat-square)](./LICENSE)\n\nLet an AI agent drive your Mac through Hammerspoon, without ever splicing its\ninput into Lua source.\n\n</div>\n\n## What it is\n\n[Hammerspoon](https://www.hammerspoon.org) is a macOS automation app you script\nin Lua. It exposes a command line interface through its `hs.ipc` module, so\n`hs -c \"<lua>\"` runs Lua inside the running Hammerspoon process.\n\nThis project is an MCP (Model Context Protocol) server that sits in front of\nthat CLI. It gives an agent a set of typed tools (list windows, move a window,\nfocus an app, search the Hammerspoon API docs, tail the console, reload your\nconfig) and translates each tool call into a Lua program that Hammerspoon runs.\n\nIt speaks MCP over stdio, so any MCP client can use it: Claude Code, Claude\nDesktop, or your own.\n\n## Project status\n\nPublished on npm as `v0.4.x`: 38 safe-tier tools, plus 3 gated behind\n`HS_MCP_TOOLS=all`. Still pre-1.0, so tool names and argument shapes can\nchange between minor versions.\n\n## Why this one is different\n\n### 1. Injection-safe by construction\n\nMost of the risk in a \"run Lua for me\" bridge is the moment you build the Lua.\nThe usual approach is to interpolate the arguments into a source string and\nescape the dangerous characters. Escaping is a discipline, and disciplines slip.\n\nThis server never interpolates. Every tool's Lua body is a **static constant**\nin the TypeScript source. Arguments travel separately:\n\n1. The validated argument object is JSON-encoded.\n2. That JSON is base64-encoded.\n3. The base64 text is spliced once, into one fixed prelude line:\n\n```lua\nlocal ARGS = hs.json.decode(hs.base64.decode(\"<base64>\"))\n```\n\nThe tool body then reads `ARGS.title`, `ARGS.windowId`, and so on.\n\nThe base64 alphabet is `A-Z`, `a-z`, `0-9`, `+`, `/`, and `=`. It contains no\nquote, no backslash, no newline, no square bracket, and no hyphen. So the\npayload cannot close the Lua string, cannot start an escape sequence, cannot\nopen a long bracket, and cannot open a comment. Injection is impossible because\nof the alphabet, not because someone remembered to escape correctly.\n\nThere is no shell layer either. The server talks to Hammerspoon over a\npersistent Unix socket, falling back to `spawn(hsPath, [\"-c\", lua])` with an\nargv array, so no `sh` ever parses the command on either path.\n\n### 2. Tiered tools, safe by default\n\nThe default tier is `safe`: read, inspect, and arrange operations. Arbitrary\nLua evaluation exists as `hs_eval`, but it is off unless you set\n`HS_MCP_TOOLS=all`.\n\nSee [Security](#security) for the reasoning. Short version: the threat is prompt\ninjection, not you.\n\n### 3. Built for the config-development loop\n\nMost of the value of Hammerspoon is your own `init.lua`. So the server helps you\nwrite it, not just drive it:\n\n- `hs_api_search` searches Hammerspoon's bundled API documentation, so the agent\n  can look up the real signature of `hs.window.moveToUnit` instead of guessing.\n- `hs_console_tail` reads back the Hammerspoon console, so the agent can see its\n  own errors.\n- `hs_reload_config` reloads `init.lua` after an edit.\n\nEdit, reload, read the console, fix. The agent can run that loop itself.\n\n## Quick start\n\n### Prerequisites\n\n- macOS.\n- Node.js 24 or newer.\n- Hammerspoon, installed and running:\n\n  ```sh\n  brew install --cask hammerspoon\n  ```\n\n- The `hs.ipc` module loaded in your Hammerspoon config. Add this line to\n  `~/.hammerspoon/init.lua`:\n\n  ```lua\n  require(\"hs.ipc\")\n  ```\n\n  Then reload your config from the Hammerspoon menu bar icon. This is what\n  installs and enables the `hs` command line tool. Without it, `hs -c` has\n  nothing to talk to.\n\nVerify the bridge by hand before wiring up any client:\n\n```sh\nhs -c \"return 1 + 1\"\n```\n\nIf that prints `2`, you are ready.\n\n### Add it to your MCP client\n\nClaude Code:\n\n```sh\nclaude mcp add hammerspoon -- npx -y @vukvukovich/hammerspoon-mcp\n```\n\nAny client that takes an `mcpServers` JSON block:\n\n```json\n{\n  \"mcpServers\": {\n    \"hammerspoon\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@vukvukovich/hammerspoon-mcp\"]\n    }\n  }\n}\n```\n\nTo opt into the unsafe tier, add the environment variable. Claude Code:\n\n```sh\nclaude mcp add hammerspoon -e HS_MCP_TOOLS=all -- npx -y @vukvukovich/hammerspoon-mcp\n```\n\nJSON:\n\n```json\n{\n  \"mcpServers\": {\n    \"hammerspoon\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@vukvukovich/hammerspoon-mcp\"],\n      \"env\": {\n        \"HS_MCP_TOOLS\": \"all\"\n      }\n    }\n  }\n}\n```\n\nAsk the agent to call `hs_health` first. It reports whether the `hs` binary was\nfound, whether Hammerspoon is running, and whether `hs.ipc` answered.\n\n## Tool reference\n\nForty-one tools: thirty-eight in the safe tier, three gated.\n\n| Tool                  | Tier   | What it does                                                                                                                                                                                                    |\n| --------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `hs_health`           | safe   | Report bridge status: resolved `hs` path, whether Hammerspoon answers, its version.                                                                                                                             |\n| `hs_api_search`       | safe   | Search Hammerspoon's bundled API reference and return exact signatures.                                                                                                                                         |\n| `hs_console_tail`     | safe   | Return the last N lines of the Hammerspoon console.                                                                                                                                                             |\n| `hs_reload_config`    | safe   | Reload `~/.hammerspoon/init.lua`.                                                                                                                                                                               |\n| `hs_notify`           | safe   | Show a transient on-screen alert, without stealing focus.                                                                                                                                                       |\n| `hs_list_windows`     | safe   | List windows, with id, title, owning app, screen, and frame.                                                                                                                                                    |\n| `hs_focus_window`     | safe   | Focus a window by id, or by a substring of its title.                                                                                                                                                           |\n| `hs_move_window`      | safe   | Move or resize a window by id, in absolute screen pixels.                                                                                                                                                       |\n| `hs_window_layout`    | safe   | Snap a window to a named preset such as `left-half` or `quarter-top-left`.                                                                                                                                      |\n| `hs_list_apps`        | safe   | List running applications, with bundle id, PID, and window count.                                                                                                                                               |\n| `hs_launch_app`       | safe   | Launch an application by name, or focus it if it is already running.                                                                                                                                            |\n| `hs_focus_app`        | safe   | Bring an already-running application to the front.                                                                                                                                                              |\n| `hs_screens`          | safe   | List screens, with id, name, frame, and which one is primary.                                                                                                                                                   |\n| `hs_machine_status`   | safe   | Battery, brightness, wifi, idle time, audio, and host info in one call.                                                                                                                                         |\n| `hs_audio_devices`    | safe   | List audio output and input devices, showing the current default.                                                                                                                                               |\n| `hs_audio_set_device` | safe   | Switch the default output or input device, for example to headphones.                                                                                                                                           |\n| `hs_audio_volume`     | safe   | Get or set volume and mute on the default device.                                                                                                                                                               |\n| `hs_brightness`       | safe   | Get or set built-in display brightness.                                                                                                                                                                         |\n| `hs_media_control`    | safe   | Play, pause, skip, or go back, via system media keys.                                                                                                                                                           |\n| `hs_list_spaces`      | safe   | List desktops (Spaces) per screen, with positions and which is current.                                                                                                                                         |\n| `hs_goto_space`       | safe   | Switch desktop by id or by 1-based position.                                                                                                                                                                    |\n| `hs_eval`             | unsafe | Evaluate arbitrary Lua. Requires `HS_MCP_TOOLS=all`.                                                                                                                                                            |\n| `hs_applescript`      | unsafe | Run AppleScript. Reaches Mail, Notes, Reminders, Finder. Requires `HS_MCP_TOOLS=all`.                                                                                                                           |\n| `hs_ui_press`         | unsafe | Press a UI element found by `hs_ui_inspect`. Refuses to act without an `expectLabel` or `expectRole` from the inspection, and refuses when the element there no longer matches it. Requires `HS_MCP_TOOLS=all`. |\n\n`hs_window_layout` presets: `left-half`, `right-half`, `top-half`, `bottom-half`,\n`maximize`, `center`, `thirds-left`, `thirds-center`, `thirds-right`,\n`two-thirds-left`, `two-thirds-right`, and the four `quarter-*` corners.\nPositions are computed from the screen's usable frame, so they respect the menu\nbar and the Dock, and they work on a second monitor whose origin is negative.\n\n`hs_ui_inspect` returns structure and labels only, never the contents of text\nfields or documents. Structure is what an agent needs in order to act; contents\nare what a password manager is made of.\n\nTools in the `unsafe` tier are not registered at all unless you opt in. A client\nconnected with default settings will not see `hs_eval`, `hs_applescript`, or\n`hs_ui_press` in its tool list.\n\n## Configuration\n\nAll configuration is environment variables, read once at startup.\n\n| Variable           | Values                                 | Default             | Meaning                                                                                                                                                         |\n| ------------------ | -------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `HS_MCP_TOOLS`     | `safe` \\| `all`                        | `safe`              | Which tiers to register. `all` adds the unsafe tier: `hs_eval`, `hs_applescript`, and `hs_ui_press`.                                                            |\n| `HS_MCP_TRANSPORT` | `socket` \\| `spawn`                    | `socket`            | How Lua reaches Hammerspoon: a persistent Unix socket (~10x faster, self-installed on first call, falls back to spawn by itself), or one `hs` process per call. |\n| `HS_MCP_HS_PATH`   | absolute path                          | auto-detected       | Path to the `hs` binary. Set this if your install is somewhere unusual.                                                                                         |\n| `HS_MCP_DOCS_PATH` | absolute path                          | from the app bundle | Path to Hammerspoon's bundled API documentation JSON, used by `hs_api_search`.                                                                                  |\n| `HS_MCP_LOG_LEVEL` | `debug` \\| `info` \\| `warn` \\| `error` | `info`              | Verbosity of the stderr log. Logs never touch stdout, which carries the protocol.                                                                               |\n\nAn unrecognised value for `HS_MCP_TOOLS` logs a loud warning and falls back to\n`safe` - never to the wider tier. A typo can cost you the gated tools, but it\ncan never grant them. For the discovery order behind the `hs` path default,\nsee [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md).\n\n## Security\n\nRead this section before you set `HS_MCP_TOOLS=all`.\n\n### Where the server runs\n\nThe server is a local process. Your MCP client spawns it, talks to it over\nstdio, and it runs as your user account. There is no network listener and no\nremote surface.\n\nThat also means it inherits your permissions. Hammerspoon holds macOS TCC\n(Transparency, Consent, and Control) grants such as Accessibility, and possibly\nScreen Recording and Automation. Anything running inside Hammerspoon acts with\nthose grants. This server does not add permissions and it cannot take any away.\n\n### The actual threat model\n\nThe risk is not that you are untrustworthy. The risk is **prompt injection**.\n\nAn agent reads untrusted text constantly: web pages, README files, issue bodies,\nlog lines, the output of other tools. Any of that text can contain instructions.\nSometimes the agent follows them. This is not hypothetical and it is not solved.\n\nSo the question for every tool is: if the agent is talked into calling this, how\nbad is it?\n\n- A curated verb has a small blast radius. Worst case with `hs_move_window`, a\n  window ends up in the wrong place. Annoying, reversible, visible.\n- Arbitrary Lua has no blast radius limit. Hammerspoon's Lua can run shell\n  commands, read the clipboard, capture the screen, watch keystrokes, and make\n  network requests. One successful injection is full control of the machine,\n  quietly.\n\nThat gap is the whole reason for tiers.\n\n### What `HS_MCP_TOOLS=all` means\n\nIt registers `hs_eval`, `hs_applescript`, and `hs_ui_press`. From that point\nthe agent can execute any Lua or AppleScript it can write, and press UI\nelements, inside a process that holds your Accessibility grants. Treat it as\nhanding over a shell that also has the screen and the keyboard.\n\nIt is a genuinely useful mode. Writing and debugging Hammerspoon config is much\nfaster when the agent can try a snippet directly. Use it in a session you are\nwatching, for work you asked for, and turn it back off. Do not leave it on in a\nlong-running or unattended agent that browses the web.\n\nSafe by default is not a claim that you cannot be trusted with the dangerous\ntool. It is a claim that turning it on should be a decision you made on purpose,\non a specific day, for a specific reason.\n\n### What is deliberately not here\n\nThese are not oversights. They are refusals, with reasons.\n\n- **Raw shell execution.** Agents already have shell tools, sandboxed and\n  audited by their own host. A Mac-control server does not need to be a second,\n  worse shell.\n- **Keystroke and click synthesis.** Synthetic typing into whatever window\n  happens to be focused is arbitrary code execution with extra steps. If that\n  window is a terminal, \"type this text\" and \"run this command\" are the same\n  operation.\n- **Clipboard reads.** Your clipboard holds passwords, tokens, and private\n  messages, often within seconds of you copying them. A tool that reads it is an\n  exfiltration primitive pointed at your most sensitive short-lived data.\n- **Screenshots.** Same reasoning. A screen capture is everything visible,\n  including the windows the agent was not asked about.\n\nSome of these may come back later, each behind its own explicit opt-in, the way\n`hs_eval` is gated now. None of them will ever be in the default tier.\n\n### Reporting a vulnerability\n\nOpen a\n[security advisory](https://github.com/vukvukovich/hammerspoon-mcp/security/advisories/new)\non the repository rather than a public issue.\n\n## One thing to know: calls are queued\n\nHammerspoon runs Lua on a single thread, so it executes one call at a time no\nmatter how many arrive. Measured: four 400ms calls issued together take 1629ms,\nnot 417ms.\n\nThe server queues accordingly, four in flight at once. That is not a throttle\nfor its own sake. Left unbounded, simultaneous calls do not merely wait, they\nstart failing (5 of 15 succeeded in testing) and the pattern crashed\nHammerspoon twice inside its own IPC layer.\n\nThe practical consequence: **a slow tool blocks the others**, because there is\nonly one queue. If something feels stuck, one call is usually holding it.\n[ARCHITECTURE.md](./docs/ARCHITECTURE.md#concurrency-calls-are-queued-four-at-a-time)\nhas the measurements.\n\n## Troubleshooting\n\nStart with `hs_health`. It is designed to tell you which of these you have.\n\n**`hs` not found.** The server looks in a fixed list of locations and then on\n`PATH`. If your Hammerspoon lives somewhere else, set `HS_MCP_HS_PATH` to the\nabsolute path of the binary. Note that GUI-launched MCP clients often have a\nminimal `PATH` that does not include Homebrew, so a path that works in your\nterminal may not work for the server. When in doubt, set the variable.\n\n**Hammerspoon is not running.** The `hs` CLI is a client. It needs the\nHammerspoon app running to talk to. Launch Hammerspoon and retry.\n\n**`hs.ipc` is not loaded.** Hammerspoon is running but nothing answers, or the\n`hs` binary does not exist at all. Both usually mean `require(\"hs.ipc\")` is\nmissing from `~/.hammerspoon/init.lua`. Add it, reload the config from the menu\nbar icon, then check `hs -c \"return 1 + 1\"` in a terminal.\n\n**Tools are missing from the client's list.** If `hs_eval` is the missing one,\nthat is the default tier working as intended. Set `HS_MCP_TOOLS=all` in the\nclient's server config, then restart the client so the server is respawned with\nthe new environment.\n\n**A tool times out.** Hammerspoon is single-threaded. If your config is stuck in\na loop or a modal dialog is blocking, calls will not return. Check the console\nwith `hs_console_tail`, or reload the config.\n\n## Development\n\n```sh\ngit clone https://github.com/vukvukovich/hammerspoon-mcp.git\ncd hammerspoon-mcp\nnpm install\n```\n\n| Script                     | What it does                                          |\n| -------------------------- | ----------------------------------------------------- |\n| `npm run build`            | Compile TypeScript to `dist/` with `tsc`.             |\n| `npm run typecheck`        | Type check everything, no emit.                       |\n| `npm run lint`             | ESLint.                                               |\n| `npm run lint:fix`         | ESLint with autofix.                                  |\n| `npm run format`           | Prettier, write.                                      |\n| `npm run format:check`     | Prettier, check only.                                 |\n| `npm test`                 | Unit tests (Vitest).                                  |\n| `npm run test:watch`       | Unit tests in watch mode.                             |\n| `npm run test:coverage`    | Unit tests with coverage.                             |\n| `npm run test:integration` | Integration tests. Needs a real, running Hammerspoon. |\n| `npm run check`            | Everything CI runs: typecheck, lint, format, tests.   |\n\nStack: TypeScript 5.9 in strict mode, ESM only, Node 24+, the\n`@modelcontextprotocol/server` v2 SDK, Zod v4 for schemas, Vitest 4, and\ntypescript-eslint 8 with Prettier 3. Plain `tsc` for the build, no bundler.\n\nBefore contributing, read [CONVENTIONS.md](./CONVENTIONS.md) (binding rules) and\n[CONTRIBUTING.md](./CONTRIBUTING.md) (workflow and commit format). The design is\nwritten up in [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md).\n\n## License\n\nMIT. Copyright (c) 2026 Vuk Vukovich. See [LICENSE](./LICENSE).\n\nHammerspoon is a separate project with its own license and is not affiliated\nwith this one.\n",
  "bytes": 22281,
  "sha": "294b132bb5074a679c849cf896b1e57e2d22bdffd8aa7a8cceca0a2b9545f4f1",
  "repo_slug": "vukvukovich/hammerspoon-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_vukvukovich_hammerspoon_mcp_bbd10500/readme"
}