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.

CI GHCR License: MIT

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:

  1. MCP resources — every skill file is exposed at skill://<name>/<path>. Resource-capable clients (Claude Code) list them automatically; no install step.
  2. The mcparr_get_skill tool — its description embeds each skill's "TRIGGER THIS SKILL WHEN…" conditions, so the model fetches the relevant guide on demand (e.g. it loads late-episode-troubleshooter as 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_TOKEN to require a bearer token on the MCP endpoint.
  • Set MCPARR_READ_ONLY=true to 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:

  1. Unit tests assert request shapes (add bodies, grab guid+indexerId, queue params) and the seeders-descending sort.
  2. 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.
  3. The Docker image is built, booted, and health-checked.

License

MIT — see LICENSE.