84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
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")
|