Media Stack
БесплатноНе проверенEnables control and management of a self-hosted media stack (Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent) through natural language via MCP.
Описание
Enables control and management of a self-hosted media stack (Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent) through natural language via MCP.
README
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/mcp
For Claude remote MCP/custom connector usage, publish the same MCP server through Cloudflare Tunnel:
https://mcp.yourdomain.com/mcp
Clients must send:
Authorization: Bearer <MCP_HTTP_TOKEN>
The container exposes an unauthenticated health endpoint:
http://<qnap-ip>:3000/healthz
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-mcp
Create a local .env from the example:
cp .env.example .env
Edit .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_here
If 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>:5080
Build 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-mcp
In another terminal, check:
curl http://localhost:3000/healthz
Expected 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 --build
The 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:3000
QNAP 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-mcp
Create your runtime config:
cp .env.example .env
vi .env
At minimum, change the token:
MCP_HTTP_TOKEN=use_a_long_random_secret_here
Build and start the MCP container:
docker compose up -d --build
Watch startup logs:
docker compose logs -f media-stack-mcp
Claude 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 certificate - the full MCP path, for example
https://mcp.yourdomain.com/mcp - the 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:3000
Copy the tunnel token into .env:
CLOUDFLARED_TOKEN=your_cloudflare_tunnel_token
Set the public MCP URL and allowed Host header:
MCP_PUBLIC_URL=https://mcp.yourdomain.com/mcp
MCP_ALLOWED_HOSTS=mcp.yourdomain.com
Start or update the stack with the tunnel enabled:
docker compose --profile cloudflare up -d --build
Check the Cloudflare Tunnel logs:
docker compose logs -f cloudflared
Use this URL in Claude:
https://mcp.yourdomain.com/mcp
Use 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_token
If Claude asks for separate auth fields, use:
Header name: Authorization
Header value: Bearer your_long_random_mcp_token
The 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_token
Configure 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_password
For 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: true
List Docker networks on the QNAP:
docker network ls
Inspect 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-stack
Then 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 vpn
When 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:5080
If 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>:5080
If 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:5080
Validate The Deployment
Check container status:
docker compose ps
Check logs:
docker compose logs -f media-stack-mcp
Check the health endpoint:
curl http://<qnap-ip>:3000/healthz
Expected response:
{"ok":true,"name":"media-stack-mcp","transport":"http"}
Check the Cloudflare HTTPS health endpoint:
curl https://mcp.yourdomain.com/healthz
Expected 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 Unauthorized
Check 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 Unauthorized
MCP 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 --build
Stop:
docker compose down
Restart:
docker compose restart media-stack-mcp
Follow logs:
docker compose logs -f media-stack-mcp
Follow Cloudflare Tunnel logs:
docker compose logs -f cloudflared
Pull the latest code and rebuild:
git pull && docker compose up -d --build
Troubleshooting
.env Not Found
Create the runtime env file:
cp .env.example .env
vi .env
network media-stack declared as external, but could not be found
Create the expected network:
docker network create media-stack
Or 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_here
Then restart:
docker compose up -d --build
CLOUDFLARED_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_token
Then restart with the cloudflare profile and check logs:
docker compose --profile cloudflare up -d --build
docker compose logs -f cloudflared
Cloudflare Tunnel Shows 502 Or Cannot Reach The Service
In the Cloudflare Tunnel public hostname settings, the service target should be:
http://media-stack-mcp:3000
Confirm 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/mcp
Do not use these in Claude remote MCP/custom connector setup:
http://<qnap-ip>:3000/mcp
http://localhost:3000/mcp
https://<qnap-ip>:3000/mcp
Then validate from outside your LAN if possible:
curl https://mcp.yourdomain.com/healthz
If 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.com
Then restart:
docker compose restart media-stack-mcp
401 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:8080
Common 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>:8080
Also 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_password
If 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=stdio
Install and build locally:
corepack enable
pnpm install
pnpm run build
Example 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.
Установка Media Stack
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/gbone001/media-stack-mcpFAQ
Media Stack MCP бесплатный?
Да, Media Stack MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Media Stack?
Нет, Media Stack работает без API-ключей и переменных окружения.
Media Stack — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Media Stack в Claude Desktop, Claude Code или Cursor?
Открой Media Stack на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Omni Video
An MCP server that transforms LLM-enabled IDEs into professional video editors by pre-processing footage into text proxies, generating motion graphics via HTML/
автор: buildwithtazaARA
Generate images, video and audio from any AI agent — one connector.
автор: ARAYouTube
Transcripts, channel stats, search
автор: YouTubeEverArt
AI image generation using various models.
автор: modelcontextprotocolCompare Media Stack with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории media
