macos-mcp-server
Integrates with macOS to manage system settings, apps, windows, audio, displays, screenshots, and Focus mode.
Control macOS system settings, apps, windows, audio, displays, screenshots, and Focus mode.
Click on "Deploy 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., "@macos-mcp-serverTake a screenshot of my current window"
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.
macOS-only. This server controls the local macOS system — it requires the host machine to be running macOS. HTTP transport is supported for completeness, but the practical use case is stdio: run it locally and point your MCP client at it.
Overview
macOS system control — application lifecycle, window management, audio and display routing, screenshots, Finder integration, notifications, and Focus mode. Launch, quit, and arrange apps and windows, switch audio devices, capture screenshots, and toggle Focus mode from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
Tools
Tool | Description |
| System snapshot: battery level and charging status, power source, Wi-Fi SSID, hostname, macOS version, uptime, and display count |
| Reports Accessibility, Screen Recording, Automation > Finder, and Notification permission status for the calling process |
| List running apps, get the frontmost app, launch, quit, force-quit, hide, or show applications |
| List, focus, move, resize, move_resize, minimize, fullscreen, or close windows across all visible apps |
| Get or set system output volume (0–100) and mute state |
| List audio devices, get current defaults, or switch the default input/output device |
| Get or set dark/light mode |
| Lock the screen or put the display to sleep |
| Capture full screen, display, named app window, or pixel region; saves PNG, optional base64 JPEG preview |
| List connected displays and apply named display layout presets |
| Post a notification to macOS Notification Center |
| Get or set Do Not Disturb / Focus mode |
| Frontmost path, current selection, reveal, open with app, or move to Trash |
Resources
Resource | Description |
| Current macOS system snapshot: battery, power source, Wi-Fi SSID, hostname, version, uptime, display count |
| All audio input and output devices, including which is the current default. Requires SwitchAudioSource CLI. |
| Connected display inventory including persistent IDs, type, resolution, origin, rotation, scaling, and enabled state. Requires displayplacer CLI. |
Resource data is also accessible via macos_get_info, macos_control_audio (action=list), and macos_manage_displays (action=list).
Related MCP server: mcp-server-macos-use
Capability reference
macos_get_info tool
Battery level (0–100), charging state, and power source (
AC,Battery,UPS);nullon desktops with no batteryWi-Fi connection status and SSID
Hostname, macOS version string (e.g.
"15.1.0"), uptime in secondsConnected display count
No permissions required
macos_check_permissions tool
Reports Accessibility (window manipulation, app hide/show), Screen Recording (window screenshots), Automation > Finder (Finder selection), and Notifications (always granted — osascript bypasses Do Not Disturb)
Returns the calling process name (e.g.
"ghostty","node") so you know which process to grant permissions forRead-only — checks status without triggering an OS permission prompt
Run this first when debugging why another tool is failing
macos_manage_apps tool
list— running user-facing apps with name, bundle ID, PID, visible, and frontmost flagsfrontmost— name, bundle ID, PID, and frontmost window title of the active applaunch— open or activate byapp_nameorbundle_id;hidden=truestarts in the backgroundquit(graceful AppleScript quit) vs.force_quit(SIGKILL, no save prompt)hide/show— toggle visibility; requires Accessibilityquit,force_quit,hide, andshowfirst check that the app is running — resolved by application name the waytell applicationdoes ("Visual Studio Code"finds theCodeprocess), without launching it — and returnnot_runningwhen it isn'tTyped errors:
app_not_found(launchof an app that isn't installed),no_frontmost_app,not_running,accessibility_required
macos_manage_windows tool
list— all visible windows across apps, with position, size, minimized state, and 0-baseddisplay_indexfocus,move,resize,move_resize,minimize,fullscreen,close— target byapp_nameor exactwindow_title(title takes precedence when both are given)moveneedsx/y,resizeneedswidth/height(both greater than 0),move_resizeneeds all fourlistandfocusrequire no permissions; every other action requires AccessibilityTyped errors:
window_not_found,accessibility_required
macos_control_volume tool
get— current output volume (0–100) and mute stateset— requireslevel(0–100),muted, or both;level=0does not muteAlways returns current state, with the
actionechoed
macos_control_audio tool
list— all input/output devices with anis_defaultflag; filter withtypecurrent— default input and output device namesswitch_output/switch_input— case-insensitive partial name match ("MacBook"matches"MacBook Pro Microphone")Volume level is separate (
macos_control_volume)Requires SwitchAudioSource CLI (
brew install switchaudio-osx); typed errorsdevice_not_found,switchaudio_unavailable
macos_control_appearance tool
get— returnsdark_mode: true/false, with theactionechoedsetrequiresmode: "dark" | "light" | "toggle"—dark/lightare idempotent,toggleflips on each callScripts System Events; typed error
accessibility_requiredwhen Automation > System Events is denied
macos_control_system tool
lock— ⌃⌘Q via Accessibility; falls back to the ScreenSaverEngine binary when Accessibility isn't grantedsleep_display—pmset displaysleepnow; no permissions requiredBoth operations are immediate and reversible with any input (wake/unlock)
macos_take_screenshot tool
target:screen,display(0-based integerdisplay_index, default 0),region(pixel rect,width/heightgreater than 0) — no Screen Recording required;window(byapp_name) requires Screen RecordingAlways saves a full-resolution PNG;
pathdefaults toMACOS_SCREENSHOT_DIR/<timestamp>.png, falling back to~/Desktop; a custompathmust be within~/Desktop,/tmp, or the home directoryinclude_data=trueadds a base64 JPEGpreview(max 1024px wide, ~70% quality) pluspreview_width/preview_heightTyped errors:
screen_recording_required,window_not_found,display_not_found,path_not_writable
macos_manage_displays tool
list— persistent ID, connection type, resolution, refresh rate, origin, rotation, scaling, and enabled state, pluscurrent_config(a displayplacer command that reproduces the active arrangement)apply_layout— activates a named preset fromMACOS_DISPLAY_LAYOUTS; raw displayplacer args are never accepted from the callerRequires displayplacer CLI (
brew install jakehilborn/jakehilborn/displayplacer); typed errorsdisplayplacer_not_found,layout_not_found
macos_send_notification tool
titlerequired;body,subtitle, andsound=true(default notification sound) are optionalEach call creates a new notification — not idempotent
Bypasses Do Not Disturb; no permission required
macos_manage_focus tool
get— best-effort; reads the Focus assertion database when accessible, returnsstatus: "active" | "inactive" | "unknown";unknownis expected on macOS 13+ where the database is SIP-protectedset— requires the built-in"Set Focus"shortcut in Shortcuts.app (present by default on macOS 12+);modemust exactly match a configured Focus profile (e.g."Do Not Disturb","Work");enableddefaults totrueTyped errors:
shortcuts_unavailable,focus_not_found
macos_manage_finder tool
frontmost_path— POSIX path of the active Finder window, ornullwhen none is openget_selection— POSIX paths of selected itemsreveal(open -R),open_with(open -a <App>, or the default app whenapp_nameis omitted),trash(moves to Trash — recoverable, never a permanent delete)open_withandtrashcheck that the path exists before callingopenor Finderfrontmost_path,get_selection, andtrashscript Finder and require Automation > Finder permission;revealandopen_withneed noneTyped errors:
finder_not_open,path_not_found,app_not_found(unknownopen_withapp),trash_refused(Finder declined an existing item — locked, in use, or no Trash on the volume),accessibility_required
macos://system/info resource
Current macOS system snapshot as
application/json— battery, power source, Wi-Fi SSID, hostname, version, uptime, display countSame data is also reachable via
macos_get_info
macos://audio/devices resource
All audio input and output devices, including which is the current default, as
application/jsonRequires SwitchAudioSource CLI
Same data is also reachable via
macos_control_audio(action=list)
macos://displays resource
Connected display inventory — persistent IDs, type, resolution, origin, rotation, scaling, enabled state, plus
current_config— asapplication/jsonRequires displayplacer CLI
Same data is also reachable via
macos_manage_displays(action=list)
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
macOS-specific:
osascript service wraps both JXA (
runJxa) and AppleScript (runAppleScript) with a configurable timeoutSwitchAudioSource and displayplacer integrations are optional dependencies — the affected tools fail with
ServiceUnavailableand an install instruction when the CLI is absentscreencapture + sips pipeline for full-resolution PNG capture and downscaled JPEG preview generation
system_profiler, pmset, and networksetup for hardware, battery, and Wi-Fi state
Permission-first design —
macos_check_permissionsreports exactly which process needs which grant before a tool hitsForbidden
Agent-friendly output:
Permission errors carry the
accessibility_requiredreason and grant instructions for the permission actually denied —Privacy & Security > Accessibility, orPrivacy & Security > Automationfor the named appA missing per-action argument (
setwithoutmode,quitwithoutapp_name) is rejected before anything runs, as-32602with reasoninvalid_argumentsand a hint naming what to send; each multi-action tool advertises those requirements in itsinputSchemaErrors carry the failing program's own error text, never its command line or script source
Optional CLI dependencies surface
ServiceUnavailablewith the exactbrew installcommand neededmacos_manage_windows action=listreportsdisplay_indexon every window so agents can reason about multi-monitor layoutsmacos_take_screenshotseparates the full-resolution disk write from an optional base64 preview, keeping response size manageable
Getting started
This server is local-only — it controls the macOS system it runs on. Use STDIO transport with your MCP client.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"macos-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/macos-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}Or with npx (no Bun required):
{
"mcpServers": {
"macos-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/macos-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcpPrerequisites
macOS 12 (Monterey) or higher.
Bun v1.4.0 or higher (or Node.js v24+).
Optional: SwitchAudioSource for audio routing (
brew install switchaudio-osx).Optional: displayplacer for display management (
brew install jakehilborn/jakehilborn/displayplacer).
Some tools require macOS permissions granted to the terminal or MCP host app:
Permission | Required by |
Accessibility |
|
Screen Recording |
|
Automation > Finder |
|
Use macos_check_permissions to check current status before running permission-gated operations.
Installation
Clone the repository:
git clone https://github.com/cyanheads/macos-mcp-server.gitNavigate into the directory:
cd macos-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env if you want to set MACOS_SCREENSHOT_DIR or MACOS_DISPLAY_LAYOUTSConfiguration
Variable | Description | Default |
| Default directory for screenshot files. |
|
| JSON object mapping layout names to displayplacer argument strings. Used by |
|
| Transport: |
|
| Port for HTTP server. |
|
| Auth mode: |
|
| Log level. |
|
| Session storage: |
|
| Enable OpenTelemetry instrumentation. |
|
See .env.example for the full list of optional overrides.
Display layout example:
# Get the current displayplacer command for your setup:
displayplacer list
# Then configure named layouts in your env:
MACOS_DISPLAY_LAYOUTS='{"office":"id:1234 res:2560x1440 hz:60 color_depth:8 scaling:on origin:(0,0) degree:0 id:5678 res:1920x1080 hz:60 color_depth:8 scaling:on origin:(2560,0) degree:0"}'Running the server
Local development
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# Run checks
bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specProject structure
Directory | Purpose |
|
|
|
|
| 13 tool definitions ( |
| Per-action argument requirements — enforced by the input schema and advertised in |
| 3 resource definitions ( |
| osascript JXA + AppleScript runner with configurable timeout and permission-denial classification |
| Caller-safe error text for a failed CLI call (never the command line) |
| SwitchAudioSource device listing and switching |
| displayplacer list and apply-layout |
| screencapture + sips PNG capture and JPEG preview |
| Battery, Wi-Fi, hostname, uptime via system_profiler/pmset |
| Tool tests mirroring definitions |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging; noconsolecallsRead actual system/CLI state and never fabricate it — return
nullorunknownwhen the OS can't answer (e.g. battery on desktops, Focus status under SIP protection) rather than guessingServices are singletons initialized in
createApp()and accessed viaget*Service()accessors
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
Use your own Mac from ChatGPT, Claude or Codex: files, commands, documents, and a browser.
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
Read and write Mission Control state via MCP — projects, tasks, subtasks, templates, status updates.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides native macOS computer control tools including mouse and keyboard simulation, screenshot capture, and application management for MCP-compatible agents. It enables AI assistants to directly interact with the macOS operating system and installed apps through standard tool calls.24488 npm8MIT
- AlicenseNot gradedqualityDmaintenanceEnables controlling macOS applications via accessibility APIs, supporting actions like clicking, typing, and keyboard input through MCP commands.22 npm354MIT
- AlicenseAqualityBmaintenanceA local MCP server that exposes macOS automation actions (AppleScript + CLIs) as tools, enabling MCP clients on your Mac to control apps, system settings, and more.39MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to control macOS via accessibility and screen recording, providing tools to list apps, observe UI, click, type, press keys, and scroll.MIT