I keep wanting Claude to do dumb simple things on my Mac. Close a tab. Move a window. Copy something from
Safari. And every time it's the same dance — Claude writes some AppleScript, I copy it, open Terminal, paste
it, it errors out because Claude hallucinated a menu item name, I go back, we try again.
I got tired of being the middleware.
## The obvious first attempt
I did what everyone does — wrapped osascript in an MCP server. One tool: "run this AppleScript." Ship it.
60% of the time it worked. The other 40% was Claude confidently typing click menu item "Save As..." when
the actual item is "Save As…" (unicode ellipsis). Or referencing "System Preferences" which hasn't existed
since Ventura. Or constructing AppleScript with a missing end tell.
I tried prompt engineering. Few-shot examples. Nothing helped because the problem wasn't the model — it was
the interface.
## What actually worked
I split the single "run anything" tool into 12 specific ones. Instead of asking Claude to write AppleScript,
I give it structured tools:
-
click_menu({app: "Safari", path: ["File", "Close Tab"]}) manage_windows({action: "move", position: {x: 0, y: 25}})-
get_browser_tabs({browser: "safari"})
Claude doesn't write AppleScript anymore. It calls functions with validated parameters. The server generates
the AppleScript internally.
## The self-correcting thing
This is the part I'm actually proud of.
When Claude calls click_menu with a wrong item name, the server doesn't just return an error. It grabs the
real menu tree and sends it back:
Claude: click "File → Export as PDF"
Server: "Not found. Available items: ['New from Clipboard',
'Open...', 'Close', 'Export...', 'Export as PDF...']"
Claude: click "File → Export as PDF..."
Server: "Clicked: File > Export as PDF..."
Claude sees what's actually there and fixes itself. No human needed. This took my menu automation from "works
sometimes" to "works almost always."
## The security stuff
Running AI-generated system commands should make you nervous. A few things I did:
- URL allowlist — Claude can open
https://andmailto:links. Notfile:///etc/passwd. - Environment isolation — the osascript process only gets PATH, HOME, and LANG. Not your API keys, not your database URLs.
- Process group kill — if a script hangs, the entire process tree dies after 30 seconds. No orphan processes.
Is it bulletproof? No. But it's better than "here's raw osascript access, good luck."
## The numbers nobody asked for
- 12 tools
- 41 integration tests
- I checked the 4 most popular alternatives on GitHub. Combined test count: 0.
- Install:
npx mcp-osascript
## Try it
json
{
"mcpServers": {
"osascript": {
"command": "npx",
"args": ["-y", "mcp-osascript"]
}
}
}
Paste that into your Claude Desktop config, restart, and ask Claude to move a window or list your Safari
tabs.
Repo: https://github.com/m0rvayne/mcp-osascript
I'm genuinely curious — if you could get Claude to do anything on your Mac reliably, what would it be? That's
what I'm building next.
Top comments (0)