A governed rmcp 3.1.2 port of the complete 19-tool LaunchdServices surface. It discovers, inspects, creates, edits, starts, stops, and removes allow-listed launchd services while placing every mutation behind sealed MCP 2026 MRTR approval.
The policy path is intentionally layered: tool-schema validation, manifest risk classification, MRTR approval, exact label resolution, canonical path allow-listing, plist validation/redaction, and only then launchctl or an atomic filesystem operation. System-owned plist roots remain read-only even when listed for discovery.
- All 19 tools advertise object output schemas, readable titles, and standard read-only, destructive, idempotent, and closed-world annotations.
- Existing JSON text is preserved for older clients and mirrored into MCP
structuredContent; rejected or failed operations returnisError: true. - The server does not advertise Tasks because launchd operations are bounded direct calls. The static tool catalog carries a 24-hour public cache hint.
- Annotations are advisory. Sealed MRTR, caller binding, exact-label resolution, plist validation, and canonical path allow-lists remain the enforcement boundary.
- All 12 lifecycle, plist, and GUI mutations require MRTR approval.
- Seven inspection tools are read-only.
- The default plist allow-list is only
~/Library/LaunchAgents. - Labels resolve by exact match before an operation is executed.
- System launchd plists can be discovered but cannot be overwritten or deleted.
- Global mutations use non-interactive privilege escalation and fail closed when unavailable.
- Secret-bearing environment values and command arguments are redacted from plist reads.
| Tool | Purpose | Risk | Approval |
|---|---|---|---|
launchd_list |
Discover allow-listed services with optional status/filter | Read-only | No |
launchd_status |
Return normalized loaded/running/error state | Read-only | No |
launchd_start |
Start or bootstrap a service | Identity/security action | Required |
launchd_stop |
Send SIGTERM or SIGKILL through launchctl | Identity/security action | Required |
launchd_restart |
Restart, bootstrapping when unloaded | Identity/security action | Required |
launchd_load |
Bootstrap a service in its domain | Identity/security action | Required |
launchd_unload |
Boot out a service | Identity/security action | Required |
launchd_enable |
Enable in launchd's override database | Identity/security action | Required |
launchd_disable |
Disable in launchd's override database | Identity/security action | Required |
launchd_logs |
Read bounded stdout/stderr tails | Read-only | No |
launchd_info |
Return bounded raw launchctl print output |
Read-only | No |
launchd_create |
Validate, write, and bootstrap a new plist | Identity/security action | Required |
launchd_delete |
Boot out and remove a non-system plist | Identity/security action | Required |
launchd_plist_read |
Read XML with secret redaction | Read-only | No |
launchd_plist_write |
Validate and atomically replace plist XML | Identity/security action | Required |
launchd_force_reload |
Boot out then bootstrap | Identity/security action | Required |
launchd_print_disabled |
Read a domain's override database | Read-only | No |
launchd_override_status |
Compare plist Disabled with override state |
Read-only | No |
launchd_open_gui |
Open the optional ZLaunchManager app | External write | Required |
The complete risk declarations live in mcp-server.toml.
Supported logical domains are user, global-agent, and global-daemon. Read/list calls may use all where documented.
export MCP_LAUNCHD_PLIST_ALLOWLIST="$HOME/Library/LaunchAgents:/Library/LaunchAgents:/Library/LaunchDaemons"
export MCP_LAUNCHD_LOG_ALLOWLIST="$HOME/Library/Logs:/var/log:/private/tmp"Entries are colon-separated canonical roots, not glob patterns. Adding a system root permits discovery, not modification of system-owned plist files.
- macOS with
/bin/launchctl. - Rust 1.94.1 or newer.
- A 32-byte-or-longer
MCP_REQUEST_STATE_KEYfor mutations. - Optional narrow non-interactive privilege for global agents/daemons.
- Optional
ZLaunchManager.appforlaunchd_open_gui.
git clone https://github.com/zavora-ai/mcp-launchd.git
cd mcp-launchd
cargo +1.94.1 build --release --locked
./target/release/mcp-launchd{
"mcpServers": {
"launchd": {
"command": "/absolute/path/to/mcp-launchd/target/release/mcp-launchd",
"env": {
"MCP_REQUEST_STATE_KEY": "replace-with-a-random-secret-of-at-least-32-bytes",
"MCP_LAUNCHD_PLIST_ALLOWLIST": "/Users/me/Library/LaunchAgents",
"MCP_LAUNCHD_LOG_ALLOWLIST": "/Users/me/Library/Logs:/private/tmp"
}
}
}
}Do not commit the request-state key. The manifest is embedded, so the binary is independent of its working directory.
{
"name": "launchd_create",
"arguments": {
"label": "com.example.heartbeat",
"program": "/usr/bin/logger",
"domain": "user",
"arguments": ["heartbeat"],
"run_at_load": true,
"start_interval": 300,
"stdout_path": "/Users/me/Library/Logs/heartbeat.log"
}
}The program must be absolute, present, and executable. Working and log directories must exist and remain inside their configured allow-lists. The MCP client must complete MRTR before creation occurs.
- Plists are capped at 10 MiB and parsed before writes.
Label, executable paths, arguments, working directory, log paths, interval, booleans, and environment dictionaries are validated.- New and replacement files are written atomically.
KeepAlivebooleans and dictionaries are recognized.- Environment values and secret-like arguments are redacted on reads.
- Paths are canonicalized or normalized without following a write outside an allowed root.
- Bounded command execution prevents a stuck
launchctlprocess from hanging the MCP server.
User LaunchAgents need no elevation. Global agent/daemon filesystem writes may require a root helper or narrowly scoped sudoers rule. docs/sudoers.example is a starting point only; narrow it to the exact domains and commands required by the host.
cargo +1.94.1 fmt --check
cargo +1.94.1 test --locked --all-targets --all-features
cargo +1.94.1 clippy --locked --all-targets --all-features -- -D warnings
cargo +1.94.1 build --release --lockedThe non-mutating suite checks all 19 manifest entries, both MCP protocol versions, path policy, plist validation/redaction, and a sealed MRTR round trip. The opt-in native lifecycle test creates a unique user agent, bootstraps it, checks status, unloads it, deletes it, and verifies cleanup:
cargo +1.94.1 test --locked --test lifecycle_smoke -- --ignored --nocapturermcp3.1.2adk-mcp-sdk0.2- Rust 1.94.1
- MCP
2025-11-25and2026-07-28 - stdio transport; direct bounded calls (Tasks intentionally not advertised)
Apache-2.0. See LICENSE.
Part of the Zavora AI MCP server ecosystem.