bazarr-mcp-server
Click on "Deploy 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., "@bazarr-mcp-serverWhich movies are missing subtitles?"
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.
bazarr-mcp-server
A minimal Model Context Protocol server that connects to Bazarr (the subtitle-manager companion to Sonarr/Radarr), packaged for Docker.
It runs as a standing network service (streamable-http transport, not stdio),
so any MCP client on your internal network can connect to
http://<host>:<port>/mcp — the container isn't spawned per-client, and
container lifecycle/updates can be handed off to a tool like
Dockhand.
Tools
Tool | Description |
| List movies Bazarr is tracking, optionally filtered by title |
| List TV series Bazarr is tracking, optionally filtered by title |
| Movies with at least one wanted subtitle language missing |
| Episodes with at least one wanted subtitle language missing |
| Search for and download one subtitle language for a movie |
| Search for and download one subtitle language for an episode |
| Trigger Bazarr's background job to search all wanted subtitles for movies or series |
| Bazarr version/environment info and current health issues |
search_movie_subtitles, search_episode_subtitles, and run_wanted_search
are the only tools that change state in Bazarr (they trigger real subtitle
searches/downloads). Everything else is read-only.
Related MCP server: media-stack-mcp
Note on API accuracy
Bazarr was not reachable while building this server, so the tools were
written against Bazarr's own source
(bazarr/api/) rather than a live instance — endpoint paths, the X-API-KEY
auth header, and response shapes ({"data": ..., "total": ...} envelopes,
field names like radarrId/sonarrSeriesId/missing_subtitles) are read
directly out of the current master branch's Flask-RESTX route definitions.
Run GET /ready (see below) and the mocked test suite is not a substitute for
this — confirm against your real instance, and if a field is missing or an
endpoint 404s, that's real API drift to report, not a bug in guesswork.
Health endpoints
Two plain HTTP endpoints, reachable without MCP_AUTH_TOKEN (so Docker's
HEALTHCHECK, Dockhand, or any other monitor can poll them without the
secret):
Endpoint | Checks | Healthy | Unhealthy |
| The process is up and serving HTTP. Does not call Bazarr. |
| (doesn't respond) |
|
|
|
|
Unlike the Servarr-family servers (Sonarr/Radarr/Prowlarr), there's no API-
version compatibility check here — Bazarr isn't a Servarr app and has no
equivalent unauthenticated version-discovery endpoint, and its /api/...
routes are unversioned.
Authentication
Set MCP_AUTH_TOKEN (a random shared secret — openssl rand -hex 32) and
every request must carry Authorization: Bearer <token> or the server
returns 401. This is checked by a small Starlette middleware in front of
the MCP app, not the mcp SDK's built-in OAuth support
(mcp.server.auth) — that machinery expects a full OAuth authorization
server (issuer/resource metadata, RFC 8414/8707/9068 discovery), which is
unnecessary complexity for one secret shared by trusted LAN clients.
Leave MCP_AUTH_TOKEN unset and the server runs with no auth — anything
that can reach http://<host>:<port>/mcp can call every tool, including the
subtitle-download and wanted-search ones. The server logs a warning on
startup when it's running this way. Either way, the trust boundary is still
the network:
Do not publish this port through any reverse proxy, port-forward, or anything else reachable from outside your LAN/VLAN — the bearer token protects against anyone on the network, not against the open internet.
Bind the compose
ports:mapping to a specific internal interface (e.g.192.168.1.50:8933:8933) rather than all interfaces, if you want to be stricter about which hosts on your network can reach it at all.
Configuration
Environment variables (see .env.example):
Variable | Required | Default | Description |
| yes | — | e.g. |
| yes | — | Bazarr > Settings > General > Security > API Key |
| no |
| Interface the server binds to inside the container |
| no |
| Port the server listens on |
| no | — | Shared secret required as |
Image
Built and pushed to ghcr.io/barrow1990/bazarr-mcp-server by
.github/workflows/ci.yml on every push to
main that passes tests, tagged :latest and :<commit-sha>.
docker-compose.yml pulls :latest by default; swap in build: . there
instead if you'd rather build locally from the Dockerfile.
The image is the same three-stage build used across this family of MCP
servers: builder compiles dependencies into --target=/deps; prep starts
fresh from python:3.12-alpine, drops pip/setuptools/wheel, strips stdlib
pieces this headless server never touches, adds the non-root app user, and
copies in /deps and server.py; runtime then does a single
COPY --from=prep / / onto a scratch base — the step that actually drops
the stripped bytes from what gets pushed, rather than just hiding them in a
layered image. Expect roughly the same ~98MB floor as the other servers
in this family (mcp.server.request_state unconditionally imports
cryptography's AES-GCM/HKDF, so that ~15MB native extension ships
regardless). Dependencies in requirements.txt are pinned to exact versions.
Running with Docker Compose
cp .env.example .env # fill in BAZARR_URL / BAZARR_API_KEY
docker compose up -d --pull alwaysThe server is then reachable at http://<docker-host>:8933/mcp from anything
on your internal network.
Managing with Dockhand
Point Dockhand at ghcr.io/barrow1990/bazarr-mcp-server and let it track new
tags — this is the registry-pull model Dockhand's image-update tracking
(Grype/Trivy scans, tag tracking, scheduled updates) is actually built around.
The alternative, pointing Dockhand at this repo as a Git-deployed Compose
stack with build: ., works too, but syncing new Git commits does not
imply rebuilding the image — those are two separate steps for a build-from-
source stack.
Make the GHCR package public, or every pull will need docker login ghcr.io with a PAT on each deploy host — a private package by default
requires auth even to docker pull, which most homelab boxes won't have
configured.
Set a restart policy of unless-stopped (already in docker-compose.yml) so
Dockhand-driven restarts and host reboots bring it back up without manual
intervention. The HEALTHCHECK in the Dockerfile (GET /health) drives
Docker's/Dockhand's container health status; use GET /ready separately if
you want to alert on Bazarr connectivity specifically rather than container
liveness.
Environment variables in Dockhand: docker-compose.yml loads
BAZARR_URL/BAZARR_API_KEY/MCP_AUTH_TOKEN via env_file: [.env, .env.dockhand]
(both optional; .env.dockhand loads second, so it wins for any key it also
sets). This is deliberate — a Git-deployed stack's .env is whatever's
checked out from the repo (i.e. .env.example's placeholders, since real
.env is gitignored and not committed), while Dockhand writes the values you
configure in its UI to .env.dockhand instead.
Connecting a client
Claude Code
claude mcp add bazarr -s user --transport http http://<docker-host>:8933/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"(Drop the --header flag if you're running with MCP_AUTH_TOKEN unset.)
Claude Desktop
Claude Desktop's built-in config expects a locally-spawned command, so for
a network server like this you'll need an HTTP-to-stdio bridge such as
mcp-remote:
{
"mcpServers": {
"bazarr": {
"command": "npx",
"args": [
"-y", "mcp-remote", "http://<docker-host>:8933/mcp",
"--header", "Authorization: Bearer <MCP_AUTH_TOKEN>"
]
}
}
}Running without Docker
pip install -r requirements.txt
BAZARR_URL=http://192.168.1.50:6767 BAZARR_API_KEY=your-api-key \
MCP_AUTH_TOKEN=your-shared-secret python server.pyTesting
pip install -r requirements-dev.txt
python -m pytest tests/ -vtests/test_tools.py— each tool's logic against a mocked Bazarr (httpx.MockTransport, no extra mocking library needed).tests/test_http.py—/health,/ready, and the bearer-auth middleware, viaserver.build_app()(the exact app__main__runs) through Starlette'sTestClient.tests/test_live_bazarr.py— opt-in contract tests against a real Bazarr instance, to catch drift if a Bazarr upgrade renames/removes a field these tools depend on (radarrId,sonarrSeriesId,missing_subtitles,bazarr_version, ...). Skipped by default (no Bazarr in CI); run with:RUN_LIVE_BAZARR_TESTS=1 BAZARR_URL=https://bazarr.example.com \ BAZARR_API_KEY=<real key> python -m pytest tests/test_live_bazarr.py -v
CI (.github/workflows/ci.yml) runs the mocked suite on every push/PR; the
GHCR build only runs after it passes.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Manage your KeepMySubs subscriptions, spend, renewals, and bills from any MCP client.
Control Sonos from any MCP client: play, search, group rooms, volume, announcements, reminders.
Unlock a world of television with the TV Maze MCP server. Effortlessly search for shows by name or
Trakt MCP — TV/movie metadata + watch tracking signals
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables interaction with the *arr media management suite (Sonarr, Radarr, Lidarr, Prowlarr, SABnzbd) and TRaSH Guides through MCP tools, allowing media library management, searching, and configuration via natural language.706 npm1MIT
- AlicenseBqualityBmaintenanceEnables control and management of a self-hosted media stack (Radarr, Sonarr, Prowlarr, SABnzbd, qBittorrent) through natural language via MCP.15GPL 3.0
- FlicenseNot gradedqualityBmaintenanceEnables MCP clients to interact with the arr media stack (Sonarr, Radarr, Prowlarr, Jellyseerr, Bazarr) via clean, typed tools over each app's REST API.1-
- AlicenseCqualityAmaintenanceEnables management of Bazarr subtitles through all 89 API operations, including blacklists, wanted lists, provider configuration, language profiles, and Plex/Jellyfin integrations.89MIT