Metadata-Version: 2.4
Name: arr-stack-mcp
Version: 0.1.2
Summary: MCP server giving any MCP-capable agent a clean, intelligent, unified interface to Sonarr, Radarr, Lidarr, Prowlarr, and Jellyfin.
Project-URL: Homepage, https://github.com/new-usemame/arr-stack-mcp
Project-URL: Repository, https://github.com/new-usemame/arr-stack-mcp
Project-URL: Issues, https://github.com/new-usemame/arr-stack-mcp/issues
Project-URL: Changelog, https://github.com/new-usemame/arr-stack-mcp/blob/main/CHANGELOG.md
Author-email: new-usemame <248195428+new-usemame@users.noreply.github.com>
License: MIT License
        
        Copyright (c) 2026 new-usemame
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: claude,jellyfin,lidarr,llm,mcp,prowlarr,radarr,sonarr
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia
Classifier: Topic :: System :: Networking
Requires-Python: >=3.12
Requires-Dist: anyio<5,>=4.6
Requires-Dist: httpx<1,>=0.28
Requires-Dist: mcp<2,>=1.27
Requires-Dist: pydantic-settings<3,>=2.6
Requires-Dist: pydantic<3,>=2.10
Requires-Dist: python-dateutil<3,>=2.9
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: rapidfuzz<4,>=3.10
Requires-Dist: structlog<26,>=25.5
Requires-Dist: typer<1,>=0.25
Description-Content-Type: text/markdown

# arr-stack-mcp

A Model Context Protocol server that exposes Sonarr, Radarr, Lidarr, and Jellyfin to any MCP client (Claude Desktop, Claude Code, n8n, ibis-bot, custom agents) through a hand-curated tool layer written for LLM consumption.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/release/python-3120/)

## What this gives an agent

28 curated tools split across four services:

| Service | Tools | Highlights |
|---|---|---|
| Sonarr | 8 | series search, TVDB lookup, queue, calendar, missing episodes, add/delete |
| Radarr | 8 | movie search, TMDB lookup, queue, calendar, missing, add/delete |
| Lidarr | 7 | artist + album search, MusicBrainz lookup, queue, add/delete |
| Jellyfin | 5 | library search, recent additions, now-playing sessions, library scan |

Every tool:

- Has a description written for an agent's decision-making, not for human docs.
- Pins its input and output with Pydantic v2, so the MCP catalogue carries strict JSON Schema.
- Returns a compact, self-describing envelope (`ok`, `query`, `count`, `total`, `items`).
- Translates upstream HTTP errors into structured `ToolError` envelopes with self-suggesting hints.
- Destructive operations require a two-call confirm-token flow.

## Quickstart

### uvx (one-shot)

```
uvx arr-stack-mcp serve --transport stdio
```

### Docker

```
docker run --rm -i \
  -e SONARR_URL=http://host.docker.internal:8989 \
  -e SONARR_API_KEY=$SONARR_API_KEY \
  -e RADARR_URL=http://host.docker.internal:7878 \
  -e RADARR_API_KEY=$RADARR_API_KEY \
  -e LIDARR_URL=http://host.docker.internal:8686 \
  -e LIDARR_API_KEY=$LIDARR_API_KEY \
  -e JELLYFIN_URL=http://host.docker.internal:8096 \
  -e JELLYFIN_API_KEY=$JELLYFIN_API_KEY \
  ghcr.io/new-usemame/arr-stack-mcp:0.1.0
```

### Local development (uv)

```
git clone https://github.com/new-usemame/arr-stack-mcp
cd arr-stack-mcp
uv sync
uv run arr-stack-mcp init           # writes a starter arr-stack-mcp.yaml
uv run arr-stack-mcp serve          # boots stdio MCP server
```

### Claude Desktop config

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, or `%APPDATA%\Claude\claude_desktop_config.json` on Windows:

```json
{
  "mcpServers": {
    "arr-stack": {
      "command": "uvx",
      "args": ["arr-stack-mcp", "serve"],
      "env": {
        "SONARR_URL": "http://localhost:8989",
        "SONARR_API_KEY": "...",
        "RADARR_URL": "http://localhost:7878",
        "RADARR_API_KEY": "...",
        "LIDARR_URL": "http://localhost:8686",
        "LIDARR_API_KEY": "...",
        "JELLYFIN_URL": "http://localhost:8096",
        "JELLYFIN_API_KEY": "..."
      }
    }
  }
}
```

## Config reference

`arr-stack-mcp init` writes a starter `arr-stack-mcp.yaml`. Every secret can also be supplied via env var. Inline expansion of `${VAR}` and `${VAR:-default}` is supported.

```yaml
log_level: info

transport:
  stdio: true
  http_enabled: false
  http_host: 127.0.0.1
  http_port: 8080

policy:
  read_only: false
  disable_destructive: false
  confirm_token_ttl_seconds: 300

services:
  sonarr:
    enabled: true
    url: http://localhost:8989
    api_key: ${SONARR_API_KEY}
    verify_tls: true
    timeout_seconds: 30
  radarr:
    enabled: true
    url: http://localhost:7878
    api_key: ${RADARR_API_KEY}
  lidarr:
    enabled: true
    url: http://localhost:8686
    api_key: ${LIDARR_API_KEY}
  jellyfin:
    enabled: true
    url: http://localhost:8096
    api_key: ${JELLYFIN_API_KEY}
    default_user_id: 4f8b6d2e-3a9c-4f1d-9c2a-1b3c5d7e9f01
```

### Runtime flags

| Flag | Env | Effect |
|---|---|---|
| `--read-only` | `ARRSTACK_READ_ONLY=true` | Skip every tool tagged `write` or `destructive` at registration. |
| `--disable-destructive` | `ARRSTACK_DISABLE_DESTRUCTIVE=true` | Skip only the `destructive` tools (deletes). |
| `--transport stdio` | — | Default. Use for Claude Desktop and Claude Code. |
| `--transport streamable-http` | — | HTTP transport for n8n / remote consumers. |
| `--config path/to/config.yaml` | — | Override config path. Defaults to `./arr-stack-mcp.yaml`, then XDG. |

## Capability matrix

Tools follow the `<service>.<verb_object>` convention. Tags drive flag-based gating.

### Sonarr

| Tool | Tag | Use |
|---|---|---|
| `sonarr.system_status` | read | Version + branch + uptime. First call when diagnosing. |
| `sonarr.series_search` | read | Search the existing library (already-added). |
| `sonarr.series_lookup` | read | Search TVDB to discover series to add. |
| `sonarr.queue` | read | Active downloads with progress. |
| `sonarr.calendar` | read | Upcoming + recently-aired episodes. |
| `sonarr.missing` | read | Monitored episodes not on disk. |
| `sonarr.series_add` | write | Add a series by TVDB id. Idempotent. |
| `sonarr.series_delete` | destructive | Two-call confirm-token flow. |

### Radarr

| Tool | Tag | Use |
|---|---|---|
| `radarr.system_status` | read | Mirrors Sonarr. |
| `radarr.movie_search` | read | Search the existing library. |
| `radarr.movie_lookup` | read | Search TMDB to discover movies to add. |
| `radarr.queue` | read | Active downloads. |
| `radarr.calendar` | read | Upcoming + recently-released movies. |
| `radarr.missing` | read | Monitored movies not on disk. |
| `radarr.movie_add` | write | Add a movie by TMDB id. Idempotent. |
| `radarr.movie_delete` | destructive | Two-call confirm-token flow. |

### Lidarr

| Tool | Tag | Use |
|---|---|---|
| `lidarr.system_status` | read | — |
| `lidarr.artist_search` | read | Search the existing library. |
| `lidarr.artist_lookup` | read | Search MusicBrainz to discover artists. |
| `lidarr.artist_albums` | read | List albums under one artist. |
| `lidarr.queue` | read | Active downloads. |
| `lidarr.artist_add` | write | Add an artist by MusicBrainz id. Idempotent. |
| `lidarr.artist_delete` | destructive | Two-call confirm-token flow. |

### Jellyfin

| Tool | Tag | Use |
|---|---|---|
| `jellyfin.system_info` | read | Version + server name. Public endpoint. |
| `jellyfin.library_search` | read | Search items by name across libraries. |
| `jellyfin.recent_additions` | read | Newest items by date_added. |
| `jellyfin.now_playing` | read | Currently-active sessions with progress. |
| `jellyfin.scan_library` | write | Trigger a library refresh. |

## Confirm-token flow

Destructive tools (`*_delete`) implement a two-call confirm:

```
# Call 1: returns plan + token, no side effect
> sonarr.series_delete(sonarr_id=42, delete_files=False)
{
  "ok": true,
  "needs_confirm": true,
  "confirm_token": "abc123",
  "summary": "remove 'Foo Bar' (sonarr_id=42) from Sonarr; keep files on disk",
  "expires_in_seconds": 300
}

# Call 2: executes
> sonarr.series_delete(sonarr_id=42, delete_files=False, confirm_token="abc123")
{
  "ok": true,
  "deleted_sonarr_id": 42,
  "title": "Foo Bar",
  "files_deleted": false,
  "msg": "deleted sonarr_id=42"
}
```

Tokens are single-use, time-limited, and bound to the request payload — a token from one tool will not confirm another.

## Architecture

Two layers:

```
┌────────────────────────────────────────────────────────────┐
│  Curated MCP tools (arr_stack_mcp.tools.<service>.tools)  │
│  Hand-written, LLM-friendly names, descriptions, schemas. │
├────────────────────────────────────────────────────────────┤
│  Generated thin clients (arr_stack_mcp.generated.<svc>)   │
│  openapi-python-client output. Do not hand-edit; regen    │
│  via scripts/regen-clients.sh on upstream version bumps.  │
├────────────────────────────────────────────────────────────┤
│  httpx + pydantic + structlog + mcp.server.fastmcp         │
└────────────────────────────────────────────────────────────┘
```

See `docs/ARCHITECTURE.md` for the longer walk-through.

## Development

```
uv sync                              # install runtime + dev deps
uv run pytest -q                     # fast unit tests
uv run ruff check src/ tests/        # lint
uv run mypy src/ tests/              # type check (strict)
uv run ruff format src/ tests/       # format
```

Integration tests require the docker test stack:

```
scripts/test-stack-up.sh             # boots Sonarr/Radarr/Lidarr/Prowlarr/Jellyfin
uv run pytest -m integration         # runs once stack is healthy
scripts/test-stack-down.sh --clean   # teardown
```

## Contributing

See `CONTRIBUTING.md` for the OpenAPI regeneration flow and the verification checklist that gates every release.

## License

MIT
