macctl

macctl

macctl is an accessibility-first macOS desktop automation MCP server that lets AI clients see the screen, inspect app accessibility trees, control mouse/keyboard, manage windows, and run commands, all behind tiered permissions and audit logging.

Category
访问服务器

README

macctl

npm version license platform

Accessibility-first macOS desktop automation for Claude and other MCP clients.

macctl gives an MCP client first-class access to a Mac: it can see the screen, read the accessibility tree of any application to identify controls by role/title/identifier (not just pixels), drive the mouse and keyboard, manage windows, inspect processes and launchd services, work with files and the clipboard, and run shell commands — all gated behind explicit permission tiers, with a local audit log of every call. It is the macOS counterpart to winctl, Sitharaj's Windows equivalent.

Why macctl

  • Accessibility-tree-first, not screenshot-and-click. ax_snapshot and ax_find let a client locate "the Save button" or "the text field named Subject" by role/title/identifier via AXUIElement, instead of guessing pixel coordinates from a screenshot and hoping the layout doesn't shift.
  • Correct global-point coordinates. Every coordinate macctl reports or accepts is a global screen point in the CG coordinate space (top-left origin of the main display), with each display's backingScaleFactor reported alongside — no silent pixel/point confusion across Retina and external displays.
  • Reliable, paced input. Keystrokes are synthesized layout-independently; text longer than ~200 characters is delivered via a clipboard paste (with the prior clipboard contents restored afterward) instead of hundreds of individually racy synthetic keypresses.
  • Stable, opaque handles. windowHandle and elementHandle values are fingerprinted (pid + window/element identity) rather than raw pointers, so a handle from one call either still resolves correctly on the next call or fails cleanly — it never silently targets the wrong window.
  • Honest, verified failures. Window moves are read back and the verified final frame is returned (apps clamp geometry); TCC failures come back as a structured PERMISSION_MISSING error naming the exact permission and the exact app to grant it to, not a generic timeout.
  • Tiered permissions + audit log. Every tool belongs to one of five permission tiers. Tools in a disabled tier are never registered with the MCP client — it cannot see or attempt them, not just be told no. Every call is appended to a local, redacted JSONL audit log.

Requirements

  • macOS 14 (Sonoma) or later recommended; built and tested on macOS 26.
  • Apple Silicon or Intel — macctl ships a universal (arm64 + x86_64) helper binary, no compilation required on your machine.
  • Node.js 20+.

Grant permissions

macctl's native tools (window management, input, accessibility, screen capture) need macOS permissions granted through System Settings → Privacy & Security, not through macctl itself. This is the single most common source of confusion, so read this before anything else:

The permission grant attaches to whichever app launches macctl — Claude Desktop, Terminal, iTerm2, VS Code, or whatever process ultimately spawns node dist/index.jsnever to node or macctl themselves. If you grant Accessibility to the wrong app, or grant it and then keep running the old process, tools will keep failing with PERMISSION_MISSING.

Steps:

  1. Run the doctor tool to find out exactly which app to grant, and get a direct System Settings link for each permission:

    npx @sitharaj88/macctl doctor
    

    This prints the responsible app (identified by walking the process tree up to the nearest .app bundle), the current status of Accessibility, Screen Recording, and Full Disk Access, and a x-apple.systempreferences: deep link for each one that isn't granted yet.

  2. Open System Settings → Privacy & Security and grant:

    • Accessibility — required for window management, mouse/keyboard input, and the ax_* accessibility tools.
    • Screen Recording — required for capture, capture_region, capture_window, and list_capturable_windows.
    • Full Disk Access — only needed if you point MACCTL_ALLOWED_PATHS outside your user-visible home folders.
  3. Restart the host app (Claude Desktop, your terminal, etc.) after granting. macOS does not apply a fresh TCC grant to an already-running process.

You can also trigger the native grant dialogs directly instead of only getting deep links:

npx @sitharaj88/macctl doctor --prompt

Tools that hit a missing permission at runtime return a structured PERMISSION_MISSING error with the same "which app, which permission, which link" detail — call system_doctor again after granting to confirm.

Installation

Claude Desktop (recommended)

Download the latest macctl.mcpb from GitHub Releases and double-click it. The bundled helper binary is Developer ID signed and notarized, so Gatekeeper accepts it without extra steps on a fresh download.

Claude Code

claude mcp add macctl -- npx -y @sitharaj88/macctl

Manual MCP client configuration

Add an entry to your client's MCP server config (Claude Desktop's claude_desktop_config.json, or the equivalent for your client):

{
  "mcpServers": {
    "macctl": {
      "command": "npx",
      "args": ["-y", "@sitharaj88/macctl"]
    }
  }
}

Then follow Grant permissions above and restart the client.

Permission tiers & profiles

Every tool belongs to exactly one tier. A tool in a disabled tier is never registered with the MCP client — this is an allowlist enforced at tools/list time, not a runtime check the model could talk its way around.

Tier Covers
observe Read-only: screenshots, window/AX inspection, system/process info, file reads.
interact Mouse/keyboard input, window focus/move/close, AX actions, clipboard.
filesystem Writing, deleting, moving, and creating files/directories.
manage Process start/kill, launchd service control, notifications.
shell Arbitrary shell commands via shell_run — unsandboxed, runs with the host process's own OS permissions.

Profiles bundle tiers together:

Profile Tiers enabled
readonly observe
standard (default) observe, interact, filesystem
full observe, interact, filesystem, manage, shell

system_doctor is always registered regardless of profile — it's how you diagnose everything else.

Set the profile via MACCTL_PROFILE, or bypass profiles entirely with an explicit tier list via MACCTL_TIERS (e.g. MACCTL_TIERS=observe,interact), which overrides the profile's tier set completely rather than adding to it.

MACCTL_PROFILE=readonly npx @sitharaj88/macctl        # observation only
MACCTL_PROFILE=full npx @sitharaj88/macctl             # everything, incl. shell
MACCTL_TIERS=observe,interact npx @sitharaj88/macctl   # explicit override

Configuration

All configuration is via environment variables, read once at startup:

Variable Default Description
MACCTL_PROFILE standard readonly | standard | full. See profiles above.
MACCTL_TIERS (unset) Comma-separated tier list (observe,interact,filesystem,manage,shell) that, if set, replaces the profile's tier set entirely.
MACCTL_ALLOWED_PATHS (unset → home dir, /tmp, /private/tmp, /Volumes) Colon-separated (:) list of directories file/exec-path tools are confined to.
MACCTL_DENIED_PATHS (unset) Colon-separated (:) list of additional directories to deny, layered on top of the built-in denylist (~/Library/Keychains, ~/.ssh, ~/Library/Application Support/com.apple.TCC, ~/Library/Containers, ~/Library/Group Containers, ~/Library/Cookies, ~/Library/Safari, ~/Library/Mail, /private/var/db). Denied always wins over allowed.
MACCTL_CONFIRM_DESTRUCTIVE true If true, destructive tools require an explicit confirm: true argument. Set to 0/false/no/off to disable.
MACCTL_AUDIT_LOG ~/Library/Logs/macctl/audit.jsonl Path to the audit log file.
MACCTL_AUDIT_DISABLED false Disable audit logging entirely.
MACCTL_MAX_IMAGE_WIDTH 1600 Screenshots wider than this (in pixels) are downscaled by the helper before being returned.
MACCTL_COMMAND_TIMEOUT_MS 60000 Default timeout for shell_run, overridable per call.
MACCTL_HELPER_PATH (unset → auto-resolved next to the installed package, native/bin/macctl-helper) Explicit path to the native helper binary — mainly for local development (scripts/build-helper.sh --dev).

Complete tool reference

41 tools total. Tier is shown per tool; destructive tools additionally require confirm: true when MACCTL_CONFIRM_DESTRUCTIVE is enabled (the default).

Doctor (always registered, any profile)

Tool Tier Description
system_doctor observe TCC permission status, responsible app, native helper reachability, active profile/tiers, audit log location, version info.

Screen (5)

Tool Tier Description
list_monitors observe List connected displays with frame, visible frame, backing scale factor, and which is main.
capture observe Screenshot a full display (main by default).
capture_region observe Screenshot a rectangular region in global screen points.
capture_window observe Screenshot a single window by windowHandle.
list_capturable_windows observe List on-screen windows available for capture_window, via ScreenCaptureKit.

Windows (7)

Tool Tier Description
window_list observe List all on-screen windows with title, owning app, pid, bounds, layer, minimized state, and a windowHandle.
window_get_active observe Get the frontmost app and its focused window.
window_get_desktop_info observe Get screen layout, light/dark appearance, and cursor position.
window_focus interact Activate a window's owning app and raise the window.
window_set_state interact Minimize, restore, maximize, fullscreen, hide, or show a window.
window_move interact Move and/or resize a window; returns the verified final frame.
window_close interact, destructive Close a window by pressing its AXCloseButton.

Input (7)

Tool Tier Description
input_move_mouse interact Move the mouse cursor to a global screen point.
input_click interact Click at a global screen point (left/right/middle, single/double/triple).
input_drag interact Press, drag through interpolated points, and release.
input_scroll interact Post a scroll-wheel event, optionally moving the cursor first.
input_type interact Type Unicode text at the current keyboard focus (layout-independent; paste for long text).
input_press_keys interact Press a keyboard chord, e.g. "cmd+shift+4".
input_key_hold interact Hold a single key down or release it (auto-releases after 30s).

Accessibility (4)

Tool Tier Description
ax_snapshot observe Walk the AXUIElement tree of an app or window (role/title/value/description/identifier/enabled/frame per node).
ax_find observe Bounded search over an app's/window's tree by role, title/value substring, and/or identifier.
ax_invoke interact Perform an accessibility action on an element (default AXPress).
ax_set_value interact Set an element's value directly, or via focus + Cmd+A + type fallback, with verified read-back.

System & Processes (8)

Tool Tier Description
system_info observe CPU, memory, disk, network, battery, graphics, macOS version, hardware model.
list_services observe List launchd services: running (launchctl list) joined with installed LaunchAgents/LaunchDaemons.
list_installed_apps observe List installed applications: name, bundle id, version, path.
process_list observe List running processes (pid, ppid, cpu%, mem%, rss, elapsed, command).
control_service manage, destructive Restart/stop/start/enable/disable a launchd LaunchAgent. LaunchDaemons (system domain) refuse with PRIVILEGE_REQUIRED.
notify manage Show a macOS notification banner.
process_start manage Launch an app (open -a/open -b), open a document/URL, or spawn a bare executable.
process_kill manage, destructive Terminate a process by pid (SIGTERM, optionally escalating to SIGKILL).

Files, Clipboard & Shell (9)

Tool Tier Description
file_known_folders observe List well-known macOS folders (home, Desktop, Documents, Downloads, iCloud Drive, etc.) with existence/allow-list status.
file_list observe List a directory's entries, optionally recursive with a depth cap.
file_read observe Read a file as utf8 text or base64, capped at maxBytes.
file_search observe Search a directory tree by name glob and/or content regex, or accelerated via mdfind.
file_write filesystem, destructive Write, append to, or create a text/base64 file.
file_manage filesystem, destructive Copy, move, delete a file/directory, or mkdir -p.
clipboard_read interact Read clipboard text, file references, and image presence.
clipboard_write interact Replace clipboard contents with text or file references.
shell_run shell Run a command via /bin/zsh -c, capturing stdout/stderr/exit code.

Example workflow

A realistic multi-tool sequence — opening TextEdit, finding its text area via the accessibility tree (not coordinates), typing, and verifying visually:

1. process_start        { app: "TextEdit" }
2. window_get_active     → windowHandle for the new TextEdit window
3. ax_find                { windowHandle, role: "AXTextArea" }
                          → elementHandle for the document's text area
4. ax_invoke               { elementHandle }              # focus/click it
5. input_type              { text: "Meeting notes...\n\n- Discuss Q3 roadmap" }
6. ax_snapshot              { windowHandle, maxDepth: 5 }  # read the value back, verify it landed
7. capture_window           { windowHandle }               # visual confirmation
8. window_move               { windowHandle, x: 100, y: 100 }
9. input_press_keys           { keys: "cmd+s" }              # save
10. window_close                { windowHandle, confirm: true }  # destructive — needs confirm

Each step uses a real tool name and an opaque handle produced by an earlier step — never a guessed coordinate or a raw pointer.

Safety & audit

  • Destructive-action confirmation. window_close, file_write, file_manage, control_service, and process_kill are marked destructive. When MACCTL_CONFIRM_DESTRUCTIVE is enabled (the default), each call must include confirm: true or it's refused with CONFIRMATION_REQUIRED — no first-try accidental deletes.
  • Path containment. File and executable-path tools resolve the real (symlink-followed) path and check it against MACCTL_ALLOWED_PATHS / MACCTL_DENIED_PATHS, with a built-in denylist (Keychains, ~/.ssh, TCC store, app containers, Safari/Mail data, /private/var/db) that always wins over anything allowed.
  • No silent privilege escalation. macctl never shells out to sudo. Actions that would need elevated privileges (e.g. controlling a system-domain LaunchDaemon) fail honestly with PRIVILEGE_REQUIRED rather than prompting for or assuming root.
  • Audit log. Every tool call is appended as one JSON line to ~/Library/Logs/macctl/audit.jsonl (configurable, or disable with MACCTL_AUDIT_DISABLED=1): timestamp, tool name, outcome, duration, error code, and redacted arguments (long strings and base64-looking blobs are replaced with [redacted N chars] before being written).

Privacy

See PRIVACY.md for the full policy. In short: macctl runs entirely locally and has no telemetry. Screenshots, accessibility-tree contents, clipboard data, and file contents are returned only to the MCP client that requested them — nothing is sent anywhere by macctl itself.

Development

git clone https://github.com/sitharaj88/macctl.git
cd macctl
npm install
npm run build:all        # tsc + universal Swift helper build (native/bin/macctl-helper)

npm run build:helper (via scripts/build-helper.sh) builds the Swift helper as a universal (arm64 + x86_64) binary using Swift Package Manager; run it with --dev for a fast, current-arch-only build during local iteration.

npm run smoke             # spawns the server, exercises TCC-free tools across all 3 profiles
npm run smoke:full        # also exercises TCC-dependent tools (skips gracefully if ungranted)
node scripts/verify-interactive.mjs   # full TextEdit round-trip — needs a real GUI session + Accessibility/Screen Recording grants

verify-interactive.mjs drives an actual TextEdit window end-to-end (open → find the text area → type → read the value back → screenshot → move → close), so it must run in a real logged-in GUI session with permissions already granted to your terminal — it will not work over SSH or in CI.

Publishing

  • npm: npm publish --access public
  • .mcpb bundle: npx @anthropic-ai/mcpb pack (respects .mcpbignore; produces macctl.mcpb for GitHub Releases / Claude Desktop's drag-and-drop install).
  • MCP registry: mcp-publisher publish (using server.json).

👤 Author

Sitharaj Seenivasan

☕ Support

If this project helps you, consider buying me a coffee — it keeps the work going.

Buy Me A Coffee

📄 License

Licensed under the Apache License 2.0. © 2026 Sitharaj Seenivasan.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选