macctl
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@macctlfind the Save button in the frontmost app and click it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
macctl
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_snapshotandax_findlet a client locate "the Save button" or "the text field named Subject" by role/title/identifier viaAXUIElement, 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
backingScaleFactorreported 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.
windowHandleandelementHandlevalues 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_MISSINGerror 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.
Related MCP server: computer-use
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.js— never tonodeormacctlthemselves. If you grant Accessibility to the wrong app, or grant it and then keep running the old process, tools will keep failing withPERMISSION_MISSING.
Steps:
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 doctorThis prints the responsible app (identified by walking the process tree up to the nearest
.appbundle), the current status of Accessibility, Screen Recording, and Full Disk Access, and ax-apple.systempreferences:deep link for each one that isn't granted yet.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, andlist_capturable_windows.Full Disk Access — only needed if you point
MACCTL_ALLOWED_PATHSoutside your user-visible home folders.
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 --promptTools 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/macctlManual 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 |
| Read-only: screenshots, window/AX inspection, system/process info, file reads. |
| Mouse/keyboard input, window focus/move/close, AX actions, clipboard. |
| Writing, deleting, moving, and creating files/directories. |
| Process start/kill, launchd service control, notifications. |
| Arbitrary shell commands via |
Profiles bundle tiers together:
Profile | Tiers enabled |
|
|
|
|
|
|
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 overrideConfiguration
All configuration is via environment variables, read once at startup:
Variable | Default | Description |
|
|
|
| (unset) | Comma-separated tier list ( |
| (unset → home dir, | Colon-separated ( |
| (unset) | Colon-separated ( |
|
| If true, destructive tools require an explicit |
|
| Path to the audit log file. |
|
| Disable audit logging entirely. |
|
| Screenshots wider than this (in pixels) are downscaled by the helper before being returned. |
|
| Default timeout for |
| (unset → auto-resolved next to the installed package, | Explicit path to the native helper binary — mainly for local development ( |
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 |
| observe | TCC permission status, responsible app, native helper reachability, active profile/tiers, audit log location, version info. |
Screen (5)
Tool | Tier | Description |
| observe | List connected displays with frame, visible frame, backing scale factor, and which is main. |
| observe | Screenshot a full display (main by default). |
| observe | Screenshot a rectangular region in global screen points. |
| observe | Screenshot a single window by |
| observe | List on-screen windows available for |
Windows (7)
Tool | Tier | Description |
| observe | List all on-screen windows with title, owning app, pid, bounds, layer, minimized state, and a |
| observe | Get the frontmost app and its focused window. |
| observe | Get screen layout, light/dark appearance, and cursor position. |
| interact | Activate a window's owning app and raise the window. |
| interact | Minimize, restore, maximize, fullscreen, hide, or show a window. |
| interact | Move and/or resize a window; returns the verified final frame. |
| interact, destructive | Close a window by pressing its |
Input (7)
Tool | Tier | Description |
| interact | Move the mouse cursor to a global screen point. |
| interact | Click at a global screen point (left/right/middle, single/double/triple). |
| interact | Press, drag through interpolated points, and release. |
| interact | Post a scroll-wheel event, optionally moving the cursor first. |
| interact | Type Unicode text at the current keyboard focus (layout-independent; paste for long text). |
| interact | Press a keyboard chord, e.g. |
| interact | Hold a single key down or release it (auto-releases after 30s). |
Accessibility (4)
Tool | Tier | Description |
| observe | Walk the |
| observe | Bounded search over an app's/window's tree by role, title/value substring, and/or identifier. |
| interact | Perform an accessibility action on an element (default |
| interact | Set an element's value directly, or via focus + Cmd+A + type fallback, with verified read-back. |
System & Processes (8)
Tool | Tier | Description |
| observe | CPU, memory, disk, network, battery, graphics, macOS version, hardware model. |
| observe | List launchd services: running ( |
| observe | List installed applications: name, bundle id, version, path. |
| observe | List running processes (pid, ppid, cpu%, mem%, rss, elapsed, command). |
| manage, destructive | Restart/stop/start/enable/disable a launchd LaunchAgent. LaunchDaemons (system domain) refuse with |
| manage | Show a macOS notification banner. |
| manage | Launch an app ( |
| manage, destructive | Terminate a process by pid (SIGTERM, optionally escalating to SIGKILL). |
Files, Clipboard & Shell (9)
Tool | Tier | Description |
| observe | List well-known macOS folders (home, Desktop, Documents, Downloads, iCloud Drive, etc.) with existence/allow-list status. |
| observe | List a directory's entries, optionally recursive with a depth cap. |
| observe | Read a file as utf8 text or base64, capped at |
| observe | Search a directory tree by name glob and/or content regex, or accelerated via |
| filesystem, destructive | Write, append to, or create a text/base64 file. |
| filesystem, destructive | Copy, move, delete a file/directory, or |
| interact | Read clipboard text, file references, and image presence. |
| interact | Replace clipboard contents with text or file references. |
| shell | Run a command via |
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 confirmEach 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, andprocess_killare marked destructive. WhenMACCTL_CONFIRM_DESTRUCTIVEis enabled (the default), each call must includeconfirm: trueor it's refused withCONFIRMATION_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-domainLaunchDaemon) fail honestly withPRIVILEGE_REQUIREDrather 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 withMACCTL_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 grantsverify-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; producesmacctl.mcpbfor GitHub Releases / Claude Desktop's drag-and-drop install).MCP registry:
mcp-publisher publish(usingserver.json).
👤 Author
Sitharaj Seenivasan
🌐 Website: sitharaj.in
💼 LinkedIn: sitharaj08
💻 GitHub: sitharaj88
☕ Support
If this project helps you, consider buying me a coffee — it keeps the work going.
📄 License
Licensed under the Apache License 2.0. © 2026 Sitharaj Seenivasan.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityFmaintenanceAn experimental MCP server providing full control over the macOS user interface through mouse, keyboard, and window management tools. It enables AI assistants to automate desktop tasks by utilizing native accessibility APIs and OCR for real-time screen comprehension.Last updated7Creative Commons Zero v1.0 Universal
- Alicense-qualityBmaintenanceStandalone MCP server that gives AI agents full GUI control over macOS — screenshots, mouse, keyboard, apps, clipboard, and multi-display — with zero private dependencies.Last updated16MIT
- Alicense-qualityAmaintenanceA local daemon for macOS that gives any MCP-capable AI client eyes, hands, and a face — screen capture, accessibility tree, mouse/keyboard actions, and an overlay — with a built-in security ceiling.Last updated1AGPL 3.0
- Alicense-qualityDmaintenanceMCP server that enables AI to fully control macOS — mouse, keyboard, terminal, screenshots, window management, UI element detection, and provides AI-optimized information reporting.Last updated23MIT
Related MCP Connectors
Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sitharaj88/macctl'
If you have feedback or need assistance with the MCP directory API, please join our Discord server