initial commit

This commit is contained in:
2026-07-14 10:49:14 +02:00
commit 9fef20ad53
16 changed files with 588 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server.py .
EXPOSE 8013
CMD ["python", "/app/server.py"]

View File

@@ -0,0 +1,2 @@
mcp[cli]>=1.10.0
requests>=2.32.3

View File

@@ -0,0 +1,83 @@
import os
from typing import Any
import requests
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("media-stack", host="0.0.0.0", port=8013)
def _get_json(url: str, headers: dict[str, str] | None = None, timeout: int = 15) -> Any:
response = requests.get(url, headers=headers or {}, timeout=timeout)
response.raise_for_status()
return response.json()
@mcp.tool()
def service_health() -> dict[str, str]:
"""Check reachability of Radarr, Sonarr, qBittorrent, and Plex endpoints."""
checks = {
"radarr": (os.getenv("RADARR_URL", ""), "/api/v3/system/status", {"X-Api-Key": os.getenv("RADARR_API_KEY", "")}),
"sonarr": (os.getenv("SONARR_URL", ""), "/api/v3/system/status", {"X-Api-Key": os.getenv("SONARR_API_KEY", "")}),
"qbit": (os.getenv("QBIT_URL", ""), "/api/v2/app/version", {}),
"plex": (os.getenv("PLEX_URL", ""), "/identity", {"X-Plex-Token": os.getenv("PLEX_TOKEN", "")}),
}
status = {}
for name, (base, path, headers) in checks.items():
if not base:
status[name] = "not_configured"
continue
try:
requests.get(f"{base.rstrip('/')}{path}", headers=headers, timeout=10).raise_for_status()
status[name] = "ok"
except Exception as exc:
status[name] = f"error: {exc}"
return status
@mcp.tool()
def radarr_queue(page: int = 1, page_size: int = 20) -> Any:
"""Return current Radarr queue items."""
base = os.getenv("RADARR_URL", "")
key = os.getenv("RADARR_API_KEY", "")
url = f"{base.rstrip('/')}/api/v3/queue?page={page}&pageSize={page_size}"
return _get_json(url, headers={"X-Api-Key": key})
@mcp.tool()
def sonarr_queue(page: int = 1, page_size: int = 20) -> Any:
"""Return current Sonarr queue items."""
base = os.getenv("SONARR_URL", "")
key = os.getenv("SONARR_API_KEY", "")
url = f"{base.rstrip('/')}/api/v3/queue?page={page}&pageSize={page_size}"
return _get_json(url, headers={"X-Api-Key": key})
@mcp.tool()
def qbit_torrents(filter_name: str = "all") -> Any:
"""Return qBittorrent torrent list by filter (all/downloading/completed/etc)."""
base = os.getenv("QBIT_URL", "")
user = os.getenv("QBIT_USERNAME", "")
password = os.getenv("QBIT_PASSWORD", "")
session = requests.Session()
session.post(f"{base.rstrip('/')}/api/v2/auth/login", data={"username": user, "password": password}, timeout=15).raise_for_status()
response = session.get(f"{base.rstrip('/')}/api/v2/torrents/info", params={"filter": filter_name}, timeout=15)
response.raise_for_status()
return response.json()
@mcp.tool()
def plex_sessions() -> Any:
"""Return active Plex sessions."""
base = os.getenv("PLEX_URL", "")
token = os.getenv("PLEX_TOKEN", "")
headers = {"X-Plex-Token": token, "Accept": "application/json"}
url = f"{base.rstrip('/')}/status/sessions"
return _get_json(url, headers=headers)
if __name__ == "__main__":
mcp.run(transport="sse")