Skip to main content
Glama
LtMarx
by LtMarx
README.md
# Arr-MCP

An MCP server for your \*ARR stack — Radarr, Sonarr, Lidarr, Readarr, and Prowlarr.

> **Readarr is retired by upstream** (since May 2024) — its tools still work but metadata may be unreliable. See [Readarr status](https://wiki.servarr.com/readarr/status).

Supports **stdio** (local, Claude Desktop) and **HTTP/Streamable HTTP** (remote, ChatGPT, any MCP-compatible client).

## Tools

### Cross-service
| Tool | Description |
|------|-------------|
| `arr_get_services` | Show which services are configured |
| `arr_search_all` | Search all configured services at once |

### Radarr (Movies)
| Tool | Description |
|------|-------------|
| `radarr_get_movies` | List movies (paginated) |
| `radarr_search_movies` | Search by title |
| `radarr_add_movie` | Add by TMDB ID |
| `radarr_update_movie` | Update monitored/quality profile |
| `radarr_delete_movie` | Delete (optionally with files) |
| `radarr_search_movie` | Trigger download search for a movie |
| `radarr_refresh_movie` | Refresh metadata |
| `radarr_get_queue` | Download queue (paginated) |
| `radarr_delete_queue_item` | Remove item from queue |
| `radarr_get_calendar` | Upcoming releases |
| `radarr_get_health` | Health warnings |
| `radarr_get_quality_profiles` | Quality profiles |
| `radarr_get_root_folders` | Root folders |
| `radarr_get_download_clients` | Download clients |
| `radarr_get_tags` | Tags |
| `radarr_get_naming` | File naming config |
| `radarr_get_status` | System status |

### Sonarr (TV Shows)
| Tool | Description |
|------|-------------|
| `sonarr_get_series` | List series (paginated) |
| `sonarr_search_series` | Search by title |
| `sonarr_add_series` | Add by TVDB ID |
| `sonarr_delete_series` | Delete (optionally with files) |
| `sonarr_refresh_series` | Refresh metadata |
| `sonarr_get_episodes` | List episodes (optionally by season) |
| `sonarr_search_missing` | Trigger search for missing episodes |
| `sonarr_search_episode` | Trigger search for a specific episode |
| `sonarr_get_queue` | Download queue (paginated) |
| `sonarr_delete_queue_item` | Remove item from queue |
| `sonarr_get_calendar` | Upcoming air dates |
| `sonarr_get_health` | Health warnings |
| `sonarr_get_quality_profiles` | Quality profiles |
| `sonarr_get_root_folders` | Root folders |
| `sonarr_get_download_clients` | Download clients |
| `sonarr_get_tags` | Tags |
| `sonarr_get_naming` | File naming config |
| `sonarr_get_status` | System status |

### Lidarr (Music)
| Tool | Description |
|------|-------------|
| `lidarr_get_artists` | List artists (paginated) |
| `lidarr_search_artists` | Search by name |
| `lidarr_add_artist` | Add by MusicBrainz ID |
| `lidarr_get_albums` | List albums (optionally by artist, paginated) |
| `lidarr_search_missing` | Trigger search for missing albums |
| `lidarr_get_releases` | Get available releases for an album |
| `lidarr_grab_release` | Grab a specific release by GUID |
| `lidarr_get_history` | Download history (grabs, imports, failures) |
| `lidarr_get_blocklist` | Blocklist |
| `lidarr_delete_blocklist_item` | Remove item from blocklist |
| `lidarr_get_wanted_missing` | Monitored albums with no file |
| `lidarr_get_queue` | Download queue (paginated) |
| `lidarr_delete_queue_item` | Remove item from queue |
| `lidarr_get_diskspace` | Disk space per root folder |
| `lidarr_get_command_status` | Check status of a triggered command |
| `lidarr_get_calendar` | Upcoming album releases |
| `lidarr_get_health` | Health warnings |
| `lidarr_get_quality_profiles` | Quality profiles |
| `lidarr_get_metadata_profiles` | Metadata profiles |
| `lidarr_get_root_folders` | Root folders |
| `lidarr_get_download_clients` | Download clients |
| `lidarr_get_tags` | Tags |
| `lidarr_get_status` | System status |

### Readarr (Books) — ⚠️ retired upstream
| Tool | Description |
|------|-------------|
| `readarr_get_authors` | List authors (paginated) |
| `readarr_search_authors` | Search by name |
| `readarr_add_author` | Add by Goodreads ID |
| `readarr_get_books` | List books (optionally by author, paginated) |
| `readarr_search_books` | Search by title or ISBN |
| `readarr_get_health` | Health warnings |
| `readarr_get_quality_profiles` | Quality profiles |
| `readarr_get_root_folders` | Root folders |
| `readarr_get_status` | System status |

### Prowlarr (Indexers)
| Tool | Description |
|------|-------------|
| `prowlarr_get_indexers` | List indexers |
| `prowlarr_test_indexer` | Test a specific indexer |
| `prowlarr_test_all_indexers` | Test all indexers |
| `prowlarr_search` | Search across indexers |
| `prowlarr_grab` | Grab a release found via search |
| `prowlarr_get_indexer_stats` | Indexer statistics |
| `prowlarr_get_health` | Health warnings |
| `prowlarr_get_status` | System status |

## Setup

### 1. Install & Build

```bash
npm install
npm run build
```

### 2. Configure

Copy `.env.example` to `.env` and fill in your service URLs and API keys. Only configure the services you use — unconfigured services are skipped at startup and their tools don't appear.

API keys: **Settings → General → Security → API Key** in each app.

---

## Transport modes

### stdio — local (Claude Desktop)

Default mode. The MCP client launches the process directly.

```json
{
  "mcpServers": {
    "arr": {
      "command": "node",
      "args": ["/path/to/arr-mcp/dist/index.js"],
      "env": {
        "RADARR_URL": "http://localhost:7878",
        "RADARR_API_KEY": "...",
        "SONARR_URL": "http://localhost:8989",
        "SONARR_API_KEY": "..."
      }
    }
  }
}
```

### HTTP — remote (ChatGPT, hosted clients)

Set `MCP_TRANSPORT=http`. The server listens on `http://HOST:PORT/mcp` and accepts POST requests per the MCP Streamable HTTP spec.

```bash
MCP_TRANSPORT=http MCP_PORT=3000 \
  RADARR_URL=http://... RADARR_API_KEY=... \
  node dist/index.js
```

### Health endpoint

In http mode zijn er twee niveaus:

| Request | Betekenis | Status |
|---|---|---|
| `GET /health` | **Liveness** — proces draait en serveert | altijd `200` zolang de server leeft |
| `GET /health?deep=1` | **Readiness** — pingt bovendien elke geconfigureerde \*ARR service | `200` als alles antwoordt, `503` bij één of meer fouten |

```bash
curl http://localhost:3000/health
# → {"status":"ok","transport":"http","version":"1.5.0","uptimeSeconds":42}

curl "http://localhost:3000/health?deep=1"
# → {"status":"degraded", ..., "services":[
#      {"name":"Radarr","ok":true,"ms":12,"version":"5.14.0"},
#      {"name":"Sonarr","ok":false,"ms":5000,"error":"timeout after 5000ms"}]}
```

De plain `/health` is bewust onafhankelijk van je \*ARR services: een kort onbereikbare Radarr maakt de MCP-server zelf niet stuk.

---

## Docker

Het gepubliceerde image staat op GitHub Container Registry:

```
ghcr.io/ltmarx/arr-mcp:latest
```

De `docker-compose.yml` heeft twee profielen — `stdio` en `http` — die nooit tegelijk actief zijn.

### HTTP mode — remote/persistent server

```bash
cp .env.example .env   # vul je keys in
docker compose --profile http up
```

De server luistert op `http://<host>:3000/mcp`. Poort aanpassen: `MCP_PORT=8080 docker compose --profile http up`.

Controleer of de server draait:

```bash
curl http://localhost:3000/health
# → {"status":"ok","transport":"http","version":"1.5.0","uptimeSeconds":42}
```

### Container healthcheck

Het image (v1.5.0 en hoger) heeft een ingebouwde `HEALTHCHECK`. Die gebruikt **node**,
niet `curl` of `wget`. `curl` zit niet in `node:22-alpine`, dus een eigen check met
`curl -f ...` faalt altijd met `curl: not found`. Busybox-`wget` is er wél, maar is
om twee andere redenen onbetrouwbaar als probe — zie de troubleshooting hieronder.

Gebruik je een eigen compose-bestand, neem dan deze probe over. Hij is bewust
*inline* en werkt daardoor op elke image-versie:

```yaml
healthcheck:
  test:
    - CMD
    - node
    - -e
    - "require('http').get({host:'127.0.0.1',port:process.env.MCP_PORT||3000,path:'/health',timeout:4000},r=>{r.resume();process.exit(r.statusCode===200?0:1)}).on('error',()=>process.exit(1))"
  interval: 30s
  timeout: 5s
  start_period: 10s
  retries: 3
```

Status bekijken:

```bash
docker compose --profile http ps          # STATUS toont (healthy)
docker inspect --format '{{json .State.Health}}' <container> | jq
```

#### Container blijft `unhealthy`

Loopt de server prima (`http server started` in de logs) maar staat de container
toch op `unhealthy`, loop dan deze drie langs:

**1. Draai je wel een recent image?**

```bash
curl -s http://127.0.0.1:3000/health
```

Zit er geen `version`-veld in de response, dan draai je een image ouder dan v1.5.0.
`docker compose up -d` haalt **geen** nieuwe `:latest` op als er al een lokaal image
ligt — je moet expliciet pullen:

```bash
docker compose pull && docker compose up -d
```

**2. Gebruik je `wget` met `localhost`?**

```yaml
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]   # onbetrouwbaar
```

Twee valkuilen tegelijk:

- De server bindt op `0.0.0.0`, en dat is **IPv4-only**. Resolvet `localhost` in de
  container naar `::1`, dan wordt de verbinding geweigerd. Gebruik `127.0.0.1`.
- Busybox-`wget` volgt `http_proxy`/`HTTP_PROXY`. Staat die in je `env_file`, dan
  gaat de probe naar de proxy in plaats van naar de container zelf.

Vergelijk beide om dit te bevestigen:

```bash
docker exec arr-mcp wget -qO- http://localhost:3000/health;  echo "localhost  -> $?"
docker exec arr-mcp wget -qO- http://127.0.0.1:3000/health;  echo "127.0.0.1  -> $?"
docker exec arr-mcp env | grep -i proxy
```

De node-probe hierboven heeft geen van beide problemen: die adresseert expliciet
IPv4 en negeert proxy-variabelen.

**3. Verwijst je healthcheck naar `docker-healthcheck.js`?**

Dat bestand bestaat pas vanaf v1.5.0. Op een ouder image geeft
`node docker-healthcheck.js` een `Cannot find module`-fout → exit 1 → `unhealthy`.
Gebruik de inline probe hierboven, die heeft die afhankelijkheid niet.

**4. Wat zegt de check zelf?**

```bash
docker inspect --format '{{json .State.Health}}' arr-mcp | jq '.Log[-1]'
```

De `Output` bevat de concrete foutmelding (`curl: not found`,
`Cannot find module`, connection refused, enz.).

In **stdio mode draait er geen HTTP-server**, dus daar is niets te proben: de check
stopt direct met exit 0 en in compose staat hij expliciet uit (`healthcheck: disable: true`).

Wil je dat de container ook ongezond wordt als een \*ARR service onbereikbaar is,
zet dan `HEALTHCHECK_DEEP=1`. Standaard staat dat uit, zodat een herstartende
Radarr niet je hele MCP-container omlaag trekt.

### Stdio mode — lokaal (Claude Desktop)

```bash
docker compose --profile stdio up
```

Of direct via `docker run` (wordt gestart door de MCP client):

```bash
docker run --rm -i \
  -e RADARR_URL=http://192.168.1.x:7878 \
  -e RADARR_API_KEY=abc123 \
  ghcr.io/ltmarx/arr-mcp:latest
```

MCP client config voor Claude Desktop:

```json
{
  "mcpServers": {
    "arr": {
      "command": "docker",
      "args": ["run", "--rm", "-i",
        "-e", "RADARR_URL", "-e", "RADARR_API_KEY",
        "-e", "SONARR_URL", "-e", "SONARR_API_KEY",
        "ghcr.io/ltmarx/arr-mcp:latest"
      ],
      "env": {
        "RADARR_URL": "http://localhost:7878",
        "RADARR_API_KEY": "...",
        "SONARR_URL": "http://localhost:8989",
        "SONARR_API_KEY": "..."
      }
    }
  }
}
```

> **Tip:** Als je \*ARR draait op de host machine gebruik dan `host.docker.internal` (Mac/Windows) of het host IP-adres in plaats van `localhost`.

## Releases

Releases worden automatisch gepubliceerd via GitHub Actions. Maak een tag aan op GitHub (`v1.0.0`) en publiceer een Release — het multi-arch image (amd64 + arm64) verschijnt vanzelf op GHCR.

## Development

```bash
npm run dev   # run met tsx (geen build stap)
npm run build # compileer naar dist/
npm start     # run gecompileerde output
```

TDQS

A4/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of confusion between tools.

Naming Consistency5/5

The single tool name follows a clear snake_case verb_noun pattern, which is consistent.

Tool Count2/5

One tool is too few for a server that presumably manages *ARR services, which typically require multiple operations.

Completeness2/5

The tool only lists services, missing essential actions like adding, updating, or deleting services, leaving significant gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues