MCPArr
An MCP server for Sonarr (TV shows) and Radarr (movies). Manage your media library in natural language from Claude Code or any MCP client.
Ask things like "are we already tracking Severance?", "add Dune", "a new episode of The Bear should be out — poke it and grab the best torrent", or "what's airing this week?" — and MCPArr turns them into the right Sonarr/Radarr API calls.
What is MCPArr?
MCPArr is a small, self-hostable Model Context Protocol server written in TypeScript. It wraps the Sonarr and Radarr v3 REST APIs as a focused set of MCP tools, and ships skills — bundled best-practice guides that the model auto-discovers and follows so it performs the right sequence of actions (e.g. always check the library before adding, never grab a rejected release, confirm before deleting files).
It is heavily inspired by homeassistant-ai/ha-mcp
and reuses its skill-delivery pattern (MCP resources + a discovery tool whose description carries
each skill's trigger conditions).
Goals
- Answer "do we have this?" — search the library by title.
- Add shows & movies — look up by title/TMDB/TVDB id and add with the right profile & folder.
- Poke late downloads — when an episode/movie that should be out wasn't grabbed, run an interactive indexer search, sort releases by seeders descending, and grab the right one.
- Calendar — what's airing/releasing over a time window.
- Queue & health — inspect downloads, fix stuck items, diagnose "why isn't anything downloading".
- Quality & cleanup — upgrades (cutoff-unmet), monitoring, and removing items / reclaiming disk.
Features
Sonarr tools (TV)
| Tool | What it does |
|---|---|
sonarr_search_library |
Is a show already tracked? |
sonarr_lookup_series |
Find a show to add (by name / tvdb:/imdb: id) |
sonarr_list_profiles_and_folders |
Quality profiles & root folders |
sonarr_add_series |
Add a show (mutating) |
sonarr_list_episodes |
Episodes of a tracked show (to get an episodeId) |
sonarr_calendar |
Upcoming episodes |
sonarr_missing_episodes |
Aired-but-missing episodes |
sonarr_cutoff_unmet |
Episodes below quality cutoff (upgrade candidates) |
sonarr_search_releases |
Interactive release search, sorted by seeders desc |
sonarr_grab_release |
Download a chosen release (mutating) |
sonarr_queue |
Download queue |
sonarr_remove_queue_item |
Remove / blocklist a queue item (mutating) |
sonarr_health |
System status, disk, indexers, download clients |
sonarr_set_monitoring |
Monitor/unmonitor series or seasons (mutating) |
sonarr_delete_series |
Remove a show (optionally delete files) (mutating) |
Radarr tools (movies)
radarr_search_library, radarr_lookup_movie, radarr_list_profiles_and_folders,
radarr_add_movie (mutating), radarr_calendar, radarr_missing_movies, radarr_cutoff_unmet,
radarr_search_releases (seeders desc), radarr_grab_release (mutating), radarr_queue,
radarr_remove_queue_item (mutating), radarr_health, radarr_set_monitoring (mutating),
radarr_delete_movie (mutating).
Skills (bundled, auto-discovered)
mcparr_get_skill exposes 8 guides; the model loads the relevant one before acting:
media-library-management, late-episode-troubleshooter, interactive-release-search,
upcoming-calendar, queue-management, library-health-and-diagnostics, quality-and-upgrades,
monitoring-and-cleanup. See Skills.
Quick start
MCPArr speaks the Streamable HTTP MCP transport and runs as a long-lived container. Point it at your existing Sonarr/Radarr with environment variables, then connect Claude Code to it.
Find each API key in the app under Settings → General → Security → API Key.
Docker (CLI)
docker run -d --name mcparr \
-p 3000:3000 \
-e SONARR_URL=http://192.168.1.10:8989 \
-e SONARR_API_KEY=your-sonarr-key \
-e RADARR_URL=http://192.168.1.10:7878 \
-e RADARR_API_KEY=your-radarr-key \
ghcr.io/ondrejmirtes/mcparr:latest
Check it's up:
curl http://localhost:3000/health # -> {"status":"ok",...}
docker-compose
services:
mcparr:
image: ghcr.io/ondrejmirtes/mcparr:latest
container_name: mcparr
environment:
SONARR_URL: http://sonarr:8989
SONARR_API_KEY: ${SONARR_API_KEY}
RADARR_URL: http://radarr:7878
RADARR_API_KEY: ${RADARR_API_KEY}
# MCPARR_AUTH_TOKEN: ${MCPARR_AUTH_TOKEN} # recommended if reachable beyond localhost
# MCPARR_READ_ONLY: "true" # disable all mutating tools
TZ: Europe/Prague
ports:
- "3000:3000"
restart: unless-stopped
docker compose up -d
If Sonarr/Radarr run in the same compose project, put them on the same network and reference them
by service name (http://sonarr:8989). Otherwise use their real address.
Image tags: :latest (stable releases), :nightly (nightly build), :edge (latest main), and
:X.Y.Z semver tags. Images are multi-arch (amd64 + arm64).
Configuration
| Env var | Default | Purpose |
|---|---|---|
SONARR_URL |
– | Sonarr base URL, e.g. http://sonarr:8989 |
SONARR_API_KEY |
– | Sonarr API key (required if SONARR_URL is set) |
RADARR_URL |
– | Radarr base URL, e.g. http://radarr:7878 |
RADARR_API_KEY |
– | Radarr API key (required if RADARR_URL is set) |
MCPARR_HOST |
0.0.0.0 |
Bind address |
MCPARR_PORT |
3000 |
HTTP port |
MCPARR_PATH |
/mcp |
MCP endpoint path |
MCPARR_AUTH_TOKEN |
– | If set, clients must send Authorization: Bearer <token> |
MCPARR_READ_ONLY |
false |
When true, all mutating tools are disabled |
LOG_LEVEL |
info |
debug | info | warn | error |
TZ |
UTC |
Timezone (affects calendar formatting in logs) |
At least one service (Sonarr or Radarr) must be configured. Tools for an unconfigured service are not registered.
Connect to Claude Code
With the container running and reachable at http://localhost:3000/mcp:
claude mcp add --transport http mcparr http://localhost:3000/mcp
If you set MCPARR_AUTH_TOKEN, include the header:
claude mcp add --transport http mcparr http://localhost:3000/mcp \
--header "Authorization: Bearer your-token"
Or add it to your project's .mcp.json:
{
"mcpServers": {
"mcparr": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
Verify with /mcp inside Claude Code — you should see the MCPArr tools and the skill://… resources.
Skills
Skills are markdown workflow guides that make the model do the right thing without you spelling out every step. They are delivered two ways, so they auto-discover the moment you add the server:
- MCP resources — every skill file is exposed at
skill://<name>/<path>. Resource-capable clients (Claude Code) list them automatically; no install step. - The
mcparr_get_skilltool — its description embeds each skill's "TRIGGER THIS SKILL WHEN…" conditions, so the model fetches the relevant guide on demand (e.g. it loadslate-episode-troubleshooteras soon as you say "this episode didn't download"). This is the reliable path, since clients list resources but don't auto-inject their content.
Optional: install as native Claude Code skills. For always-on triggering without a tool call, copy the bundled skills into your skills directory:
git clone https://github.com/ondrejmirtes/mcparr
cp -r mcparr/skills/* ~/.claude/skills/ # or <project>/.claude/skills/
Example prompts
- "Are we already tracking Severance?"
- "Add the movie Dune: Part Two in 1080p."
- "Add The Bear and start searching for episodes."
- "A new episode of Slow Horses should be out by now — find it, grab the release with the most seeders that isn't rejected, and confirm it's downloading."
- "What episodes are airing in the next 7 days?"
- "What movies I'm tracking release this month?"
- "Why isn't anything downloading?"
- "Show me the download queue and clear out anything that's stalled."
- "Stop monitoring season 1 of The Office."
Security
- MCPArr holds your Sonarr/Radarr API keys and can add/delete library items. Do not expose its port to the public internet. Keep it on your LAN / behind a reverse proxy / VPN.
- Set
MCPARR_AUTH_TOKENto require a bearer token on the MCP endpoint. - Set
MCPARR_READ_ONLY=trueto expose only read tools (no add/grab/remove/delete). - The server runs as a non-root user inside the container.
Development & testing
npm install
npm run dev # run from source (tsx watch)
npm run lint # biome
npm run type-check # tsc --noEmit
npm test # unit tests (mocked HTTP)
npm run build # bundle to dist/ with tsup
End-to-end tests
The E2E suite spins up real Sonarr + Radarr containers (with pre-seeded API keys) plus a mock Torznab indexer, drives them through the MCPArr clients, and verifies via the actual API that each action took effect:
npm run e2e:up # start test/docker/docker-compose.test.yml
npm run test:e2e # add a show/movie & confirm it's tracked; calendar; queue;
# interactive search returns mock releases sorted by seeders desc
npm run e2e:down # tear down (-v)
tests/unit mocks the *arr HTTP layer (undici MockAgent); tests/e2e runs against the live
containers. CI (GitHub Actions) runs lint + type-check + unit + a Docker image smoke test on every
PR, and the full E2E stack on pushes to main.
How MCPArr is verified
Every release is exercised end-to-end before the image ships:
- Unit tests assert request shapes (add bodies, grab
guid+indexerId, queue params) and the seeders-descending sort. - E2E tests add a known show/movie to a fresh Sonarr/Radarr and read it back through the API, query the calendar and queue, and run an interactive search against the mock indexer asserting the releases come back ranked by peers.
- The Docker image is built, booted, and health-checked.
License
MIT — see LICENSE.
No comments yet
Be the first to share your take.