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")