DEV Community

Daniil
Daniil

Posted on

I Gave Claude 12 Hands to Control My Mac (and Taught It to Fix Its Own Mistakes)

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:// and mailto: links. Not file:///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.  
Enter fullscreen mode Exit fullscreen mode

Top comments (0)