Radarr电影管理MCP服务

RadarrMCP 是一个将 Radarr 电影管理能力封装为 MCP 工具的服务,支持通过自然语言在 Claude 等 MCP 客户端中搜索、添加、监控和管理电影。

  • by cvalerio
  • C#
  • 本地部署
  • 更新于
在 GitHub 查看 cvalerio/radarr-mcp

使用说明

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__APIKEY maps to ApiKey, not RADARR__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 polls GET /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 or waitTimeoutSeconds runs out, and adds a completion object to the response; or
  • check the queuedCommands[].id later with radarr_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

  1. 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)" }, ...]

  2. 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": "…" }

  3. 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