radarr-mcp-server
Provides tools for managing and monitoring a Radarr instance: listing movies, retrieving movie details, finding missing movies, searching for new movies, triggering movie searches, and checking system status.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@radarr-mcp-serverWhich movies are missing from my library?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
radarr-mcp-server
A minimal Model Context Protocol server that connects to Radarr, packaged for Docker.
It runs as a standing network service (streamable-http transport, not stdio),
so any MCP client on your internal network can connect to
http://<host>:<port>/mcp — the container isn't spawned per-client, and
container lifecycle/updates can be handed off to a tool like
Dockhand.
Tools
Tool | Description |
| List movies in the library, optionally filtered by title |
| Full details for one movie by ID |
| Monitored movies that don't have a file yet |
| Search for new movies by title (does not add them) |
| Trigger a search for a movie already in the library |
| Radarr system status, disk space, and health checks |
search_movie is the only tool that changes state in Radarr (it kicks off a
real search/download). Everything else is read-only.
Related MCP server: media-stack-mcp
Health endpoints
Two plain HTTP endpoints, reachable without MCP_AUTH_TOKEN (so Docker's
HEALTHCHECK, Dockhand, or any other monitor can poll them without the
secret):
Endpoint | Checks | Healthy | Unhealthy |
| The process is up and serving HTTP. Does not call Radarr. |
| (doesn't respond) |
|
|
|
|
They're split deliberately: /health is what the container's own
HEALTHCHECK uses (so a transient Radarr outage doesn't get the container
itself restarted in a loop), while /ready is for verifying config — after
changing RADARR_URL/RADARR_API_KEY, curl http://<host>:8932/ready tells
you plainly whether the host is reachable, the key is valid, or both.
Authentication
Set MCP_AUTH_TOKEN (a random shared secret — openssl rand -hex 32) and
every request must carry Authorization: Bearer <token> or the server
returns 401. This is checked by a small Starlette middleware in front of
the MCP app, not the mcp SDK's built-in OAuth support
(mcp.server.auth) — that machinery expects a full OAuth authorization
server (issuer/resource metadata, RFC 8414/8707/9068 discovery), which is
unnecessary complexity for one secret shared by trusted LAN clients.
Leave MCP_AUTH_TOKEN unset and the server runs with no auth — anything
that can reach http://<host>:<port>/mcp can call every tool, including
search_movie. The server logs a warning on startup when it's running this
way. Either way, the trust boundary is still the network:
Do not publish this port through any reverse proxy, port-forward, or anything else reachable from outside your LAN/VLAN — the bearer token protects against anyone on the network, not against the open internet.
Bind the compose
ports:mapping to a specific internal interface (e.g.192.168.1.50:8932:8932) rather than all interfaces, if you want to be stricter about which hosts on your network can reach it at all.
Configuration
Environment variables (see .env.example):
Variable | Required | Default | Description |
| yes | — | e.g. |
| yes | — | Radarr > Settings > General > API Key |
| no |
| Radarr REST API version to call ( |
| no |
| Interface the server binds to inside the container |
| no |
| Port the server listens on |
| no | — | Shared secret required as |
API version checking
Radarr shares its underlying HTTP framework with Sonarr (both are Servarr
apps built on the same *arr common codebase), so it exposes the same
unauthenticated, unversioned GET /api endpoint reporting which API version
is current and which are deprecated (e.g. {"current": "v3", "deprecated": []}). GET /ready calls it and compares it against RADARR_API_VERSION:
version matches
current, or is listed underdeprecated(still served, just on notice) → healthy, reported under the response'sapiVersionkey.version isn't offered at all any more →
503, since every tool call would otherwise start failing with 404s. BumpRADARR_API_VERSIONto match what Radarr now reports.Radarr doesn't have this endpoint (very old versions) or it's unreachable → non-fatal,
apiVersion: {"checked": false}.
This turns a silent break on a Radarr upgrade into a readiness-probe failure instead.
Image
Built and pushed to ghcr.io/barrow1990/radarr-mcp-server by
.github/workflows/ci.yml on every push to
main that passes tests, tagged :latest, :<commit-sha>, and
:radarr-<api-version> (e.g. :radarr-v3 — the Radarr API version this
build targets, read out of server.py's RADARR_API_VERSION default so it
can't drift from what the code actually calls). docker-compose.yml pulls
:latest by default; swap in build: . there instead if you'd rather build
locally from the Dockerfile.
The image is a three-stage build: builder compiles dependencies into
--target=/deps (all of them, including cryptography's compiled cffi
extension, ship musllinux wheels, so this needs no compiler even on alpine);
prep starts fresh from python:3.12-alpine, drops pip/setuptools/wheel,
strips stdlib pieces this headless server never touches (tkinter,
idlelib, lib2to3, ensurepip, ...), adds the non-root app user, and
copies in /deps and server.py; runtime then does a single
COPY --from=prep / / onto a scratch base. That last step matters more
than it looks — a plain RUN rm -rf only hides files still physically
present in the base image's own layers underneath, so it doesn't shrink a
normal layered image at all; copying the already-trimmed filesystem onto
scratch is what actually drops those bytes from what gets pushed.
That takes the published image to roughly ~98MB. The floor from here is
mcp's own dependency graph: mcp.server.request_state unconditionally
imports cryptography's AES-GCM/HKDF (spec-mandated integrity protection for
MCP's requestState, not something gated behind JWT/OAuth use), so its
~15MB native extension ships regardless. Dependencies in requirements.txt
are pinned to exact versions rather than >= ranges, so a routine
docker build can't silently pull in a heavier resolution than
the one that was actually tested.
Running with Docker Compose
cp .env.example .env # fill in RADARR_URL / RADARR_API_KEY
docker compose up -d --pull alwaysThe server is then reachable at http://<docker-host>:8932/mcp from anything
on your internal network.
Managing with Dockhand
Point Dockhand at ghcr.io/barrow1990/radarr-mcp-server and let it track new
tags — this is the registry-pull model Dockhand's image-update tracking
(Grype/Trivy scans, tag tracking, scheduled updates) is actually built around.
The alternative, pointing Dockhand at this repo as a Git-deployed Compose
stack with build: ., works too, but syncing new Git commits does not
imply rebuilding the image — those are two separate steps for a build-from-
source stack.
Make the GHCR package public, or every pull will need docker login ghcr.io with a PAT on each deploy host — a private package by default
requires auth even to docker pull, which most homelab boxes won't have
configured.
Set a restart policy of unless-stopped (already in docker-compose.yml) so
Dockhand-driven restarts and host reboots bring it back up without manual
intervention. The HEALTHCHECK in the Dockerfile (GET /health) drives
Docker's/Dockhand's container health status; use GET /ready (see above)
separately if you want to alert on Radarr connectivity specifically rather
than container liveness.
Environment variables in Dockhand: docker-compose.yml loads
RADARR_URL/RADARR_API_KEY/MCP_AUTH_TOKEN via env_file: [.env, .env.dockhand]
(both optional; .env.dockhand loads second, so it wins for any key it also
sets). This is deliberate — a Git-deployed stack's .env is whatever's
checked out from the repo (i.e. .env.example's placeholders, since real
.env is gitignored and not committed), while Dockhand writes the values you
configure in its UI to .env.dockhand instead. If you set RADARR_URL in
Dockhand's UI and the container is still using a placeholder, check that
Dockhand is actually writing to .env.dockhand in the stack directory (not
some other file) and that a rebuild has run since — a synced Git file change
alone doesn't rebuild the image; see GET /ready to confirm what's live.
Connecting a client
Claude Code
claude mcp add radarr -s user --transport http http://<docker-host>:8932/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"(Drop the --header flag if you're running with MCP_AUTH_TOKEN unset.)
Claude Desktop
Claude Desktop's built-in config expects a locally-spawned command, so for
a network server like this you'll need an HTTP-to-stdio bridge such as
mcp-remote:
{
"mcpServers": {
"radarr": {
"command": "npx",
"args": [
"-y", "mcp-remote", "http://<docker-host>:8932/mcp",
"--header", "Authorization: Bearer <MCP_AUTH_TOKEN>"
]
}
}
}Running without Docker
pip install -r requirements.txt
RADARR_URL=http://192.168.1.50:7878 RADARR_API_KEY=your-api-key \
MCP_AUTH_TOKEN=your-shared-secret python server.pyTesting
pip install -r requirements-dev.txt
python -m pytest tests/ -vtests/test_tools.py— each tool's logic against a mocked Radarr (httpx.MockTransport, no extra mocking library needed).tests/test_http.py—/health,/ready, and the bearer-auth middleware, viaserver.build_app()(the exact app__main__runs) through Starlette'sTestClient.tests/test_live_radarr.py— opt-in contract tests against a real Radarr instance, to catch drift if a Radarr upgrade renames/removes a field these tools depend on (id,title,hasFile,tmdbId,version, ...). Skipped by default (no Radarr in CI); run with:RUN_LIVE_RADARR_TESTS=1 RADARR_URL=https://radarr.example.com \ RADARR_API_KEY=<real key> python -m pytest tests/test_live_radarr.py -v
CI (.github/workflows/ci.yml) runs the mocked suite on every push/PR; the
GHCR build only runs after it passes.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
OMDb MCP — IMDB-derived movie / TV / episode data (BYO key)
- mcpOAuthcom.zomato
An MCP server that exposes functionalities to use Zomato's services.
Star Wars API (SWAPI) as a remote MCP server: films, people, planets, species, starships, vehicles.
Hosted MCP server for Xweather weather data: conditions, forecasts, alerts, and more.
Related MCP Servers
- FlicenseAqualityBmaintenanceMCP server to manage your *ARR media stack (Radarr, Sonarr, Lidarr, Readarr, Prowlarr) for searching, adding, and managing movies, TV shows, music, books, and indexers.1-
- AlicenseBqualityBmaintenanceEnables control and management of a self-hosted media stack (Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent) through natural language via MCP.15GPL 3.0
- AlicenseCqualityAmaintenanceMCP server exposing Radarr's v3 REST API as tools, enabling LLMs to read and manage movies, downloads, history, indexers, and more.100MIT
- FlicenseNot gradedqualityBmaintenanceEnables MCP clients to interact with the arr media stack (Sonarr, Radarr, Prowlarr, Jellyseerr, Bazarr) via clean, typed tools over each app's REST API.1-