media-stack-mcp
This server provides a unified MCP interface to manage a self-hosted media stack, allowing you to search, add, and track media downloads across multiple services.
Health Check –
media_health_check: Verify connectivity and version status for all configured media apps (Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent).Radarr (Movies)
radarr_lookup_movie– Search for a movie by title via Radarr/TMDB.radarr_add_movie– Add a movie by TMDB ID (with options for quality profile, root folder, availability, and auto-search).radarr_get_queue– View the current download/import queue.
Sonarr (TV Series)
sonarr_lookup_series– Search for a TV series by title via Sonarr/TVDB.sonarr_add_series– Add a series by TVDB ID (with options for quality profile, root folder, season folders, and auto-search for missing episodes).sonarr_get_queue– View the current download/import queue.
SABnzbd (Usenet Downloader)
sabnzbd_get_queue– View the current download queue.sabnzbd_pause– Pause all downloads.sabnzbd_resume– Resume all downloads.
Prowlarr (Indexer Management)
prowlarr_search– Search across indexers (supports general, movie, and TV search types).prowlarr_test_indexers– List and test all configured indexers.
qBittorrent (Torrent Client)
qbittorrent_list_torrents– List all torrents, optionally filtered by status.qbittorrent_pause_all– Pause all active torrents.qbittorrent_resume_all– Resume all paused torrents.
Provides tools for listing torrents, pausing all torrents, and resuming all torrents in qBittorrent.
Provides tools for searching and adding movies, and checking the download queue in Radarr.
Provides tools for searching and adding series, and checking the download queue in Sonarr.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@media-stack-mcpadd movie Inception to Radarr"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
media-stack-mcp
What This Is
media-stack-mcp is a Model Context Protocol server for operating a self-hosted media stack:
Radarr
Sonarr
Prowlarr
SABnzbd
qBittorrent
The primary deployment target is QNAP Docker. In Docker mode this runs as a persistent Streamable HTTP MCP endpoint:
http://<qnap-ip>:3000/mcpFor Claude remote MCP/custom connector usage, publish the same MCP server through Cloudflare Tunnel:
https://mcp.yourdomain.com/mcpClients must send:
Authorization: Bearer <MCP_HTTP_TOKEN>The container exposes an unauthenticated health endpoint:
http://<qnap-ip>:3000/healthzRelated MCP server: Arr Suite MCP Server
Clone The Repo Locally And Build Docker
Use this path if you want to clone the repository on your workstation, build the Docker image locally, and optionally smoke-test the HTTP server before deploying it on the QNAP.
Clone the repo locally:
git clone https://github.com/gbone001/media-stack-mcp.git
cd media-stack-mcpCreate a local .env from the example:
cp .env.example .envEdit .env and set at least:
MCP_TRANSPORT=http
MCP_HTTP_HOST=0.0.0.0
MCP_HTTP_PORT=3000
MCP_HTTP_TOKEN=use_a_long_random_secret_hereIf you want to run the container locally, use LAN IP URLs for Radarr, Sonarr, Prowlarr, SABnzbd, and qBittorrent unless those services are also running on the same local Docker network:
RADARR_URL=http://<qnap-ip>:7878
SONARR_URL=http://<qnap-ip>:8989
PROWLARR_URL=http://<qnap-ip>:9696
SABNZBD_URL=http://<qnap-ip>:8080
QBITTORRENT_URL=http://<qnap-ip>:5080Build the Docker image locally:
docker build -t media-stack-mcp .This does not require Node.js or pnpm on your workstation. The Dockerfile installs and builds everything inside the image.
Optional local smoke test:
docker run --rm -p 3000:3000 --env-file .env -e MCP_TRANSPORT=http media-stack-mcpIn another terminal, check:
curl http://localhost:3000/healthzExpected response:
{"ok":true,"name":"media-stack-mcp","transport":"http"}When you are ready to run it on the QNAP, SSH into the QNAP and either clone the same repo there or pull the latest code in the existing QNAP checkout:
cd /share/Container/media-stack-mcp
git pull
docker compose up -d --buildThe included docker-compose.yml is the preferred QNAP runtime path because it publishes port 3000, loads .env, and attaches the container to the expected media-stack Docker network.
For Claude, also configure Cloudflare Tunnel so the public HTTPS URL routes to the internal Docker service:
https://mcp.yourdomain.com -> http://media-stack-mcp:3000QNAP Docker Quick Start
SSH into your QNAP and choose an install location:
mkdir -p /share/Container
cd /share/Container
git clone https://github.com/gbone001/media-stack-mcp.git
cd media-stack-mcpCreate your runtime config:
cp .env.example .env
vi .envAt minimum, change the token:
MCP_HTTP_TOKEN=use_a_long_random_secret_hereBuild and start the MCP container:
docker compose up -d --buildWatch startup logs:
docker compose logs -f media-stack-mcpClaude HTTPS Setup
Claude remote MCP/custom connectors need a trusted HTTPS URL. This project ships an optional cloudflared sidecar in Docker Compose so the MCP app can stay plain HTTP inside Docker while Cloudflare publishes HTTPS externally.
The sidecar is off by default and lives behind the cloudflare Compose profile. Enable it by adding --profile cloudflare to your compose commands (see below). If you reach the MCP another way (LAN only, your own reverse proxy, etc.), you can ignore this section entirely and leave CLOUDFLARED_TOKEN blank.
The important lesson from Claude setup is that Claude does not connect to the QNAP LAN URL directly. Use the Cloudflare HTTPS hostname in Claude, not http://<qnap-ip>:3000/mcp, not localhost, and not a self-signed HTTPS URL.
Claude needs:
a publicly reachable
https://URL with a trusted certificatethe full MCP path, for example
https://mcp.yourdomain.com/mcpthe bearer token header:
Authorization: Bearer <MCP_HTTP_TOKEN>no extra browser-only login page in front of
/mcp
In the Cloudflare Zero Trust dashboard:
Create a Cloudflare Tunnel.
Choose Docker as the connector environment.
Add a public hostname, for example
mcp.yourdomain.com.Set the tunnel service target to:
http://media-stack-mcp:3000Copy the tunnel token into .env:
CLOUDFLARED_TOKEN=your_cloudflare_tunnel_tokenSet the public MCP URL and allowed Host header:
MCP_PUBLIC_URL=https://mcp.yourdomain.com/mcp
MCP_ALLOWED_HOSTS=mcp.yourdomain.comStart or update the stack with the tunnel enabled:
docker compose --profile cloudflare up -d --buildCheck the Cloudflare Tunnel logs:
docker compose logs -f cloudflaredUse this URL in Claude:
https://mcp.yourdomain.com/mcpUse this header:
Authorization: Bearer <MCP_HTTP_TOKEN>Do not put Cloudflare Access in front of /mcp unless your Claude client can send the required Cloudflare Access headers. Keep MCP_HTTP_TOKEN as the MCP protection layer.
Claude connector values should look like this:
Name: media-stack
URL: https://mcp.yourdomain.com/mcp
Authorization header: Bearer your_long_random_mcp_tokenIf Claude asks for separate auth fields, use:
Header name: Authorization
Header value: Bearer your_long_random_mcp_tokenThe token must be the value from MCP_HTTP_TOKEN, not the Cloudflare tunnel token.
Do not paste CLOUDFLARED_TOKEN into Claude. That token is only for the cloudflared Docker sidecar.
Required .env Values
These MCP settings should be present for QNAP Docker mode:
MCP_SERVER_NAME=media-stack-mcp
MCP_TRANSPORT=http
MCP_HTTP_HOST=0.0.0.0
MCP_HTTP_PORT=3000
MCP_HTTP_PATH=/mcp
MCP_HTTP_TOKEN=use_a_long_random_secret_here
MCP_PUBLIC_URL=https://mcp.yourdomain.com/mcp
MCP_ALLOWED_HOSTS=mcp.yourdomain.com
CLOUDFLARED_TOKEN=your_cloudflare_tunnel_tokenConfigure whichever media apps you want enabled. A tool returns a clear configuration error if its matching app URL/key is missing.
RADARR_URL=http://radarr:7878
RADARR_API_KEY=your_radarr_api_key
SONARR_URL=http://sonarr:8989
SONARR_API_KEY=your_sonarr_api_key
PROWLARR_URL=http://prowlarr:9696
PROWLARR_API_KEY=your_prowlarr_api_key
SABNZBD_URL=http://sabnzbd:8080
SABNZBD_API_KEY=your_sabnzbd_api_key
QBITTORRENT_URL=http://vpn:5080
QBITTORRENT_USERNAME=admin
QBITTORRENT_PASSWORD=your_qbittorrent_passwordFor Radarr, Sonarr, and Prowlarr, get the API key from each app's UI under Settings -> General -> Security / API Key.
For SABnzbd, get the API key from Config -> General -> Security / API Key.
For qBittorrent, use the WebUI URL and WebUI username/password.
Docker Networking On QNAP
The included docker-compose.yml expects an existing external Docker network named media-stack:
networks:
media-stack:
external: trueList Docker networks on the QNAP:
docker network lsInspect which networks your existing media containers use:
docker inspect radarr --format '{{json .NetworkSettings.Networks}}'
docker inspect sonarr --format '{{json .NetworkSettings.Networks}}'
docker inspect prowlarr --format '{{json .NetworkSettings.Networks}}'
docker inspect sabnzbd --format '{{json .NetworkSettings.Networks}}'
docker inspect vpn --format '{{json .NetworkSettings.Networks}}'If media-stack already exists and your media containers are on it, no compose change is needed.
If your media stack uses a different network name, update docker-compose.yml to use that network instead of media-stack.
If the network does not exist, create it:
docker network create media-stackThen attach existing containers as needed:
docker network connect media-stack radarr
docker network connect media-stack sonarr
docker network connect media-stack prowlarr
docker network connect media-stack sabnzbd
docker network connect media-stack vpnWhen this MCP container shares a Docker network with your media apps, use container DNS names:
RADARR_URL=http://radarr:7878
SONARR_URL=http://sonarr:8989
PROWLARR_URL=http://prowlarr:9696
SABNZBD_URL=http://sabnzbd:8080
QBITTORRENT_URL=http://vpn:5080If it does not share a Docker network, use your QNAP or LAN IP instead:
RADARR_URL=http://<qnap-ip>:7878
SONARR_URL=http://<qnap-ip>:8989
PROWLARR_URL=http://<qnap-ip>:9696
SABNZBD_URL=http://<qnap-ip>:8080
QBITTORRENT_URL=http://<qnap-ip>:5080If qBittorrent runs behind Gluetun and the WebUI is exposed by the VPN/Gluetun container, use the container that exposes the WebUI. Commonly:
QBITTORRENT_URL=http://vpn:5080Validate The Deployment
Check container status:
docker compose psCheck logs:
docker compose logs -f media-stack-mcpCheck the health endpoint:
curl http://<qnap-ip>:3000/healthzExpected response:
{"ok":true,"name":"media-stack-mcp","transport":"http"}Check the Cloudflare HTTPS health endpoint:
curl https://mcp.yourdomain.com/healthzExpected response:
{"ok":true,"name":"media-stack-mcp","transport":"http"}Check that /mcp rejects unauthenticated requests:
curl -i -X POST http://<qnap-ip>:3000/mcp \
-H 'Content-Type: application/json' \
-d '{}'Expected status:
401 UnauthorizedCheck that the public HTTPS /mcp endpoint also rejects unauthenticated requests:
curl -i -X POST https://mcp.yourdomain.com/mcp \
-H 'Content-Type: application/json' \
-d '{}'Expected status:
401 UnauthorizedMCP clients must call /mcp with:
Authorization: Bearer <MCP_HTTP_TOKEN>MCP Client Config
Use this shape for clients that support remote Streamable HTTP MCP servers:
{
"mcpServers": {
"media-stack": {
"url": "https://mcp.yourdomain.com/mcp",
"headers": {
"Authorization": "Bearer your_long_random_token"
}
}
}
}Replace mcp.yourdomain.com with your Cloudflare Tunnel hostname and your_long_random_token with MCP_HTTP_TOKEN from .env.
HTTP MCP sessions are stored in memory. Restarting the container resets active MCP sessions, which is expected.
Operations
Start or update after config changes:
docker compose up -d --buildStop:
docker compose downRestart:
docker compose restart media-stack-mcpFollow logs:
docker compose logs -f media-stack-mcpFollow Cloudflare Tunnel logs:
docker compose logs -f cloudflaredPull the latest code and rebuild:
git pull && docker compose up -d --buildTroubleshooting
.env Not Found
Create the runtime env file:
cp .env.example .env
vi .envnetwork media-stack declared as external, but could not be found
Create the expected network:
docker network create media-stackOr edit docker-compose.yml to use the Docker network your media containers already use.
MCP_HTTP_TOKEN is required when MCP_TRANSPORT=http
Set a token in .env:
MCP_HTTP_TOKEN=use_a_long_random_secret_hereThen restart:
docker compose up -d --buildCLOUDFLARED_TOKEN Is Missing Or Invalid
Create a Cloudflare Tunnel in the Cloudflare Zero Trust dashboard, copy the Docker tunnel token, and set:
CLOUDFLARED_TOKEN=your_cloudflare_tunnel_tokenThen restart with the cloudflare profile and check logs:
docker compose --profile cloudflare up -d --build
docker compose logs -f cloudflaredCloudflare Tunnel Shows 502 Or Cannot Reach The Service
In the Cloudflare Tunnel public hostname settings, the service target should be:
http://media-stack-mcp:3000Confirm both containers are on the same Docker network:
docker compose ps
docker inspect media-stack-mcp --format '{{json .NetworkSettings.Networks}}'
docker inspect media-stack-mcp-cloudflared --format '{{json .NetworkSettings.Networks}}'Claude Cannot Connect To The MCP URL
Use the public Cloudflare URL in Claude:
https://mcp.yourdomain.com/mcpDo not use these in Claude remote MCP/custom connector setup:
http://<qnap-ip>:3000/mcp
http://localhost:3000/mcp
https://<qnap-ip>:3000/mcpThen validate from outside your LAN if possible:
curl https://mcp.yourdomain.com/healthzIf health works but Claude still fails, check that Claude has the bearer token header exactly:
Authorization: Bearer <MCP_HTTP_TOKEN>Also confirm Cloudflare is not presenting an Access login page or other browser challenge in front of /mcp.
Public HTTPS URL Returns Host Header Errors
Set MCP_ALLOWED_HOSTS to your Cloudflare Tunnel hostname:
MCP_ALLOWED_HOSTS=mcp.yourdomain.comThen restart:
docker compose restart media-stack-mcp401 Unauthorized
Your MCP client is missing the bearer token or using the wrong token. Configure:
Authorization: Bearer <MCP_HTTP_TOKEN>Media App Connection Failures
Check that each URL works from inside the MCP container's network. If container names do not resolve, either attach the MCP container to the same Docker network or use LAN IP URLs.
Common same-network URLs:
RADARR_URL=http://radarr:7878
SONARR_URL=http://sonarr:8989
PROWLARR_URL=http://prowlarr:9696
SABNZBD_URL=http://sabnzbd:8080Common LAN URLs:
RADARR_URL=http://<qnap-ip>:7878
SONARR_URL=http://<qnap-ip>:8989
PROWLARR_URL=http://<qnap-ip>:9696
SABNZBD_URL=http://<qnap-ip>:8080Also confirm the API key is copied from the matching app, not another service.
qBittorrent Auth Failure
Confirm QBITTORRENT_URL points to the WebUI endpoint and that the WebUI credentials are correct:
QBITTORRENT_URL=http://vpn:5080
QBITTORRENT_USERNAME=admin
QBITTORRENT_PASSWORD=your_qbittorrent_passwordIf you use Gluetun, the correct host is often the VPN/Gluetun container rather than a separate qbittorrent container.
Current MCP Tools
Health
media_health_check
Radarr
radarr_lookup_movieradarr_add_movieradarr_get_queue
Sonarr
sonarr_lookup_seriessonarr_add_seriessonarr_get_queue
SABnzbd
sabnzbd_get_queuesabnzbd_pausesabnzbd_resume
Prowlarr
prowlarr_searchprowlarr_test_indexers
qBittorrent
qbittorrent_list_torrentsqbittorrent_pause_allqbittorrent_resume_all
Local Stdio Mode
For local process-spawned MCP clients, set:
MCP_TRANSPORT=stdioInstall and build locally:
corepack enable
pnpm install
pnpm run buildExample local stdio MCP config:
{
"mcpServers": {
"media-stack": {
"command": "node",
"args": ["/path/to/media-stack-mcp/dist/index.js"],
"env": {
"MCP_TRANSPORT": "stdio",
"RADARR_URL": "http://<qnap-ip>:7878",
"RADARR_API_KEY": "your_key",
"SONARR_URL": "http://<qnap-ip>:8989",
"SONARR_API_KEY": "your_key",
"SABNZBD_URL": "http://<qnap-ip>:8080",
"SABNZBD_API_KEY": "your_key",
"PROWLARR_URL": "http://<qnap-ip>:9696",
"PROWLARR_API_KEY": "your_key",
"QBITTORRENT_URL": "http://<qnap-ip>:5080",
"QBITTORRENT_USERNAME": "admin",
"QBITTORRENT_PASSWORD": "your_password"
}
}
}
}Security
Do not expose port 3000 directly to the public internet.
Use Cloudflare Tunnel for Claude HTTPS connectivity instead of opening inbound ports on the QNAP. Keep bearer-token auth enabled, use a long random MCP_HTTP_TOKEN, set MCP_ALLOWED_HOSTS to your public tunnel hostname, and rotate both MCP_HTTP_TOKEN and CLOUDFLARED_TOKEN if either value is shared or exposed.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gbone001/media-stack-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server