使用说明
RadarrMCP
A production-grade Model Context Protocol (MCP) server for Radarr v3, built with .NET 10.
Expose your Radarr instance as MCP tools so Claude (or any MCP client) can search, add, monitor, and manage movies via natural language.
Prerequisites
- .NET 10 SDK
- A running Radarr v3 instance
- A Radarr API key (Settings → General → Security)
Build
# Debug build
dotnet build
# Self-contained single binary for Linux x64
dotnet publish -c Release -r linux-x64 --self-contained
# Self-contained single binary for Windows x64
dotnet publish -c Release -r win-x64 --self-contained
# Self-contained single binary for macOS arm64 (Apple Silicon)
dotnet publish -c Release -r osx-arm64 --self-contained
Output lands in bin/Release/net10.0/<rid>/publish/.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
RADARR__URL |
yes | — | Base URL of your Radarr instance, e.g. http://radarr:7878 |
RADARR__APIKEY |
yes | — | API key from Radarr → Settings → General → Security |
RADARR__TIMEOUTMS |
no | 15000 |
Per-request timeout in milliseconds |
RADARR__MOVETIMEOUTMS |
no | 120000 |
Timeout in milliseconds for the radarr_move_movies request (no retries) |
Note:
__(double underscore) is the .NET hierarchy separator. Property names are matched case-insensitively with no underscore substitution —RADARR__APIKEYmaps toApiKey, notRADARR__API_KEY.
Claude Desktop Configuration
Add to claude_desktop_config.json:
{
"mcpServers": {
"radarr": {
"command": "/path/to/RadarrMcp",
"env": {
"RADARR__URL": "http://radarr:7878",
"RADARR__APIKEY": "your-api-key-here"
}
}
}
}
On Windows use the .exe binary and forward slashes or escaped backslashes.
Claude Code Configuration
claude mcp add radarr /path/to/RadarrMcp \
-e RADARR__URL=http://radarr:7878 \
-e RADARR__APIKEY=your-api-key-here
Docker Compose
Deploy alongside an existing Radarr stack:
services:
radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: radarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
volumes:
- /config/radarr:/config
- /data/movies:/movies
- /data/downloads:/downloads
ports:
- 7878:7878
restart: unless-stopped
radarr-mcp:
image: mcr.microsoft.com/dotnet/runtime-deps:10.0
container_name: radarr-mcp
command: /app/RadarrMcp
volumes:
- ./publish/linux-x64:/app:ro
environment:
- RADARR__URL=http://radarr:7878
- RADARR__APIKEY=your-api-key-here
- RADARR__TIMEOUTMS=15000
depends_on:
- radarr
stdin_open: true
restart: unless-stopped
Build the publish artifact first:
dotnet publish -c Release -r linux-x64 --self-contained -o publish/linux-x64
Available Tools
radarr_search_movie
Search for movies by title via Radarr (which queries TMDB). Returns both library movies and new candidates.
Example: "Search for Interstellar"
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | required | Movie title to search |
limit |
int | 5 |
Max results (1–20) |
radarr_multi_search_movie
Run several title searches in parallel in one call. Use it instead of calling radarr_search_movie repeatedly. Takes at most 50 searches; any beyond that are ignored. Each search returns its own results and error.
Example: "Search for Heat, Ronin and Collateral"
[{ "query": "Heat", "limit": 3 }, { "query": "Ronin" }, { "query": "Collateral" }]
| Parameter | Type | Description |
|---|---|---|
searchesJson |
string (JSON array) | Array of { query, limit? } (limit defaults to 5, clamped to 1–20) |
radarr_get_library
List movies in the Radarr library with optional status filtering and title search.
Example: "Show me all unmonitored movies", "Find movies with 'dark' in the title"
| Parameter | Type | Default | Description |
|---|---|---|---|
filter |
string | all |
all | missing | downloaded | monitored | unmonitored |
search |
string | null |
Case-insensitive title substring filter |
radarr_add_movie
Add one or more movies to Radarr by TMDB ID. Fetches full movie metadata automatically and uses sensible defaults for quality profile and root folder.
Example: "Add Dune Part Two and Oppenheimer to Radarr"
| Parameter | Type | Default | Description |
|---|---|---|---|
movies |
array | required | Array of { tmdbId, qualityProfileId?, rootFolderPath?, monitored?, searchForMovie? } |
radarr_get_movie_details
Get full details of a specific movie by its Radarr ID.
Example: "Show full details for movie ID 42"
| Parameter | Type | Description |
|---|---|---|
radarrId |
int | Radarr-assigned movie ID |
radarr_multi_get_details
Get full details of several movies in parallel. Use it instead of calling radarr_get_movie_details repeatedly. Each ID returns either its result or an error.
Example: "Show details for movies 407, 1513 and 464"
| Parameter | Type | Description |
|---|---|---|
radarrIdsJson |
string (JSON array) | Radarr movie IDs, e.g. [407, 1513, 464] (max 100) |
radarr_delete_movie
Remove a movie from the library. Optionally delete files from disk.
Example: "Delete movie 42 and remove its files"
| Parameter | Type | Default | Description |
|---|---|---|---|
radarrId |
int | required | Radarr movie ID |
deleteFiles |
bool | false |
Also delete files from disk |
addImportExclusion |
bool | false |
Prevent future re-import |
radarr_update_movie
Update monitored status or quality profile of a library movie.
Example: "Unmonitor movie 42", "Change quality profile of movie 7 to profile ID 3"
| Parameter | Type | Default | Description |
|---|---|---|---|
radarrId |
int | required | Radarr movie ID |
monitored |
bool? | null |
Set monitored state |
qualityProfileId |
int? | null |
Assign a different quality profile |
Does not change the root folder or move files — use radarr_move_movies.
radarr_update_movies
Update monitored status or quality profile for several movies in parallel. Takes at most 50 entries; any beyond that are ignored. Each entry returns success and either the updated movie or an error. Does not change the root folder or move files — use radarr_move_movies.
Example: "Unmonitor movies 12 and 15, and set movie 40 to quality profile 4"
[{ "radarrId": 12, "monitored": false }, { "radarrId": 15, "monitored": false }, { "radarrId": 40, "qualityProfileId": 4 }]
| Parameter | Type | Description |
|---|---|---|
updatesJson |
string (JSON array) | Array of { radarrId, monitored?, qualityProfileId? }; each entry needs at least one of the two optional fields |
radarr_move_movies
Move one or more movies to a different root folder via Radarr's bulk movie editor (PUT /api/v3/movie/editor). With moveFiles: true Radarr physically moves the folders on disk in a background BulkMoveMovie job and may rename the movie folder according to its folder naming format. Destructive — run with dryRun: true first.
Before sending anything, the tool checks that the root folder exists (trailing slash ignored) and that every ID is in the library; if either check fails, nothing is changed. Movies already in the target root folder are reported as skipped. The response lists moved (or planned in dry-run mode) with id, title, oldPath, newRootFolder, newPath, plus skipped and the queuedCommands Radarr started for the move.
Radarr updates the paths straight away, but the file transfer runs in the background. When the source and destination root folders are on different physical volumes, the transfer is a byte-by-byte copy followed by a delete and can take minutes per movie. To find out when it has finished:
- pass
waitForCompletion: true: the call pollsGET /api/v3/command/{id}(every 2 s for the first 30 s, then every 5 s up to 2 min, then every 10 s) until the move command ends orwaitTimeoutSecondsruns out, and adds acompletionobject to the response; or - check the
queuedCommands[].idlater withradarr_get_command.
completion.status is one of:
| Status | Meaning |
|---|---|
completed |
Transfer finished and every movie is in the target root folder (durationSeconds) |
completedWithErrors |
Radarr finished the command but could not transfer some movies: it reverted their paths to the old location (reverted[] with id, title, currentPath). See Radarr's logs for the reason |
failed / aborted |
The command failed: message, errorMessage (first line of Radarr's exception) |
timeout |
Still running when waitTimeoutSeconds ran out: commandId, lastStatus, lastMessage. Not an error: the paths are already updated and the transfer continues in the background. Check again with radarr_get_command |
unknown |
No move command was found, or Radarr no longer knows it. Check the movie paths |
Keep waitForCompletion off for large batches: the MCP call stays blocked for the whole wait. While it waits, the server sends MCP progress notifications with Radarr's progress message, if the client asked for them.
Example: "Move movies 12, 15 and 40 to /movies/T — show me the plan first"
{ "movieIds": [12, 15, 40], "rootFolderPath": "/movies/T", "moveFiles": true, "dryRun": true }
| Parameter | Type | Default | Description |
|---|---|---|---|
movieIds |
int[] | required | Radarr movie IDs (1–100), from radarr_get_library |
rootFolderPath |
string | required | Destination root folder, as listed by radarr_get_root_folders |
moveFiles |
bool | true |
false = only update the path in Radarr's DB, leave files in place |
dryRun |
bool | false |
Return the plan without changing anything |
waitForCompletion |
bool | false |
Wait for the file transfer to finish and report it in completion. Ignored with dryRun: true or moveFiles: false |
waitTimeoutSeconds |
int | 300 |
Maximum wait, 1–1800 seconds |
End-to-end example: moving to another disk
-
Plan:
radarr_move_movies{ "movieIds": [12, 15], "rootFolderPath": "/mnt/disk2/movies", "dryRun": true }→
planned: [{ "id": 12, "oldPath": "/mnt/disk1/movies/Heat (1995)", "newPath": "/mnt/disk2/movies/Heat (1995)" }, ...] -
Move and wait:
radarr_move_movies{ "movieIds": [12, 15], "rootFolderPath": "/mnt/disk2/movies", "waitForCompletion": true, "waitTimeoutSeconds": 600 }→
queuedCommands: [{ "id": 4711, "name": "BulkMoveMovie" }],completion: { "status": "timeout", "commandId": 4711, "lastStatus": "started", "lastMessage": "Moving Heat (1995) from '…' to '…' (1/2)", "note": "…" } -
Check again later:
radarr_get_command{ "commandId": 4711 }→
{ "id": 4711, "name": "BulkMoveMovie", "status": "completed", "durationSeconds": 842.3, ... }
radarr_get_command
Check the status of a Radarr command started earlier (GET /api/v3/command/{id}), e.g. the BulkMoveMovie in queuedCommands from radarr_move_movies or the id returned by radarr_command. Use it to confirm that a move between different volumes has really finished.
It returns a compact object: id, name, status (queued / started / completed / failed / aborted), result, queued, started, ended, duration / durationSeconds, message (Radarr's progress text, e.g. Moving X from '…' to '…' (2/2)) and, on failure, errorMessage (first line of the exception) and exception (capped at 2000 characters). Radarr keeps commands only for a limited time: an unknown or purged ID returns a clear error.
Radarr's move job reports completed even when it could not transfer a single movie; in that case it reverts that movie's path. radarr_move_movies with waitForCompletion: true checks for this. With radarr_get_command alone, confirm the paths with radarr_get_movie_details.
Example: "Has the move I started earlier finished?"
| Parameter | Type | Description |
|---|---|---|
commandId |
int | Command ID, from queuedCommands[].id (radarr_move_movies) or id (radarr_command) |
radarr_get_queue
Get the current download queue including active and pending items.
Example: "What's currently downloading in Radarr?"
| Parameter | Type | Default | Description |
|---|---|---|---|
includeMovie |
bool | true |
Include movie metadata in each queue record |
radarr_get_cutoff_unmet
List monitored movies that have a file but haven't reached their quality profile's cutoff yet (upgrade candidates). Returns radarrId, title, year, qualityProfileId, current quality and resolution.
Example: "Which movies could still be upgraded?"
No parameters.
radarr_get_root_folders
List the configured root folders with id, path, freeSpace, accessible and unmappedFolderCount.
Example: "Which root folders do I have and how much space is left?"
No parameters.
radarr_get_unmapped_folders
Scan a root folder and list the subfolders on disk that aren't linked to any movie in Radarr. This is the first step of a library import. On large folders the scan can take a few minutes (5-minute timeout).
Example: "Which folders in root folder 2 aren't in Radarr yet?"
| Parameter | Type | Description |
|---|---|---|
rootFolderId |
int | Root folder ID, from radarr_get_root_folders |
radarr_import_movies
Import unmapped folders into the library. For each folder, the tool looks up the best TMDB match for folderName and imports it with path set to folderPath, without triggering a search. If the match is already in Radarr, that entry fails with conflict: true and the existing movie's ID/title: delete the existing movie first, then retry (same as the Radarr UI).
Example: "Import the unmapped folder 'Heat (1995)' from /movies/H"
[{ "folderPath": "/movies/H/Heat (1995)", "folderName": "Heat (1995)", "qualityProfileId": 4, "monitored": true }]
| Parameter | Type | Description |
|---|---|---|
imports |
string (JSON array) | Array of { folderPath, folderName, qualityProfileId?, monitored? } (monitored defaults to true) |
radarr_command
Send a command to Radarr (POST /api/v3/command) to trigger an action right away, e.g. MoviesSearch, RefreshMovie, RescanMovie, RssSync. Returns the queued command's id, status and timestamps without waiting for it to finish. Use radarr_get_command to follow it.
Example: "Search for new releases of movies 123 and 456"
| Parameter | Type | Default | Description |
|---|---|---|---|
commandName |
string | required | Radarr command name, e.g. MoviesSearch |
commandArgs |
string (JSON object) | null |
Extra fields merged into the command body, e.g. {"movieIds": [123, 456]} |
radarr_get_system_status
Get Radarr version, system info, and any active health check warnings.
Example: "Is Radarr healthy?"
No parameters.
Architecture Notes
- Transport: stdio — compatible with Claude Desktop, Claude Code, and any MCP-capable client
- Resilience: Polly standard pipeline — 2 retries with exponential backoff, per-attempt timeout
- Logging: all logs go to stderr (stdout is reserved for the MCP protocol)
- Startup check: on start, the server calls Radarr's
/api/v3/system/status; failure is logged as a warning but does not abort startup