Skip to main content

Oraclarr

Ask your media stack what's going on, in plain language.

A locally-run, read-only MCP server that lets an LLM client (Claude Code, Claude Desktop) diagnose a self-hosted *arr media stack.

License Python MCP Mode Status


What it does

Oraclarr gives a language model one place to answer the operational "why" questions that normally mean tabbing through six web UIs. It aggregates across your services and returns a single, structured answer without changing anything (it is strictly read-only in this phase).

Covered now: Sonarr, Radarr, Sportarr, Prowlarr, qBittorrent, Tdarr, and Profilarr — including multiple instances of the same type (e.g. a separate anime or 4K Sonarr/Radarr). Every tool fans out across all the instances you define and reports each one separately.

In practice

Once registered, you just ask your MCP client. Oraclarr picks the right tool and answers:

You ask… Oraclarr runs …and tells you
"Is anything in my stack down?" stack_health which services are unreachable, unhealthy, or low on disk
"Why hasn't the new episode of X downloaded yet?" diagnose whether X is in your library, sitting in the download queue, or still on the wanted list
"Why did a sub-only release get grabbed when I only allow English dubs?" explain_decision the release that was grabbed, its custom-format score, and the language formats that matched
"Why does Profilarr keep upgrading stuff that already looks fine?" explain_decision + get_quality_config the profile's cutoff and upgrade thresholds next to the scores of recent grabs
"What's stuck downloading right now?" get_queues a unified queue with progress and stall flags

Tools

All read-only and outcome-oriented (not one-per-endpoint, which keeps the model accurate):

Tool Answers
stack_health Is anything down / unhealthy / low on disk?
get_queues Unified active downloads (arr queue ↔ qBittorrent), stalls
diagnose Where is X in the pipeline / why isn't it here yet?
explain_decision Why was X grabbed or being upgraded? (profile, custom formats, grab history)
get_quality_config Quality profiles, custom formats, release profiles, Profilarr sync state
get_history Recent grabs / imports / failures
get_wanted What's missing / cutoff-unmet?
search_media Do I have X, what's its status?
get_indexers Which indexers are failing?
get_transcodes What's transcoding / stuck?
get_upcoming What's airing or releasing soon, and will it land? (up to 90 days)

Install

There are two ways to run Oraclarr — pick one, you don't need both:

  • 🐳 Docker (recommended) — a small always-on container that lives next to your arr stack. Best for homelabs; works great with Portainer, Dockge, Unraid, etc.
  • 🐍 Python (uv) — run it directly on your machine with no container. Best for local development, or if you simply don't use Docker.

Both options need the same two files. Make them from the templates in this repo — config.example.yaml (your service URLs) and .env.example (your API keys / passwords):

  • If you cloned the repo: cp config.example.yaml config.yaml and cp .env.example .env, then edit them.
  • If you're pasting into Dockge/Portainer: open those two example files above, copy their contents into a config.yaml and a .env in your stack folder, and fill in your details.

See Configuration below for exactly what goes in config.yaml. Your config.yaml and .env are never committed — your URLs and secrets stay on your machine. Now follow one of the two options below.


Oraclarr runs as a long-running container that serves MCP over HTTP at http://<host>:7979/mcp. You need three things in one folder: your config.yaml, your .env, and a docker-compose.yml.

Using a stack manager (Dockge, Portainer, etc.): create a new stack, paste the compose below into the editor, and put your config.yaml and .env in the same stack folder. Hit deploy.

Create config.yaml as a real file before you deploy. The compose mounts ./config.yaml into the container. If that file doesn't exist yet, Docker silently creates an empty directory at the mount point and the container exits with IsADirectoryError: Is a directory: '/config/config.yaml'. If you hit that: stop the stack, delete the config.yaml directory in the stack folder, create config.yaml as a file, and redeploy.

Using the command line:

cp docker-compose.example.yml docker-compose.yml
docker compose up -d

Either way, this is the compose file — copy/paste it as-is:

services:
  oraclarr:
    image: ghcr.io/bdog720/oraclarr-mcp:latest
    container_name: oraclarr
    restart: unless-stopped
    ports:
      - "7979:7979"           # left side is the host port — change it if 7979 is taken
    volumes:
      - ./config.yaml:/config/config.yaml:ro
    environment:
      SONARR_KEY: ${SONARR_KEY}
      SONARR_ANIME_KEY: ${SONARR_ANIME_KEY}
      RADARR_KEY: ${RADARR_KEY}
      PROWLARR_KEY: ${PROWLARR_KEY}
      QBIT_USER: ${QBIT_USER}
      QBIT_PASS: ${QBIT_PASS}
      PROFILARR_KEY: ${PROFILARR_KEY}
      SPORTARR_KEY: ${SPORTARR_KEY}

The keys on the right (${SONARR_KEY} …) are read from your .env file. Then connect Claude Desktop.


Option B — Python (uv)

Run Oraclarr directly, no Docker. Requires Python 3.12+ and uv.

From PyPI (no clone needed): the package is published as oraclarr-mcp. Point ORACLARR_CONFIG at your config.yaml and run it with uvx:

ORACLARR_CONFIG=config.yaml uvx oraclarr-mcp

From a clone (for development):

uv sync
ORACLARR_CONFIG=config.yaml uv run python -m oraclarr_mcp

This speaks MCP over stdio — the client launches the process for you, so you register it in your client's config rather than connecting to a URL:

"oraclarr": {
  "command": "uvx",
  "args": ["oraclarr-mcp"],
  "env": { "ORACLARR_CONFIG": "/absolute/path/to/config.yaml" }
}

(From a clone instead of PyPI, use "command": "uv", "args": ["run", "python", "-m", "oraclarr_mcp"], and add "cwd" pointing at the repo.)


Connect Claude Desktop

(For Option A / Docker. Option B registers itself with the JSON snippet above.)

Use the mcp-remote bridge in claude_desktop_config.json. It runs locally as a Claude Desktop "local MCP server", so it can reach the container on your LAN (requires Node.js / npx):

{
  "mcpServers": {
    "oraclarr": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://<host>:7979/mcp", "--allow-http"]
    }
  }
}

Replace <host> with the IP or hostname of the machine running the container.

Why --allow-http? Recent versions of mcp-remote reject plain http:// URLs unless the host is localhost. Without this flag the bridge exits immediately and Claude Desktop shows the server as "disconnected" — even though the server itself is reachable. The flag is safe here: the traffic stays on your trusted LAN.

Why not the native "Add custom connector"? Custom connectors connect to your server from Anthropic's cloud, not from your machine — so the URL must be a publicly reachable HTTPS endpoint (a http:// LAN/localhost URL is rejected). Pointing it at a homelab box means exposing this unauthenticated read-only service to the internet, which we don't recommend until authentication lands (a later phase). If you want it anyway, put a reverse proxy (Caddy/nginx) or tunnel (Cloudflare Tunnel, Tailscale Funnel) with TLS in front and add your own auth.

Security: the service is unauthenticated read-only MCP. Deploy on a trusted LAN only and do not expose port 7979 to the internet. Authentication is a later phase.

Transport settings (Docker only)

These apply to the Docker container only. The image already sets sensible defaults, so you normally don't touch them — the table is here for reference. The Python path (Option B) always uses stdio and ignores these entirely.

Variable Default What it does
ORACLARR_TRANSPORT http How clients reach the server. http = long-running service (Docker). stdio = the client launches the process (the Python path). You don't normally set this by hand — your install method picks it.
ORACLARR_HTTP_HOST 0.0.0.0 Which network interface to listen on. 0.0.0.0 means "reachable on your LAN" — leave as-is for Docker.
ORACLARR_HTTP_PORT 7979 Port inside the container. To change the port you reach it on, edit the ports: line in the compose instead.
ORACLARR_CONFIG /config/config.yaml Where the container reads config.yaml. Leave as-is.

Configuration

Instances are named in config.yaml; type selects the client, so any number of instances of any type (e.g. two Sonarrs) just work. Secrets are referenced as ${ENV_VAR} and pulled from .env:

server:
  allow_writes: false              # read-only; writes are a future phase
  toolsets: [media, downloads, transcode]
  tool_budget_seconds: 30          # per tool call; slow instances report status "timeout"
  max_output_bytes: 200000         # safety net for oversized responses
defaults:
  timeout_seconds: 10
instances:
  sonarr:       { type: sonarr,       url: http://host:8989, api_key: ${SONARR_KEY} }
  sonarr-anime: { type: sonarr,       url: http://host:8990, api_key: ${SONARR_ANIME_KEY} }
  radarr:       { type: radarr,       url: http://host:7878, api_key: ${RADARR_KEY} }
  prowlarr:     { type: prowlarr,     url: http://host:9696, api_key: ${PROWLARR_KEY} }
  qbit:         { type: qbittorrent,  url: http://host:8080, username: ${QBIT_USER}, password: ${QBIT_PASS} }
  tdarr:        { type: tdarr,        url: http://host:8265 }
  profilarr:    { type: profilarr,    url: http://host:6868, api_key: ${PROFILARR_KEY} }
  sportarr:     { type: sportarr,     url: http://host:1867, api_key: ${SPORTARR_KEY} }

Every tool call has a time budget, set by tool_budget_seconds. If an instance doesn't answer in time, it comes back as status: "timeout" and the other instances still return their data. List results carry only the useful fields and stop at each tool's limit. When there is more, truncated and total say so. API keys and passwords from your config show up as *** in responses and log lines (values shorter than four characters are left alone).

Roadmap

Oraclarr is built in phases. v1 is deliberately read-only; write capability arrives behind explicit safety gates. The architecture (typed clients + outcome tools + config-selectable toolsets) is designed to extend to other self-hosted suites beyond *arr, too.

  • ✅ Phase 1: read-only diagnostics. Sonarr, Radarr, Sportarr, Prowlarr, qBittorrent, Tdarr, Profilarr.
  • 🔜 Phase 2: more services, still read-only. Jellyfin, Seerr, Gluetun, TrueNAS, Cleanuparr, Unpackerr, ntfy. The architecture also opens up to non-*arr stacks.
  • 🔭 Phase 3: safe write actions. Trigger search, retry, refresh/rescan, approve requests, gated behind allow_writes with dry-run previews and an audit log.
  • 🔭 Phase 4: destructive actions. Delete media, blocklist, stack control, behind an explicit confirmation and allowlist.

Have a service you'd like covered? Open an issue.

💬 Feedback & Issues

Found a bug or have a feature request? We'd love to hear about it. Please open an issue here on GitHub.

🤝 Contributing

We welcome contributions! Issues and PRs welcome — the codebase is TDD throughout:

uv run pytest        # tests (respx-mocked, no live services needed)
uv run ruff check .  # lint

Adding a service follows a small recipe (new client → register by type → extend a tool → write the test). See the project layout under src/oraclarr_mcp/.

📄 License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.

💖 Support the Project

If you find Oraclarr useful and it saves you from tabbing through six web UIs to work out why something didn't download, consider buying me a coffee to support future development!

Buy Me A Coffee

Metadata

Release files for oraclarr-mcp 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oraclarr-mcp 1.1.0
File Size Uploaded Provenance Actions
oraclarr_mcp-1.1.0.tar.gz 92.1 kB With provenance Details for oraclarr_mcp-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for oraclarr-mcp 1.1.0
File Interpreter ABI Platform Actions
oraclarr_mcp-1.1.0-py3-none-any.whl Python 3 none any Details for oraclarr_mcp-1.1.0-py3-none-any.whl

Total release size: 127.6 kB

Release files / oraclarr_mcp-1.1.0.tar.gz

Download URL oraclarr_mcp-1.1.0.tar.gz
Size 92.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1f25732661f906af0a08bc51c35c68e665e0bbdd6846f1235f86d12cf717f85a
BLAKE2b-256 checksum
How to use checksums
6f115ac915c8b5e95e8eaff7dddb50aaa166f2d275631affc741a45d28a72ba2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / oraclarr_mcp-1.1.0-py3-none-any.whl

Download URL oraclarr_mcp-1.1.0-py3-none-any.whl
Size 35.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8b958698e42b2bcf007147bd7a89f4fbe2a64111c2b52ed85a148b77beb8c00d
BLAKE2b-256 checksum
How to use checksums
f247b47628086ca0022ee81526004f108650676bdb5cffebc8d659229ba4c590
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Security for oraclarr-mcp 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for oraclarr_mcp-1.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page