Arr Stack Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Arr Stack Mcp (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A Model Context Protocol server that exposes Sonarr, Radarr, Lidarr, and Jellyfin to any MCP client (Claude Desktop, Claude Code, n8n, ibis-bot, custom agents) through a hand-curated tool layer written for LLM consumption.
28 curated tools split across four services:
| Service | Tools | Highlights |
|---|---|---|
| Sonarr | 8 | series search, TVDB lookup, queue, calendar, missing episodes, add/delete |
| Radarr | 8 | movie search, TMDB lookup, queue, calendar, missing, add/delete |
| Lidarr | 7 | artist + album search, MusicBrainz lookup, queue, add/delete |
| Jellyfin | 5 | library search, recent additions, now-playing sessions, library scan |
Every tool:
ok, query, count, total, items).ToolError envelopes with self-suggesting hints.uvx arr-stack-mcp serve --transport stdiodocker run --rm -i \
-e SONARR_URL=http://host.docker.internal:8989 \
-e SONARR_API_KEY=$SONARR_API_KEY \
-e RADARR_URL=http://host.docker.internal:7878 \
-e RADARR_API_KEY=$RADARR_API_KEY \
-e LIDARR_URL=http://host.docker.internal:8686 \
-e LIDARR_API_KEY=$LIDARR_API_KEY \
-e JELLYFIN_URL=http://host.docker.internal:8096 \
-e JELLYFIN_API_KEY=$JELLYFIN_API_KEY \
ghcr.io/new-usemame/arr-stack-mcp:0.1.0git clone https://github.com/new-usemame/arr-stack-mcp
cd arr-stack-mcp
uv sync
uv run arr-stack-mcp init # writes a starter arr-stack-mcp.yaml
uv run arr-stack-mcp serve # boots stdio MCP serverAdd to ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or %APPDATA%\Claude\claude_desktop_config.json on Windows:
{
"mcpServers": {
"arr-stack": {
"command": "uvx",
"args": ["arr-stack-mcp", "serve"],
"env": {
"SONARR_URL": "http://localhost:8989",
"SONARR_API_KEY": "...",
"RADARR_URL": "http://localhost:7878",
"RADARR_API_KEY": "...",
"LIDARR_URL": "http://localhost:8686",
"LIDARR_API_KEY": "...",
"JELLYFIN_URL": "http://localhost:8096",
"JELLYFIN_API_KEY": "..."
}
}
}
}arr-stack-mcp init writes a starter arr-stack-mcp.yaml. Every secret can also be supplied via env var. Inline expansion of ${VAR} and ${VAR:-default} is supported.
log_level: info
transport:
stdio: true
http_enabled: false
http_host: 127.0.0.1
http_port: 8080
policy:
read_only: false
disable_destructive: false
confirm_token_ttl_seconds: 300
services:
sonarr:
enabled: true
url: http://localhost:8989
api_key: ${SONARR_API_KEY}
verify_tls: true
timeout_seconds: 30
radarr:
enabled: true
url: http://localhost:7878
api_key: ${RADARR_API_KEY}
lidarr:
enabled: true
url: http://localhost:8686
api_key: ${LIDARR_API_KEY}
jellyfin:
enabled: true
url: http://localhost:8096
api_key: ${JELLYFIN_API_KEY}
default_user_id: 4f8b6d2e-3a9c-4f1d-9c2a-1b3c5d7e9f01| Flag | Env | Effect |
|---|---|---|
--read-only | ARRSTACK_READ_ONLY=true | Skip every tool tagged write or destructive at registration. |
--disable-destructive | ARRSTACK_DISABLE_DESTRUCTIVE=true | Skip only the destructive tools (deletes). |
--dry-run | — | Plan-and-record mode (v0.2). Write/destructive tools short-circuit before the upstream mutation and log the intended payload to a ring buffer surfaced by stack.dryrun_log. Reads run normally. |
--transport stdio | — | Default. Use for Claude Desktop and Claude Code. |
--transport streamable-http | — | HTTP transport for n8n / remote consumers. |
--bearer-token-env ARR_STACK_MCP_BEARER_TOKEN | (env name) | Env var the server reads the bearer token from. Streamable-HTTP on a non-loopback bind refuses to start when this env var is unset. |
--config path/to/config.yaml | — | Override config path. Defaults to ./arr-stack-mcp.yaml, then XDG. |
Confirm tokens default to in-memory storage. Set policy.state_db_path in the config to persist tokens across server restarts and across streamable-HTTP request lifetimes:
policy:
state_db_path: ~/.local/state/arr-stack-mcp/state.dbThe path is created idempotently. SQLite WAL mode lets multiple worker processes share the same DB safely.
Opt-in runaway-agent protection. Tools without an entry are uncapped:
policy:
hourly_caps:
sonarr.series_add: 50
radarr.movie_add: 50
sonarr.series_delete: 10Per-process, per-tool, rolling-hour. Failed upstream calls still consume budget so a runaway loop hits the cap on the Nth attempt.
Tools follow the <service>.<verb_object> convention. Tags drive flag-based gating.
| Tool | Tag | Use |
|---|---|---|
sonarr.system_status | read | Version + branch + uptime. First call when diagnosing. |
sonarr.series_search | read | Search the existing library (already-added). |
sonarr.series_lookup | read | Search TVDB to discover series to add. |
sonarr.series_status | read | Per-season breakdown (v0.2). Episode counts, files on disk, missing. |
sonarr.queue | read | Active downloads with progress. |
sonarr.calendar | read | Upcoming + recently-aired episodes. |
sonarr.missing | read | Monitored episodes not on disk. |
sonarr.series_add | write | Add a series by TVDB id. Idempotent. |
sonarr.series_delete | destructive | Two-call confirm-token flow. |
| Tool | Tag | Use |
|---|---|---|
radarr.system_status | read | Mirrors Sonarr. |
radarr.movie_search | read | Search the existing library. |
radarr.movie_lookup | read | Search TMDB to discover movies to add. |
radarr.queue | read | Active downloads. |
radarr.calendar | read | Upcoming + recently-released movies. |
radarr.missing | read | Monitored movies not on disk. |
radarr.movie_add | write | Add a movie by TMDB id. Idempotent. |
radarr.movie_delete | destructive | Two-call confirm-token flow. |
| Tool | Tag | Use |
|---|---|---|
lidarr.system_status | read | — |
lidarr.artist_search | read | Search the existing library. |
lidarr.artist_lookup | read | Search MusicBrainz to discover artists. |
lidarr.artist_albums | read | List albums under one artist. |
lidarr.queue | read | Active downloads. |
lidarr.artist_add | write | Add an artist by MusicBrainz id. Idempotent. |
lidarr.artist_delete | destructive | Two-call confirm-token flow. |
| Tool | Tag | Use |
|---|---|---|
jellyfin.system_info | read | Version + server name. Public endpoint. |
jellyfin.library_search | read | Search items by name. Pass user_id= for per-user scoping (v0.2). |
jellyfin.recent_additions | read | Newest items by date_added. Pass user_id= for per-user scoping (v0.2). |
jellyfin.users_list | read | Enumerate user accounts (v0.2). Returns user_id, name, admin flag, last login. |
jellyfin.now_playing | read | Currently-active sessions with progress. |
jellyfin.scan_library | write | Trigger a library refresh. |
| Tool | Tag | Use |
|---|---|---|
prowlarr.system_status | read | Version + branch. First call when diagnosing Prowlarr. |
prowlarr.health | read | Health-check issues (warnings, errors, wiki links). |
prowlarr.indexer_list | read | Configured indexers with implementation, protocol, priority, enable. |
prowlarr.indexer_stats | read | Query / grab / failure counts per indexer + average response time. |
prowlarr.indexer_status | read | Currently-failing indexers with retry-after timestamps. |
prowlarr.indexer_test_all | write | Trigger the test-every-indexer probe; returns per-indexer pass/fail. |
prowlarr.search | read | Search across configured indexers. Returns title, indexer, size, age, seeders. Does NOT download — use *.add on Sonarr / Radarr / Lidarr with catalog id. |
| Tool | Tag | Use |
|---|---|---|
stack.health | read | Probe every enabled service's *.system_status in parallel. Returns reachability matrix + overall_ok. |
stack.dryrun_log | read | Read the ring buffer of would-have-fired mutations recorded under --dry-run. |
stack.report_issue | read | Compose a pre-filled GitHub issue URL the user can post upstream. Never auto-submits. |
stack.find_anywhere | read | Fan a query across every enabled arr + Jellyfin library; merged result with source per row. |
stack.queue_status_all | read | Aggregate download queue across Sonarr, Radarr, Lidarr. Normalized row shape. |
Destructive tools (*_delete) implement a two-call confirm:
# Call 1: returns plan + token, no side effect
> sonarr.series_delete(sonarr_id=42, delete_files=False)
{
"ok": true,
"needs_confirm": true,
"confirm_token": "abc123",
"summary": "remove 'Foo Bar' (sonarr_id=42) from Sonarr; keep files on disk",
"expires_in_seconds": 300
}
# Call 2: executes
> sonarr.series_delete(sonarr_id=42, delete_files=False, confirm_token="abc123")
{
"ok": true,
"deleted_sonarr_id": 42,
"title": "Foo Bar",
"files_deleted": false,
"msg": "deleted sonarr_id=42"
}Tokens are single-use, time-limited, and bound to the request payload — a token from one tool will not confirm another.
Two layers:
┌────────────────────────────────────────────────────────────┐
│ Curated MCP tools (arr_stack_mcp.tools.<service>.tools) │
│ Hand-written, LLM-friendly names, descriptions, schemas. │
├────────────────────────────────────────────────────────────┤
│ Generated thin clients (arr_stack_mcp.generated.<svc>) │
│ openapi-python-client output. Do not hand-edit; regen │
│ via scripts/regen-clients.sh on upstream version bumps. │
├────────────────────────────────────────────────────────────┤
│ httpx + pydantic + structlog + mcp.server.fastmcp │
└────────────────────────────────────────────────────────────┘See docs/ARCHITECTURE.md for the longer walk-through.
uv sync # install runtime + dev deps
uv run pytest -q # fast unit tests
uv run ruff check src/ tests/ # lint
uv run mypy src/ tests/ # type check (strict)
uv run ruff format src/ tests/ # formatIntegration tests require the docker test stack:
scripts/test-stack-up.sh # boots Sonarr/Radarr/Lidarr/Prowlarr/Jellyfin
uv run pytest -m integration # runs once stack is healthy
scripts/test-stack-down.sh --clean # teardownSee CONTRIBUTING.md for the OpenAPI regeneration flow and the verification checklist that gates every release.
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.