by bulutarkan
Provides a local macOS control server that exposes shell execution, file manipulation, UI and browser automation, voice interaction, delegated AI agents, memory, and a REST/OpenAPI surface, all managed through a native menu‑bar controller and a secure dashboard.
Mac MCP enables AI agents and scripts to control a Mac programmatically. It runs a local HTTP endpoint that offers a compact core set of 21 tools (out of 84 registered capabilities) for actions such as command execution, file I/O, UI interaction, background browser automation, voice I/O, and delegation to OpenCode/Codex agents. A SwiftUI menu‑bar app provides status, live tool usage, session steering, and an operations dashboard secured with a per‑user bearer token.
curl -fsSL https://raw.githubusercontent.com/bulutarkan/mac-mcp/main/install.sh | bash
The script verifies dependencies, creates a Git checkout, sets up a Python virtual environment, generates a strong API key, installs the mac-mcp CLI, and builds the native menu‑bar app.mac-mcp start (add --ngrok to expose a public HTTPS endpoint).mac-mcp dashboard opens a loopback‑only web UI. Authentication uses a separate dashboard token stored at ~/.mac-mcp/dashboard-token.http://127.0.0.1:8000/mcp with the generated Authorization: Bearer <MCP_API_KEY> header or, if necessary, ?ApiKey= query param.tool_discover / tool_invoke.browser_do, browser_find, browser_act, browser_extract.trusted, standard, read_only) and fine‑grained approval gates.~/.mac-mcp/settings.json; changes (e.g., voice enable/disable) take effect without restart.Q: Do I need to run this as an admin? A: No. The installer works for standard macOS users; however, the app may request Accessibility, Screen Recording, and Automation permissions the first time those tools are used.
Q: Can I expose the API to the internet?
A: Yes, by enabling ngrok (mac-mcp start --ngrok). Always keep MCP_ALLOW_NO_AUTH set to false and use the generated bearer token.
Q: How do I add a tool to the default core surface?
A: Set MAC_MCP_CORE_EXTRA_TOOLS=name1,name2 in ~/.mac-mcp/settings.json or the server .env.
Q: What happens if the dashboard cannot reach the server? A: The menu bar shows a Degraded or Disconnected state, preserves the last snapshot, and backs off polling exponentially until the service recovers.
Q: Is there a way to disable voice without restarting?
A: Yes, toggle the ask_user_voice setting in the menu bar or edit ~/.mac-mcp/settings.json; changes are applied live.
Q: How are updates applied?
A: mac-mcp update pulls the latest origin/main, builds the Python runtime and native app, backs up the current runtime, restarts the service, and verifies health before finalizing.
Mac MCP is a local macOS control server for AI agents. It exposes your Mac through a native MCP endpoint and a REST/OpenAPI surface, with shell, files, browser automation, macOS UI control, delegated OpenCode/Codex agents, memory, Agent Skills, voice interaction, self-update tooling, and a local operations dashboard.
Security: Mac MCP can execute commands, read/write files, and control desktop apps. Keep MCP authentication enabled whenever the service is reachable outside localhost and expose it only to clients you trust. The operations dashboard is loopback-only and requires a separate per-user dashboard Bearer token; localhost is machine-local transport, not a same-user sandbox.
tool_discover + tool_invoke, so older capabilities are not removed.browser_do, tool_discover, and tool_invoke. All previous 81 tools remain callable.browser_do for one-call browser transactions: open a URL, wait, interact, extract targeted fields, optionally verify state, and optionally close the newly opened tab without extra MCP round trips.extract actions so agents can request only the data they need instead of pulling large DOM/HTML payloads into context.browser_find and browser_act are now part of the default core surface; visual observations retain compact DOM IDs alongside the image, and semantic browser extraction is more resilient on dynamic pages such as Google Maps.mac_observe, cutting unnecessary context and capture work.network_idle waits against Safari's transient about:blank state and preserved normal risk/profile enforcement for dynamically invoked tools.MAC_MCP_TOOL_PROFILE=full if a client explicitly needs the entire registered catalog advertised up front.1s → 2s → 4s → 8s → 16s → 30s max) and resets to the normal 2.5-second interval after recovery. A manual Retry performs an immediate refresh.ask_user_voice can be enabled/disabled live without removing the MCP tool from discovery.experimental_tool_disabled and instruct the agent to fall back to ask_user.~/.mac-mcp/settings.json; voice changes do not require an MCP restart.menu_app/ runtime alongside mcp_server/ and refreshes an already-installed menu app after updates.Mac MCP can inspect and interact with Safari and Chrome tabs in the background while you keep working in another app or browser tab. Here, background means a normal, visible Safari/Chrome tab that Mac MCP controls without bringing the browser or tab to the front; it is not a hidden/headless browser session.
tab_handle.browser_observe can return compact DOM context plus viewport, element, or full-page visuals without activating the browser, switching tabs, scrolling the user's page, or leaving screenshot files on disk.This is designed for workflows where an AI agent keeps working in one or more background browser tabs while the Mac remains usable normally.
swiftc)brew install python git ngrok
Optional helpers:
brew install cliclick brightness
The easiest way to install Mac MCP on a new Mac is the interactive installer:
curl -fsSL https://raw.githubusercontent.com/bulutarkan/mac-mcp/main/install.sh | bash
The installer is designed specifically to work safely through curl | bash while still reading interactive answers from the real terminal. It:
swiftc;cliclick and brightness optional;~/Projects/mac-mcp and a separate runtime at ~/mac-mcp without Git metadata;.env with mode 600;mac-mcp CLI at ~/.local/bin/mac-mcp and records the deployed commit for the built-in updater;Mac MCP.app menu bar controller in ~/Applications;?ApiKey= connection formats at the end;Existing source/runtime/CLI paths are never silently overwritten. If Mac MCP is already installed, use the built-in updater instead of re-running the installer over the same paths.
If you prefer to manage the checkout and Python environment yourself:
git clone https://github.com/bulutarkan/mac-mcp.git
cd mac-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
cp mcp_server/.env.example mcp_server/.env
Configure at minimum:
MCP_API_KEY=replace-with-a-long-random-token
MCP_ALLOW_NO_AUTH=false
MCP_ALLOW_SHELL=true
RATE_LIMIT_PER_MINUTE=120
# Optional public tunnel
NGROK_DOMAIN=your-domain.ngrok-free.dev
Generate a strong token:
python3 - <<'PY'
import secrets
print(secrets.token_urlsafe(48))
PY
When authentication is enabled, the preferred client credential is still:
Authorization: Bearer <MCP_API_KEY>
For MCP clients/connectors that cannot set an Authorization header, Mac MCP also accepts the configured global key in the endpoint URL:
https://your-domain.example/mcp?ApiKey=<MCP_API_KEY>
Authorization remains authoritative when both forms are supplied. Empty, duplicate, or invalid ApiKey query credentials are rejected while authentication is enabled. The server removes ApiKey from its local access-log URL before logging, but upstream proxies/tunnels can still observe query strings, so Bearer headers should be preferred whenever the client supports them.
Mac MCP uses a small canonical glossary so UI and documentation do not imply stronger isolation or invisibility than the product actually provides. See docs/TERMINOLOGY.md for the full definitions.
For an advanced two-account deployment, see Hardened deployment with a dedicated non-admin macOS user. It covers loopback authentication, an explicit ACL-shared directory, TCC/GUI-session limits, tool behavior, rollback, and a two-user validation matrix.
Mac MCP treats capability enforcement and human approval as separate security concepts. A capability being allowed means only that the Mac MCP server policy permits that tool/risk class. It does not mean a second confirmation prompt will appear before the action runs.
| Profile | Server-enforced capability behavior | Approval source | Automatic Mac MCP prompt |
|---|---|---|---|
trusted |
All registered capabilities; destructive families are not additionally restricted by the profile. | none |
No |
standard |
Blocks raw_execution and update_control; destructive operations are limited to browser/accessibility families; access-mode ceiling is read-only. |
none |
No |
read_only |
Allows read/network/browser/native-accessibility capabilities only and denies destructive calls. | none |
No |
ask_confirmation remains an explicit interaction tool and is not a blanket confirmation wrapper around normal tool calls. Separately, Mac MCP now has narrow server-side security gates for risky trust-boundary crossings: untrusted web context → privileged host action and detected credential/secret egress to an untrusted origin require a source-aware Allow Once / Block decision. Those grants are exact-action/origin-bound and single-use; they do not turn a permission profile into a general “approval-heavy” mode.
Delegated Codex workers currently run with Codex approval_policy="never"; their sandbox/access mode is separate from human approval. OpenCode permission behavior is also provider-side and must not be treated as a Mac MCP server confirmation guarantee.
Set the server capability profile with MAC_MCP_PERMISSION_PROFILE=trusted|standard|read_only. The native menu bar app reads /dashboard/api/security/semantics and shows Allowed Capabilities and Approval Behavior separately for the active profile. The three preset rows are clickable: choosing one persists the value in mcp_server/.env and applies it to new global requests immediately without restarting the server or ngrok. Existing delegated agents keep the scoped profile issued when they were started; new agents inherit the newly selected parent profile.
Browser resilience guards are configurable with MAC_MCP_NO_PROGRESS_THRESHOLD (default 4, range 2–10) and MAC_MCP_TAB_LEASE_TTL_S (default 300 seconds, range 30–3600). The no-progress breaker stops only repeated meaningful browser actions that fail to change DOM revision, URL, or title; wait/scroll/extract flows do not consume that budget. Delegated-agent tab ownership is logical and time-bounded: completing/cancelling/crashing an agent releases ownership without closing the user's tab, and the next agent must make a fresh browser_observe before acting on a previously owned handle.
Untrusted web provenance is sticky at the logical-session level. Once a session consumes third-party browser content, writing that content to a local scratch file, reading it back, closing the tab, or passing through unrelated read-only tools does not make the session trusted again. Privileged host actions continue through the web→host security gate until the work moves to an independent clean-room session. Delegated children spawned from a tainted session inherit the taint metadata (origin, reason, and credential fingerprints) without copying raw DOM or secrets into the security log; nested delegation preserves the inheritance chain. A genuinely independent MCP session with no transferred payload starts clean under the normal policy.
./menu_app/install_app.sh
Default location:
~/Applications/Mac MCP.app
The app is independent from the server:
mac-mcp start opens the app automatically when it is installed;The app uses the localhost dashboard APIs with a separate dashboard Bearer credential stored in ~/.mac-mcp/dashboard-token (mode 0600). The menu app reads that owner-only file locally and never needs the global MCP_API_KEY:
/dashboard/api/summary
/dashboard/api/security/semantics
/dashboard/api/events
/dashboard/api/agents
/dashboard/api/steering
Mac MCP keeps a Mac MCP logical session visible between tool calls instead of showing it only for the few milliseconds while a tool is running. It prefers stable conversation metadata supplied by the MCP client (for example OpenAI's conversation-scoped openai/session metadata), then generic _meta.client_id, and finally a reused stateful Streamable HTTP transport as a fallback. Raw identity values are hashed before entering steering state and are never exposed in the dashboard. This matters for hosts that create a fresh transport session for every tool call: repeated calls from the same conversation still collapse into one Working / Idle agent card.
Steering messages are kept in memory only and are bound to the selected logical agent, never to a global "next caller" queue. If the selected agent currently has a tool running, the prompt is appended to that tool's live response as structured _mac_mcp_steering content. If the agent is idle, the prompt remains queued for that logical session and its next requested tool is preempted before execution with a mac_mcp_steering_preempted tool error, so the agent sees the user's new direction before doing more work. A different conversation/session cannot consume that prompt. Logical steering sessions expire after 10 minutes of inactivity by default. The menu-bar Sessions disclosure lets you enter any positive number of minutes and persists that value in ~/.mac-mcp/settings.json; stateful protocol transports are separately bounded so short-lived client transports do not accumulate indefinitely. Raw steering text is not written into telemetry SQLite, and nested fallback calls such as tool_invoke do not create duplicate visible sessions.
Steering POST acceptance is idempotent for clients that send client_instruction_id. The menu app generates a UUID for each Send action and reuses that same UUID for a bounded retry when the HTTP result is ambiguous. Replaying the same session + client ID + text returns the original canonical st_* message instead of enqueuing a duplicate; reusing a client ID with different text or another live session returns 409 idempotency_conflict. Recent lifecycle rows include the client correlation ID so the menu app can recover an accepted message after a lost response. Legacy clients that omit client_instruction_id keep the original enqueue behavior. The in-memory idempotency index is bounded and is discarded with the logical session TTL.
For example, if an agent is researching with visible, non-focus-stealing browser automation in a Safari tab and you type stop using Airbnb and check Booking.com instead into that agent's Session, Mac MCP routes the instruction only to that logical agent. A running tool can return the steering immediately; an idle agent is interrupted before its next tool call so it can change course first.
The native controller remains @MainActor and keeps the same polling cadence, but it publishes decoded dashboard values only when they actually change. Session snapshots are diffed by stable session_id before replacing the observable array, preserving SwiftUI row identity and avoiding hierarchy invalidation for identical polls. Volatile duration updates are coalesced only while their rendered label would remain unchanged. This is a rendering/state-efficiency optimization, not a slower-refresh mode.
The menu bar derives three deterministic sections from the versioned lifecycle snapshot: Needs Attention (failed, unresolved/unknown, or recent disconnected/expired session events), Active (working, queued, delivered, pending, or awaiting acknowledgement), and Recent (retained idle ready/acknowledged sessions). Historical terminal rows are informational rather than steerable. The menu-bar status icon carries only an aggregate attention/active signal. Mac MCP does not show session Retry/Cancel controls because no such backend actions exist; connection Retry remains a separate dashboard-reachability action.
The steering API exposes schema_version: 1 and separates activity from instruction lifecycle. Legacy state=working|idle and queued fields remain for compatibility; new clients should prefer activity_state, lifecycle_state, pending_instruction_count, last_transition_at, and last_error.
The instruction lifecycle is intentionally small: ready → queued → delivered → acknowledged. If the underlying tool fails before queued steering can be delivered, the session enters failed while keeping the instruction pending for the next tool call. Transport-backed sessions emit disconnected when their MCP transport disappears, and idle sessions emit expired when their retention TTL elapses. Illegal lifecycle transitions are rejected internally instead of silently producing ambiguous state.
acknowledged is inferred when the same logical agent makes its next top-level tool request after receiving steering; it means the agent continued after the delivery boundary, not that the model sent a separate acknowledgement packet. Daemon/API reachability is a different connection concern and is handled separately by the menu app's connection UX.
The native controller does not treat a failed dashboard request as valid empty data. A successful empty /dashboard/api/steering response clears the session list normally; an HTTP error, timeout, or connection refusal preserves the last successful snapshot and marks it stale. If the app has never received a valid session snapshot, it shows Session data unavailable rather than No agent sessions yet.
A transport failure such as timeout or connection refusal enters disconnected; an HTTP error or invalid response from a reachable server enters degraded, including failures from the primary summary endpoint. HTTP status failures, timeouts, and connection-refused errors are surfaced separately. Automatic polling backs off from 1 second to a 30-second cap and returns to the normal 2.5-second cadence after the next complete successful refresh.
mac-mcp start
mac-mcp start --ngrok
mac-mcp status
mac-mcp restart --ngrok
mac-mcp stop
mac-mcp dashboard
Default local endpoint:
http://127.0.0.1:8000/mcp
A custom port can be supplied through MAC_MCP_PORT or CLI flags.
ask_user_voice speaks a short prompt, records the local answer, transcribes it with Groq Whisper, and returns the transcript to the calling agent.
The menu app manages:
Non-secret settings are stored in:
~/.mac-mcp/settings.json
Environment variables remain supported as fallbacks, including MAC_MCP_VOICE_GROQ_API_KEY, GROQ_API_KEY, MAC_MCP_VOICE_LANGUAGE, MAC_MCP_VOICE_INPUT_DEVICE, MAC_MCP_VOICE_OUTPUT_DEVICE, and MAC_MCP_VOICE_TTS_RATE.
Open it with the authenticated local launcher:
mac-mcp dashboard
The static dashboard shell is loopback-only. Every sensitive /dashboard/api/* request and the live /dashboard/events stream additionally require a separate dashboard Bearer token stored at ~/.mac-mcp/dashboard-token with mode 0600; the state directory is kept owner-only (0700). The CLI/menu app passes the browser credential in a URL fragment, which is not sent in the HTTP request, and dashboard JavaScript immediately moves it to sessionStorage, removes it from the address bar, and uses an Authorization header for API/SSE requests. The global connector MCP_API_KEY is not exposed to the browser.
The dashboard records sanitized MCP/REST tool activity, status, latency, recent delegated-agent state, active calls, and tool frequency. Provider identity fields such as OpenAI session/subject/organization/location are dropped from telemetry; the security migration also scrubs legacy persisted rows on first startup. Telemetry persists locally under:
~/.mac-mcp/dashboard/telemetry.sqlite3
Loopback means machine-local, not user-private. The dashboard token prevents unrelated unauthenticated local processes and browser-origin requests from using sensitive endpoints, but a malicious process already running as the same macOS user can generally read that user's files and is inside this trust boundary. Unix-domain sockets were evaluated for menu-app ↔ daemon traffic; they can provide filesystem-owner permissions but do not solve same-UID isolation and cannot be consumed directly by the browser dashboard, so authenticated loopback HTTP remains the single transport. See docs/LOCAL_API_SECURITY.md for the threat model and decision.
Mac MCP 2.0.5 advertises a compact 21-tool core surface by default, backed by 84 registered MCP capabilities. The 63 less-common tools remain available through tool_discover and tool_invoke, including every tool from the previous 81-tool surface.
Set MAC_MCP_TOOL_PROFILE=full to advertise all registered tools directly to the client. You can also add selected tools to the compact surface with MAC_MCP_CORE_EXTRA_TOOLS=name1,name2.
The capability set covers:
Use MCP tool discovery for the authoritative live schema.
mac-mcp update --check
mac-mcp update
The updater follows origin/main, blocks on dirty repositories, preserves runtime overlays and private files, creates a runtime backup, restarts the managed service, performs a health check, and rolls back managed runtime files if verification fails.
In 2.0, menu_app/ is part of the managed runtime. If Mac MCP.app is already installed, a successful update rebuilds and refreshes it automatically.
Grant only the permissions required by the tools you use:
mac_observe, mac_act, System Events, and desktop automation;ask_user_voice.Run tests:
python -m unittest discover -s tests -v
Build the native menu app without installing it:
./menu_app/build_app.sh /tmp/mac-mcp-build
Project layout:
mcp_server/ Python MCP server and dashboard
menu_app/ Native SwiftUI menu bar controller
tests/ Regression tests
openapi/ REST/OpenAPI schema assets
MIT
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by activepieces
A self‑hosted, open‑source platform that provides a no‑code builder for creating, versioning, and running AI‑driven automation workflows. Pieces are TypeScript‑based plugins that become MCP servers, allowing direct consumption by large language models.
by Skyvern-AI
Automates browser‑based workflows by leveraging large language models and computer‑vision techniques, turning natural‑language prompts into fully functional web interactions without writing custom scripts.
by ahujasid
Enables Claude AI to control Blender for prompt‑assisted 3D modeling, scene creation, and manipulation via a socket‑based Model Context Protocol server.
by PipedreamHQ
Connect APIs quickly with a free, hosted integration platform that enables event‑driven automations across 1,000+ services and supports custom code in Node.js, Python, Go, or Bash.
by elie222
Organizes email inbox, drafts replies in the user's tone, tracks follow‑ups, and provides analytics to achieve inbox zero quickly.
by grab
Enables Cursor AI to read and programmatically modify Figma designs through a Model Context Protocol integration.
by CursorTouch
Enables AI agents to control the Windows operating system, performing file navigation, application launching, UI interaction, QA testing, and other automation tasks through a lightweight server.
by ahujasid
Enables Claude AI to control Ableton Live in real time, allowing AI‑driven creation, editing, and playback of tracks, clips, instruments, and effects through a socket‑based server.
by leonardsellem
Provides tools and resources to enable AI assistants to manage and execute n8n workflows via natural language commands.