MCP server stack for a self-hosted media + ops setup — 8 Docker services exposing 68 tools (jellyfin, arr, qbit, jellyseerr, system, matrix, npm, pihole) over loopback SSE.
  • Python 93.3%
  • Shell 4.8%
  • Dockerfile 1.9%
Find a file
mikd 22c6c454af arr: fix add_album leaving new artists/albums unmonitored
Lidarr applies addOptions monitor:'none' server-side AFTER our flags: the
new artist row lands monitored=false (hiding all its albums from
wanted/missing and Soularr), and the initial RefreshArtist rewrites album
rows monitored=false racing the immediate monitor PUT. Now: verify-retry
loop (4x6s) re-monitors the album until the refresh settles + repairs the
artist flag; already_in_library early-return also repairs an unmonitored
artist instead of skipping it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRSnHJdvjZWGfqd7v4mf7c
2026-07-04 13:45:46 +02:00
arr arr: fix add_album leaving new artists/albums unmonitored 2026-07-04 13:45:46 +02:00
crowdsec feat(crowdsec): add mcp-crowdsec — security visibility + ban control 2026-06-20 12:49:33 +02:00
docker feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
forgejo feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
headscale mcp: add homeassistant (8773) + headscale (8774) servers 2026-05-12 18:23:08 +02:00
homeassistant mcp: add homeassistant (8773) + headscale (8774) servers 2026-05-12 18:23:08 +02:00
host feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
jellyfin feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
jellyseerr feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
matrix add mcp-matrix, mcp-npm, mcp-pihole — 28 new tools across 3 new servers 2026-05-11 21:40:04 +02:00
npm feat(arr): optional Lidarr music tools — lookup_album, add_album, get_album_status 2026-06-10 23:41:19 +02:00
pihole feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
qbit feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
searxng feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
system feat: 5 new MCP servers + expanded existing — 40 tools total 2026-05-11 21:12:16 +02:00
.gitignore feat: docker-compose.yml — mcp-jellyfin runs in host-network mode 2026-05-11 20:45:22 +02:00
.leak-allowlist feat(arr): optional Lidarr music tools — lookup_album, add_album, get_album_status 2026-06-10 23:41:19 +02:00
_safety.py feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
CLAUDE.md feat: operator write tier + mcp-docker/host/forgejo + mcp-searxng 2026-06-20 12:26:20 +02:00
docker-compose.yml feat(crowdsec): add mcp-crowdsec — security visibility + ban control 2026-06-20 12:49:33 +02:00
INSTALL.md add install.sh — interactive installer with backend autoprobe 2026-05-11 22:38:35 +02:00
install.sh add install.sh — interactive installer with backend autoprobe 2026-05-11 22:38:35 +02:00
README.md README: trim — drop marketing voice and obvious framing 2026-05-11 23:08:57 +02:00

mcp-stack

Docker-compose stack of Model Context Protocol servers. Each subdirectory is one server; each server exposes a small set of tools that an MCP client (Claude Code, the Python SDK, etc.) can call over HTTP/SSE on 127.0.0.1.

Setup: git clone … && cd mcp-stack && ./install.sh. See INSTALL.md.

Services

8 servers, 68 tools, all bound to 127.0.0.1.

Service Port Tools Backend
mcp-jellyfin 8765 10 Jellyfin :8096
mcp-arr 8766 14 Sonarr :8989 + Radarr :7878
mcp-qbit 8767 7 qBittorrent :8080
mcp-jellyseerr 8768 3 Jellyseerr :5055
mcp-system 8769 6 host metrics via psutil
mcp-matrix 8770 8 Synapse C2S + admin API
mcp-npm 8771 11 Nginx Proxy Manager :81
mcp-pihole 8772 9 Pi-hole v6 :5080

Port range 87708779 reserved for future servers.

Tools

* mutates state. No extra confirmation at the MCP layer (see Security).

mcp-jellyfinsearch_library, get_recently_added, get_active_sessions, get_item_details, browse_by_genre, get_libraries, get_continue_watching, get_next_up, mark_watched, mark_unwatched

mcp-arrget_series, get_series_episodes, get_movie, get_queue, get_health, get_history, get_calendar, get_blocklisted, get_quality_profiles, lookup_movie, lookup_series, trigger_search, add_movie, add_series*

mcp-qbitlist_downloads, get_transfer_stats, get_torrent_details, get_categories, force_recheck, pause_torrent, resume_torrent*

mcp-jellyseerrlist_requests, get_request, search_jellyseerr

mcp-systemget_disk_usage, get_disk_usage_all, get_memory, get_cpu, get_temps, get_uptime

mcp-matrixsend_message*, get_recent_messages, list_rooms, get_room_members, get_room_info, get_admin_stats, list_admin_users, whoami

mcp-npmlist_proxy_hosts, get_proxy_host, delete_proxy_host, enable_proxy_host, disable_proxy_host, add_proxy_host, list_certificates, get_certificate, renew_certificate*, list_redirection_hosts, get_server_info

mcp-piholeget_query_stats, get_top_clients, get_top_blocked_domains, get_top_permitted_domains, get_query_history, list_blocklists, get_blocking_status, disable_blocking, enable_blocking

Architecture

  • One subdirectory per server: Dockerfile, server.py, requirements.txt.
  • network_mode: host — each server binds 127.0.0.1:<port> and reaches its backend via localhost.
  • Secrets read from .env at the repo root (symlink /opt/yams/.env if you already have one).
  • mcp-system also needs pid: host and /:/host:ro so psutil sees host processes and filesystems. Tool args take host paths (/srv/media); the server maps them to /host/srv/media internally.
  • Tool return shapes are slimmed via _slim_*() helpers — LLM-friendly, smaller payloads.

Connecting a client

Claude Code — add to .mcp.json:

{
  "mcpServers": {
    "mcp-jellyfin":   { "type": "sse", "url": "http://localhost:8765/sse" },
    "mcp-arr":        { "type": "sse", "url": "http://localhost:8766/sse" },
    "mcp-qbit":       { "type": "sse", "url": "http://localhost:8767/sse" },
    "mcp-jellyseerr": { "type": "sse", "url": "http://localhost:8768/sse" },
    "mcp-system":     { "type": "sse", "url": "http://localhost:8769/sse" },
    "mcp-matrix":     { "type": "sse", "url": "http://localhost:8770/sse" },
    "mcp-npm":        { "type": "sse", "url": "http://localhost:8771/sse" },
    "mcp-pihole":     { "type": "sse", "url": "http://localhost:8772/sse" }
  }
}

Python SDK:

from mcp import ClientSession
from mcp.client.sse import sse_client

async with sse_client("http://localhost:8765/sse") as (r, w):
    async with ClientSession(r, w) as session:
        await session.initialize()
        result = await session.call_tool("search_library", {"query": "Mad Max", "limit": 5})

Gotcha for custom clients: FastMCP returns list-typed results as N separate text content blocks (one per element), not one JSON list. Concat them:

blocks = [json.loads(b.text) for b in res.content if hasattr(b, "text")]
return blocks[0] if len(blocks) == 1 else blocks

Security

Loopback bind only. The backend still checks its own API keys. The calling LLM is the write-gate — there's no extra "are you sure?" at the MCP layer for add_movie, delete_proxy_host, disable_blocking, etc. If you need write gating, fork and split write tools behind a separate port.

Operating it

docker compose up -d                            # bring up
docker compose ps
docker logs -f mcp-matrix
docker compose up -d --build mcp-pihole         # rebuild one after edit
docker compose down

Adding a new MCP server

  1. mkdir <service>/ with Dockerfile, server.py, requirements.txt. pihole/ is small and easy to copy.
  2. Pick a free port in 87708779. Reserve it in the table above.
  3. Add a services: entry to docker-compose.ymlnetwork_mode: host, only the env vars the server needs, :? on the required ones.
  4. Tool docstrings describe inputs + output + when to call. Slim returns to fields useful to an LLM.
  5. docker compose up -d --build mcp-<service> and smoke-test.
  6. Add the SSE URL to clients' .mcp.json.

License

Source-available, no formal license. Fork freely.