apple-system-mcp 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Apple MCP Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: apple-system-mcp
3
+ Version: 1.0.1
4
+ Summary: Local Apple System MCP server for macOS
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/JonathanRReed/Apple-MCPs
7
+ Project-URL: Repository, https://github.com/JonathanRReed/Apple-MCPs
8
+ Project-URL: Changelog, https://github.com/JonathanRReed/Apple-MCPs/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/JonathanRReed/Apple-MCPs/issues
10
+ Keywords: mcp,model-context-protocol,macos,apple,ai-agent,automation
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: MacOS X
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: apple-mcp-common<2,>=1.0.0
24
+ Requires-Dist: mcp<3,>=2.0.0
25
+ Requires-Dist: pydantic>=2.12.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest<10,>=8; extra == "dev"
28
+ Requires-Dist: ruff<1,>=0.12; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ <!-- mcp-name: io.github.jonathanrreed/apple-system-mcp -->
32
+
33
+ # Apple System MCP
34
+
35
+ Local MCP server for macOS system context, truthful Focus support metadata, assistant-relevant settings reads, explicit settings writes, and bounded GUI fallback automation.
36
+
37
+ ## Capabilities
38
+
39
+ - battery status
40
+ - frontmost app
41
+ - running applications
42
+ - clipboard read and write
43
+ - local notifications
44
+ - open an application
45
+ - appearance settings
46
+ - accessibility settings
47
+ - Dock settings
48
+ - Finder settings
49
+ - truthful Focus support metadata
50
+ - combined system context snapshots
51
+ - appearance mode write
52
+ - Finder visibility and bar writes
53
+ - Dock write controls for autohide and recent apps
54
+ - accessibility writes for reduce motion, increase contrast, and reduce transparency
55
+ - GUI fallback tools for menu clicks, key presses, text entry, button clicks, and pop-up selection
56
+ - preference-domain inspection via `defaults export`
57
+ - resources: `system://status`, `system://applications`, `system://settings`, `system://context`
58
+ - prompt: `system_capture_context`
59
+ - tool discovery helpers `search_tools` and `get_tool_info` for context-constrained clients
60
+
61
+ ## Install On This Mac
62
+
63
+ <details>
64
+ <summary>Quick start (uvx, from PyPI)</summary>
65
+
66
+ With [uv](https://docs.astral.sh/uv/getting-started/installation/) installed:
67
+
68
+ ```bash
69
+ uvx apple-system-mcp
70
+ ```
71
+
72
+ No clone, no venv management.
73
+
74
+ </details>
75
+
76
+ <details>
77
+ <summary>From a clone</summary>
78
+
79
+ ```bash
80
+ git clone https://github.com/JonathanRReed/Apple-MCPs.git
81
+ cd Apple-MCPs
82
+ uv sync --all-packages
83
+ ```
84
+
85
+ This builds one workspace environment with every server's entry point in `.venv/bin` (for example `.venv/bin/apple-system-mcp`). You can also point an MCP client at `AppleSystem-MCP/start.sh`, which prefers `uv run` and falls back to a plain venv bootstrap (Python 3.11+ required).
86
+
87
+ </details>
88
+
89
+ ## Install In AI Agents
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "apple-system": {
95
+ "command": "uvx",
96
+ "args": ["apple-system-mcp"],
97
+ "env": {
98
+ "APPLE_SYSTEM_MCP_SAFETY_MODE": "safe_manage"
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ Running from a clone instead? Use `/path/to/Apple-MCPs/AppleSystem-MCP/start.sh` as the command with empty `args`.
106
+
107
+ Claude Code:
108
+
109
+ ```bash
110
+ claude mcp add --transport stdio --scope project apple-system -- uvx apple-system-mcp
111
+ ```
112
+
113
+ ## Transport
114
+
115
+ `stdio` is the default and recommended transport. Set `APPLE_SYSTEM_MCP_TRANSPORT=streamable-http` (with optional `APPLE_SYSTEM_MCP_HOST` and `APPLE_SYSTEM_MCP_PORT`) to serve Streamable HTTP instead.
116
+
117
+ ## Prompting Notes
118
+
119
+ - `tools/list` returns the full System tool surface. Context-constrained clients can use `search_tools` first, then `get_tool_info` for the System tool they need.
120
+ - Use this server when the user’s current desktop context matters.
121
+ - Read battery state and the frontmost app before interruptive actions.
122
+ - Use the settings tools before falling back to raw `defaults read` in prompts or E2E checks.
123
+ - Use `APPLE_SYSTEM_MCP_SAFETY_MODE=safe_manage` for assistant-grade operation.
124
+ - Keep `safe_readonly` only for audit-only or context-only deployments.
125
+ - Keep GUI fallback narrow. Prefer native app-domain MCPs first, then explicit System tools, then GUI tools only when native support is missing.
126
+ - Focus reporting is best-effort and truthful. This server does not claim Notification Center history support when macOS does not expose it cleanly on an unsigned local install.
127
+
128
+ ## Health And Recovery
129
+
130
+ - `system_health`
131
+ - `system_permission_guide`
132
+ - `system_get_settings_snapshot`
133
+ - `system_get_focus_status`
134
+ - `system_get_context_snapshot`
135
+ - `system_read_preference_domain`
136
+
137
+ ## Assistant-Grade Control Surface
138
+
139
+ The launch System write surface is explicit, not generic:
140
+
141
+ - `system_set_appearance_mode`
142
+ - `system_set_show_all_extensions`
143
+ - `system_set_show_hidden_files`
144
+ - `system_set_finder_path_bar`
145
+ - `system_set_finder_status_bar`
146
+ - `system_set_dock_autohide`
147
+ - `system_set_dock_show_recents`
148
+ - `system_set_reduce_motion`
149
+ - `system_set_increase_contrast`
150
+ - `system_set_reduce_transparency`
151
+
152
+ The bounded GUI fallback surface is:
153
+
154
+ - `system_gui_list_menu_bar_items`
155
+ - `system_gui_click_menu_path`
156
+ - `system_gui_press_keys`
157
+ - `system_gui_type_text`
158
+ - `system_gui_click_button`
159
+ - `system_gui_choose_popup_value`
160
+
161
+ ## Launch Checklist
162
+
163
+ - Add `uvx apple-system-mcp` (or a clone's `AppleSystem-MCP/start.sh`) to your MCP client
164
+ - Reload or reconnect the client so the System tool surface is loaded into context
165
+ - Call `system_health` first
166
+ - If a scoped system action is blocked, call `system_permission_guide`
167
+ - Call `system_status` to verify the full context surface is available
@@ -0,0 +1,137 @@
1
+ <!-- mcp-name: io.github.jonathanrreed/apple-system-mcp -->
2
+
3
+ # Apple System MCP
4
+
5
+ Local MCP server for macOS system context, truthful Focus support metadata, assistant-relevant settings reads, explicit settings writes, and bounded GUI fallback automation.
6
+
7
+ ## Capabilities
8
+
9
+ - battery status
10
+ - frontmost app
11
+ - running applications
12
+ - clipboard read and write
13
+ - local notifications
14
+ - open an application
15
+ - appearance settings
16
+ - accessibility settings
17
+ - Dock settings
18
+ - Finder settings
19
+ - truthful Focus support metadata
20
+ - combined system context snapshots
21
+ - appearance mode write
22
+ - Finder visibility and bar writes
23
+ - Dock write controls for autohide and recent apps
24
+ - accessibility writes for reduce motion, increase contrast, and reduce transparency
25
+ - GUI fallback tools for menu clicks, key presses, text entry, button clicks, and pop-up selection
26
+ - preference-domain inspection via `defaults export`
27
+ - resources: `system://status`, `system://applications`, `system://settings`, `system://context`
28
+ - prompt: `system_capture_context`
29
+ - tool discovery helpers `search_tools` and `get_tool_info` for context-constrained clients
30
+
31
+ ## Install On This Mac
32
+
33
+ <details>
34
+ <summary>Quick start (uvx, from PyPI)</summary>
35
+
36
+ With [uv](https://docs.astral.sh/uv/getting-started/installation/) installed:
37
+
38
+ ```bash
39
+ uvx apple-system-mcp
40
+ ```
41
+
42
+ No clone, no venv management.
43
+
44
+ </details>
45
+
46
+ <details>
47
+ <summary>From a clone</summary>
48
+
49
+ ```bash
50
+ git clone https://github.com/JonathanRReed/Apple-MCPs.git
51
+ cd Apple-MCPs
52
+ uv sync --all-packages
53
+ ```
54
+
55
+ This builds one workspace environment with every server's entry point in `.venv/bin` (for example `.venv/bin/apple-system-mcp`). You can also point an MCP client at `AppleSystem-MCP/start.sh`, which prefers `uv run` and falls back to a plain venv bootstrap (Python 3.11+ required).
56
+
57
+ </details>
58
+
59
+ ## Install In AI Agents
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "apple-system": {
65
+ "command": "uvx",
66
+ "args": ["apple-system-mcp"],
67
+ "env": {
68
+ "APPLE_SYSTEM_MCP_SAFETY_MODE": "safe_manage"
69
+ }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ Running from a clone instead? Use `/path/to/Apple-MCPs/AppleSystem-MCP/start.sh` as the command with empty `args`.
76
+
77
+ Claude Code:
78
+
79
+ ```bash
80
+ claude mcp add --transport stdio --scope project apple-system -- uvx apple-system-mcp
81
+ ```
82
+
83
+ ## Transport
84
+
85
+ `stdio` is the default and recommended transport. Set `APPLE_SYSTEM_MCP_TRANSPORT=streamable-http` (with optional `APPLE_SYSTEM_MCP_HOST` and `APPLE_SYSTEM_MCP_PORT`) to serve Streamable HTTP instead.
86
+
87
+ ## Prompting Notes
88
+
89
+ - `tools/list` returns the full System tool surface. Context-constrained clients can use `search_tools` first, then `get_tool_info` for the System tool they need.
90
+ - Use this server when the user’s current desktop context matters.
91
+ - Read battery state and the frontmost app before interruptive actions.
92
+ - Use the settings tools before falling back to raw `defaults read` in prompts or E2E checks.
93
+ - Use `APPLE_SYSTEM_MCP_SAFETY_MODE=safe_manage` for assistant-grade operation.
94
+ - Keep `safe_readonly` only for audit-only or context-only deployments.
95
+ - Keep GUI fallback narrow. Prefer native app-domain MCPs first, then explicit System tools, then GUI tools only when native support is missing.
96
+ - Focus reporting is best-effort and truthful. This server does not claim Notification Center history support when macOS does not expose it cleanly on an unsigned local install.
97
+
98
+ ## Health And Recovery
99
+
100
+ - `system_health`
101
+ - `system_permission_guide`
102
+ - `system_get_settings_snapshot`
103
+ - `system_get_focus_status`
104
+ - `system_get_context_snapshot`
105
+ - `system_read_preference_domain`
106
+
107
+ ## Assistant-Grade Control Surface
108
+
109
+ The launch System write surface is explicit, not generic:
110
+
111
+ - `system_set_appearance_mode`
112
+ - `system_set_show_all_extensions`
113
+ - `system_set_show_hidden_files`
114
+ - `system_set_finder_path_bar`
115
+ - `system_set_finder_status_bar`
116
+ - `system_set_dock_autohide`
117
+ - `system_set_dock_show_recents`
118
+ - `system_set_reduce_motion`
119
+ - `system_set_increase_contrast`
120
+ - `system_set_reduce_transparency`
121
+
122
+ The bounded GUI fallback surface is:
123
+
124
+ - `system_gui_list_menu_bar_items`
125
+ - `system_gui_click_menu_path`
126
+ - `system_gui_press_keys`
127
+ - `system_gui_type_text`
128
+ - `system_gui_click_button`
129
+ - `system_gui_choose_popup_value`
130
+
131
+ ## Launch Checklist
132
+
133
+ - Add `uvx apple-system-mcp` (or a clone's `AppleSystem-MCP/start.sh`) to your MCP client
134
+ - Reload or reconnect the client so the System tool surface is loaded into context
135
+ - Call `system_health` first
136
+ - If a scoped system action is blocked, call `system_permission_guide`
137
+ - Call `system_status` to verify the full context surface is available
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "apple-system-mcp"
7
+ version = "1.0.1"
8
+ description = "Local Apple System MCP server for macOS"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ keywords = ["mcp", "model-context-protocol", "macos", "apple", "ai-agent", "automation"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Environment :: MacOS X",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: MacOS",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "apple-mcp-common>=1.0.0,<2",
27
+ "mcp>=2.0.0,<3",
28
+ "pydantic>=2.12.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/JonathanRReed/Apple-MCPs"
33
+ Repository = "https://github.com/JonathanRReed/Apple-MCPs"
34
+ Changelog = "https://github.com/JonathanRReed/Apple-MCPs/blob/main/CHANGELOG.md"
35
+ Issues = "https://github.com/JonathanRReed/Apple-MCPs/issues"
36
+
37
+ [project.scripts]
38
+ apple-system-mcp = "apple_system_mcp.tools:main"
39
+
40
+ [project.optional-dependencies]
41
+ dev = [
42
+ "pytest>=8,<10",
43
+ "ruff>=0.12,<1",
44
+ ]
45
+
46
+ [tool.setuptools]
47
+ package-dir = {"" = "src"}
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+
52
+ [tool.pytest.ini_options]
53
+ pythonpath = ["src"]
54
+ testpaths = ["tests"]
55
+
56
+ [tool.uv.sources]
57
+ apple-mcp-common = { workspace = true }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Apple System MCP package."""
@@ -0,0 +1,36 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from functools import lru_cache
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Settings:
8
+ server_name: str
9
+ version: str
10
+ safety_mode: str
11
+ transport: str
12
+ host: str
13
+ port: int
14
+ log_level: str
15
+
16
+
17
+ def _parse_int(value: str | None, default: int) -> int:
18
+ if value is None:
19
+ return default
20
+ try:
21
+ return int(value)
22
+ except ValueError:
23
+ return default
24
+
25
+
26
+ @lru_cache(maxsize=1)
27
+ def load_settings() -> Settings:
28
+ return Settings(
29
+ server_name="Apple System MCP",
30
+ version="1.0.1",
31
+ safety_mode=os.environ.get("APPLE_SYSTEM_MCP_SAFETY_MODE", "safe_manage").strip().lower() or "safe_manage",
32
+ transport=os.environ.get("APPLE_SYSTEM_MCP_TRANSPORT", "stdio").strip().lower() or "stdio",
33
+ host=os.environ.get("APPLE_SYSTEM_MCP_HOST", "127.0.0.1"),
34
+ port=_parse_int(os.environ.get("APPLE_SYSTEM_MCP_PORT"), 8000),
35
+ log_level=os.environ.get("APPLE_SYSTEM_MCP_LOG_LEVEL", "INFO").strip().upper() or "INFO",
36
+ )
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ToolError(BaseModel):
7
+ error_code: str = Field(description="Stable machine-readable error code")
8
+ message: str = Field(description="Human-readable error message")
9
+ suggestion: str | None = Field(default=None, description="Suggested next step")
10
+
11
+
12
+ class ErrorResponse(BaseModel):
13
+ ok: bool = False
14
+ error: ToolError
15
+
16
+
17
+ class HealthResponse(BaseModel):
18
+ ok: bool = True
19
+ server_name: str
20
+ version: str
21
+ safety_mode: str
22
+ transport: str
23
+ capabilities: list[str]
24
+ supports: list[str]
25
+
26
+
27
+ class BatteryStatus(BaseModel):
28
+ percentage: int | None = None
29
+ power_source: str | None = None
30
+ charging: bool | None = None
31
+ raw: str
32
+
33
+
34
+ class AppRecord(BaseModel):
35
+ name: str
36
+ bundle_id: str | None = None
37
+ process_id: int | None = None
38
+
39
+
40
+ class ClipboardResponse(BaseModel):
41
+ ok: bool = True
42
+ text: str
43
+
44
+
45
+ class NotificationResponse(BaseModel):
46
+ ok: bool = True
47
+ delivered: bool
48
+ title: str
49
+
50
+
51
+ class OpenAppResponse(BaseModel):
52
+ ok: bool = True
53
+ opened: bool
54
+ application: str
55
+ bundle_id: str | None = None
56
+
57
+
58
+ class RunningAppsResponse(BaseModel):
59
+ ok: bool = True
60
+ apps: list[AppRecord]
61
+ count: int
62
+
63
+
64
+ class StatusResponse(BaseModel):
65
+ ok: bool = True
66
+ battery: BatteryStatus | None = None
67
+ frontmost_app: str | None = None
68
+ frontmost_application: AppRecord | None = None
69
+ running_apps_count: int | None = None
70
+
71
+
72
+ class SettingsDomainsResponse(BaseModel):
73
+ ok: bool = True
74
+ domains: list[dict[str, str]]
75
+ count: int
76
+
77
+
78
+ class SettingsSectionResponse(BaseModel):
79
+ ok: bool = True
80
+ section: str
81
+ values: dict[str, object]
82
+
83
+
84
+ class SettingsSnapshotResponse(BaseModel):
85
+ ok: bool = True
86
+ appearance: dict[str, object]
87
+ accessibility: dict[str, object]
88
+ dock: dict[str, object]
89
+ finder: dict[str, object]
90
+
91
+
92
+ class FocusStatusResponse(BaseModel):
93
+ ok: bool = True
94
+ focus_supported: bool
95
+ focus_active: bool | None = None
96
+ focus_name: str | None = None
97
+ observed_at: str
98
+ source: str
99
+ confidence: float | None = None
100
+ notes: list[str] = Field(default_factory=list)
101
+
102
+
103
+ class SystemContextResponse(BaseModel):
104
+ ok: bool = True
105
+ observed_at: str
106
+ battery: BatteryStatus | None = None
107
+ frontmost_app: str | None = None
108
+ frontmost_application: AppRecord | None = None
109
+ running_apps_count: int | None = None
110
+ focus: FocusStatusResponse
111
+ notification_history_supported: bool = False
112
+ notification_history_notes: list[str] = Field(default_factory=list)
113
+
114
+
115
+ class PreferenceDomainResponse(BaseModel):
116
+ ok: bool = True
117
+ domain: str
118
+ current_host: bool = False
119
+ values: dict[str, object]
120
+
121
+
122
+ class SettingMutationResponse(BaseModel):
123
+ ok: bool = True
124
+ section: str
125
+ setting: str
126
+ requested_value: bool | str
127
+ observed_value: bool | str | None = None
128
+ restarted_processes: list[str] = Field(default_factory=list)
129
+ used_gui_fallback: bool = False
130
+
131
+
132
+ class GuiMenuItemsResponse(BaseModel):
133
+ ok: bool = True
134
+ application: str
135
+ bundle_id: str | None = None
136
+ process_id: int | None = None
137
+ menu_bar_items: list[str]
138
+ count: int
139
+ used_gui_fallback: bool = True
140
+
141
+
142
+ class GuiActionResponse(BaseModel):
143
+ ok: bool = True
144
+ action: str
145
+ application: str | None = None
146
+ bundle_id: str | None = None
147
+ process_id: int | None = None
148
+ target: str | None = None
149
+ selector_type: str | None = None
150
+ value: bool | str | list[str] | None = None
151
+ used_gui_fallback: bool = True
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from apple_system_mcp.config import load_settings
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class SafetyError(Exception):
10
+ error_code: str
11
+ message: str
12
+ suggestion: str | None = None
13
+
14
+
15
+ READ_ONLY_ACTIONS = {
16
+ "system_health",
17
+ "system_status",
18
+ "system_get_battery",
19
+ "system_get_frontmost_app",
20
+ "system_list_running_apps",
21
+ "system_get_clipboard",
22
+ "system_list_settings_domains",
23
+ "system_get_appearance_settings",
24
+ "system_get_accessibility_settings",
25
+ "system_get_dock_settings",
26
+ "system_get_finder_settings",
27
+ "system_get_settings_snapshot",
28
+ "system_get_focus_status",
29
+ "system_get_context_snapshot",
30
+ "system_read_preference_domain",
31
+ }
32
+
33
+ MANAGE_ACTIONS = {
34
+ "system_set_clipboard",
35
+ "system_show_notification",
36
+ "system_open_application",
37
+ "system_set_appearance_mode",
38
+ "system_set_show_all_extensions",
39
+ "system_set_show_hidden_files",
40
+ "system_set_finder_path_bar",
41
+ "system_set_finder_status_bar",
42
+ "system_set_dock_autohide",
43
+ "system_set_dock_show_recents",
44
+ "system_set_reduce_motion",
45
+ "system_set_increase_contrast",
46
+ "system_set_reduce_transparency",
47
+ "system_gui_list_menu_bar_items",
48
+ "system_gui_click_menu_path",
49
+ "system_gui_press_keys",
50
+ "system_gui_type_text",
51
+ "system_gui_click_button",
52
+ "system_gui_choose_popup_value",
53
+ }
54
+
55
+
56
+ def ensure_action_allowed(action: str) -> None:
57
+ safety_mode = load_settings().safety_mode
58
+ if action in READ_ONLY_ACTIONS:
59
+ return
60
+ if action in MANAGE_ACTIONS and safety_mode in {"safe_manage", "full_access"}:
61
+ return
62
+ raise SafetyError(
63
+ error_code="SAFETY_RESTRICTION",
64
+ message=f"{action} is not allowed while APPLE_SYSTEM_MCP_SAFETY_MODE={safety_mode}.",
65
+ suggestion="Use safe_manage or full_access for system mutation tools.",
66
+ )