mcp server
macMCP
Control any macOS app from Claude over MCP — per-app permission gating, screenshots, accessibility read/drive, synthetic input, menu-bar control, and per-app network metadata. Zero deps, Swift.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 21, 2026macMCP
Control any macOS app from Claude over MCP — enumerate apps, screenshot a window, read its UI (accessibility tree), click/type/scroll, and watch its network. Sister project to safari-mcp: same idea, but for native apps (e.g. Lark, Slack) instead of Safari.
Access is gated by a per-app, two-level permission model managed from a menu-bar app:
- Default ("none") — Claude may only learn that an app exists, is running, and its window titles / frontmost state. No screenshots, no UI content, no input, no network bodies.
- Full — everything: screenshot, accessibility tree, click/type/scroll, network capture.
Granting is one click (a native Allow/Deny prompt the first time Claude acts on an app, or a toggle in the menu bar). Revoking is one click. Claude can never grant itself access.
Architecture
Two binaries, mirroring safari-mcp's zero-dependency Swift / hand-rolled-JSON-RPC stack:
macmcp(shim) — the thin stdio MCP server Claude Code launches per session. Holds no permissions; forwards every tool call to the agent over a unix-domain socket.macmcp-agent(agent) — the persistent, signed menu-bar app that owns the macOS TCC grants (Accessibility, Screen Recording), the per-app allowlist, the network system extension, and the work layer (AX / ScreenCaptureKit / CGEvent). Many shims → one agent.
MacMCPCore is the shared, Foundation-only library (MCP types + IPC primitives) so the shim
never pulls AppKit.
Status
All shipped (19 tools), each verified live:
- Phase 1 ✅ — shim + agent + unix-socket IPC + default-tier tools (
mac_list_apps,mac_app_info,mac_list_windows,mac_permissions,mac_network_status). - Phase 2 ✅ — screenshot (ScreenCaptureKit) + AX read + permission gate + menu-bar UI
(
mac_screenshot,mac_read_ui,mac_find_element). - Phase 3 ✅ — input/control via CGEvent + screenshot-coordinate fallback
(
mac_click,mac_type,mac_scroll,mac_key,mac_computer). - Phase 4 ✅ — per-app network metadata via
lsof(mac_read_network): remote host/port/state, works on every app including cert-pinned ones. - v0.2 ✅ — drag (
mac_drag), window management (mac_window: move/resize/minimize/ close/raise/list), waiting (mac_wait_for), and one-shot/batch flows (mac_clickby query,mac_do). Driving an app never raises it or steals focus — control clicks go through Accessibility (AXPress) — the sole exception being a drag, which needs a real mouse-down.
Network body capture (decrypting request/response payloads) is intentionally out of scope: it would require a transparent-proxy system extension + a trusted root CA, which needs a paid Apple Developer Network Extension provision and still fails against cert-pinned apps. macMCP stops at connection metadata.
Develop
make build # swift build (macmcp + macmcp-agent)
make test # protocol self-test (shim ⇄ agent over the socket)
make package # release build + signed ./macMCP.app bundle
make install # install /Applications/macMCP.app + register the release shim with Claude Code
The installed app owns macOS TCC grants for Accessibility and Screen Recording. By default
the bundle is ad-hoc signed; set MACMCP_SIGN_IDENTITY to a stable code-signing identity
before make package or make install if you want to grant those permissions once and keep
them across rebuilds.
Prebuilt — each release attaches a
macMCP.app.zip built by CI. It's ad-hoc signed and not notarized (no Developer ID), so
after unzipping you must clear quarantine before it'll open:
xattr -dr com.apple.quarantine macMCP.app && mv macMCP.app /Applications/
Because the ad-hoc signature changes per build, TCC grants won't persist across versions —
building from source with your own signing identity (make install) is recommended for
regular use.
Security model
The agent gates every Full-tier capability per app (bundle id), fails closed on identity
mismatch (signing Team ID, or canonical path for unsigned apps), and only ever screenshots a
granted app's own windows. Grants happen solely via the native prompt or the menu bar —
Claude can never self-grant. The agent socket and allowlist live in an owner-only (0700)
directory; note that, as with any same-user agent, another process running as you can use
the socket, so treat the granted set like any local capability you hold.
Requirements
macOS 14+. No third-party dependencies; no paid Apple Developer account required.
License
Licensed under the Apache License, Version 2.0 — see LICENSE.
Copyright © 2026 Erwin Zhang.