launchd-audit
MCP server for macOS that lists launchd jobs, cron entries, and brew services in plain English, flags the ones that are failing or stale, and can act on them without rm or sudo.
Ask an agent: what's running in the background on this Mac, and is anything broken?
totals: {"jobs": 31, "running": 8, "disabled": 0, "failing": 1, "stale": 1}
[failing] com.example.drift-check | exit=1
[stale] com.example.backup | daily at 03:00, no evidence for 41 days
macOS only. Python 3.11+. MIT.
Why
A machine that has been alive for a while accumulates agents from apps you uninstalled, cron lines from a test, brew services that log megabytes a day. launchctl list is a wall of PIDs. Plists are XML. Answering "is anything wrong?" means reading a dozen files by hand.
Four of the five tools are read-only. Mutation is one tool, dry-run by default. Design notes live in PHILOSOPHY.md. Tool contracts live in SPEC.md.
Tools
| Tool | Role |
|---|---|
job_health |
The audit. Failing, stale, or writing huge logs, with totals and reasons. Start here. |
list_scheduled_jobs |
Every launchd job, cron entry, and brew service. Schedules as "every 15 minutes", "daily at 03:00", "keep alive (respawn)". |
job_detail |
One job: plist (secrets masked), live launchctl state, log tails. |
search_job_logs |
Regex search in that job's output files, optional since date. |
job_action |
The only mutating tool. Dry-run unless confirm=true. |
Sources scanned: ~/Library/LaunchAgents, /Library/LaunchAgents, /Library/LaunchDaemons, crontab -l plus /etc/cron.d and /etc/crontab. Brew services are recognised by their homebrew.mxcl.* plists in ~/Library/LaunchAgents; brew itself is never called. /System/Library is skipped on purpose.
Safety
Guardrails are in the server, not in a companion skill or prompt.
- Dry-run by default. Without
confirm=true,job_actiononly reports what it would run: commands, files, warnings, and an undo recipe. - System daemons are listable and never modified. Anything in
/Library/LaunchDaemonsis refused, whatever it is called. - Nothing is deleted.
removeunloads the plist and moves it to Trash. Every applied action returns an undo recipe. - No sudo. A plist the current user cannot move is refused up front, not half-removed.
- Secrets are masked at parse time.
EnvironmentVariablesvalues never leave the process. Secret-lookingProgramArguments(--token=…,API_KEY=…,--password …) are redacted too. Masking is a heuristic: a secret hidden in a plain positional argument or inside a script the job runs is not covered. - Every applied action is appended to
~/.launchd-audit/actions.jsonl.
Install
Requires macOS, Python 3.11+, and uv. No clone needed: uvx fetches and runs the server straight from GitHub.
Claude Code
claude mcp add launchd-audit -- uvx --from git+https://github.com/aka-kika/launchd-audit launchd-audit
Claude Desktop, Cursor, and other JSON-configured clients
{
"mcpServers": {
"launchd-audit": {
"command": "uvx",
"args": ["--from", "git+https://github.com/aka-kika/launchd-audit", "launchd-audit"]
}
}
}
Pin a release with git+https://github.com/aka-kika/launchd-audit@v0.3.0.
From a local checkout
git clone https://github.com/aka-kika/launchd-audit.git
cd launchd-audit
uv sync
uv run pytest -q
uv run python scripts/smoke.py # spawns the server over stdio and calls the read-only tools
Then point the client at the checkout:
{
"mcpServers": {
"launchd-audit": {
"command": "uv",
"args": ["--directory", "/path/to/launchd-audit", "run", "launchd-audit"]
}
}
}
Use
Ask the client something like:
- "What scheduled jobs are on this Mac?"
- "Is anything quietly failing?"
- "Did the backup run yesterday?"
- "Preview disabling
com.vendor.sync."
Worked conversations are in EXAMPLES.md.
Limitations
/System/Librarylaunchd items are not scanned.- Full
system/domain runtime state needs root. Refused by design, so system daemon state shows asnot-loadedandlast_exitis unknown. - Stale detection uses log-file mtimes. Jobs with no declared output logs are not flagged stale.
last_exitin listings comes fromlaunchctl list, which reports 0 for a job that has never run.job_detailuseslaunchctl printand reportsnullin that case.- Cron entries have no live state (macOS cron does not expose one). Only
removeis supported for them. - Not Windows Task Scheduler. Not Linux systemd.
Docs
| File | Contents |
|---|---|
| PHILOSOPHY.md | Why it exists |
| SPEC.md | Inputs, outputs, safety rules |
| EXAMPLES.md | Five example sessions |
| CHANGELOG.md | Version history |
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file launchd_audit-0.3.0.tar.gz.
File metadata
- Download URL: launchd_audit-0.3.0.tar.gz
- Upload date:
- Size: 76.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ed70d66e0b3e5b659efb5bbe98a1336b4da5ba6349270cbaa1402165880ea82
|
|
| MD5 |
7b2e1961a9852f31b1e556d014efdbc1
|
|
| BLAKE2b-256 |
1b8f47e2753c395beda2bfe606e03e62b48e2fbfb93d4fa52c09ec0c62c740c2
|
Provenance
The following attestation bundles were made for launchd_audit-0.3.0.tar.gz:
Publisher:
publish.yml on aka-kika/launchd-audit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
launchd_audit-0.3.0.tar.gz -
Subject digest:
4ed70d66e0b3e5b659efb5bbe98a1336b4da5ba6349270cbaa1402165880ea82 - Sigstore transparency entry: 2774485998
- Sigstore integration time:
-
Permalink:
aka-kika/launchd-audit@c3937433a0e32665fbd758add0086da227082e3b -
Branch / Tag:
refs/heads/master - Owner: https://github.com/aka-kika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3937433a0e32665fbd758add0086da227082e3b -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file launchd_audit-0.3.0-py3-none-any.whl.
File metadata
- Download URL: launchd_audit-0.3.0-py3-none-any.whl
- Upload date:
- Size: 22.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cad168e1d81cb767a041529f445b72ef2e4f9e46860bee700446764a1cd6236
|
|
| MD5 |
ee1daecff6af0627a691a6d253be8efa
|
|
| BLAKE2b-256 |
b2217afdb6673ac1ec1dcac956c72542b4983c8dd12177197963156c53d2a492
|
Provenance
The following attestation bundles were made for launchd_audit-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on aka-kika/launchd-audit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
launchd_audit-0.3.0-py3-none-any.whl -
Subject digest:
2cad168e1d81cb767a041529f445b72ef2e4f9e46860bee700446764a1cd6236 - Sigstore transparency entry: 2774486076
- Sigstore integration time:
-
Permalink:
aka-kika/launchd-audit@c3937433a0e32665fbd758add0086da227082e3b -
Branch / Tag:
refs/heads/master - Owner: https://github.com/aka-kika
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3937433a0e32665fbd758add0086da227082e3b -
Trigger Event:
workflow_dispatch
-
Statement type: