setup
Writes the initial TOML configuration file for first-run setup, configuring Prowlarr and qBittorrent credentials.
Instructions
Write the TOML config file (first-run setup). mutates: true
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prowlarr_url | Yes | ||
| prowlarr_api_key | Yes | ||
| qb_url | Yes | ||
| qb_username | Yes | ||
| qb_password | Yes | ||
| download_dir | No | ||
| confirm | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- The `setup` tool handler function. Decorated with @mcp.tool and @confirm_required('setup'), it accepts prowlarr_url, prowlarr_api_key, qb_url, qb_username, qb_password, and optional download_dir, constructs a Config object, calls _cfg.write() to persist it to disk as TOML, and returns a success dict with the config_path.
@mcp.tool(description="Write the TOML config file (first-run setup). mutates: true") @confirm_required("setup") def setup( prowlarr_url: str, prowlarr_api_key: str, qb_url: str, qb_username: str, qb_password: str, download_dir: str = "", confirm: bool = False, ) -> dict[str, Any]: cfg = _cfg.Config() cfg.prowlarr.base_url = prowlarr_url cfg.prowlarr.api_key = prowlarr_api_key cfg.qbittorrent.base_url = qb_url cfg.qbittorrent.username = qb_username cfg.qbittorrent.password = qb_password cfg.qbittorrent.download_dir = download_dir path = _cfg.write(cfg) return {"ok": True, "config_path": str(path)} - The schema/dataclass definitions (Config, ProwlarrCfg, QbCfg, PolicyCfg) used by the setup tool to construct configuration before writing. ProwlarrCfg holds base_url + api_key, QbCfg holds base_url + username + password + download_dir, and Config composes them together.
@dataclass class ProwlarrCfg: base_url: str = "http://localhost:9696" api_key: str = "" @dataclass class QbCfg: base_url: str = "http://localhost:8080" username: str = "admin" password: str = "" download_dir: str = "" @dataclass class PolicyCfg: blocklist: list[str] = field(default_factory=lambda: ["fitgirl"]) allow_indexers: list[int] = field(default_factory=list) max_size_gb: float = 50.0 freeleech_only: bool = False min_seeders: int = 1 @dataclass class Config: prowlarr: ProwlarrCfg = field(default_factory=ProwlarrCfg) qbittorrent: QbCfg = field(default_factory=QbCfg) policy: PolicyCfg = field(default_factory=PolicyCfg) - src/lutris_source_mcp/tools/diagnostics.py:56-56 (registration)The MCP tool registration via the @mcp.tool decorator on line 56, which registers 'setup' as an MCP tool with description 'Write the TOML config file (first-run setup). mutates: true'. The registration is triggered when the diagnostics module is imported in server.py's _register().
@mcp.tool(description="Write the TOML config file (first-run setup). mutates: true") - The `write()` helper function called by the setup tool to persist the Config to disk as a TOML file. Creates parent directories, writes the config sections (prowlarr, qbittorrent, policy) in TOML format, sets file permissions to 0o600, and returns the Path.
def write(cfg: Config, path: Path | None = None) -> Path: """Write a config TOML to disk (used by the `setup` tool on first run).""" target = path or CONFIG_PATH target.parent.mkdir(parents=True, exist_ok=True) body = ( "[prowlarr]\n" f'base_url = "{cfg.prowlarr.base_url}"\n' f'api_key = "{cfg.prowlarr.api_key}"\n' "\n[qbittorrent]\n" f'base_url = "{cfg.qbittorrent.base_url}"\n' f'username = "{cfg.qbittorrent.username}"\n' f'password = "{cfg.qbittorrent.password}"\n' f'download_dir = "{cfg.qbittorrent.download_dir}"\n' "\n[policy]\n" f"blocklist = {cfg.policy.blocklist!r}\n" f"allow_indexers = {cfg.policy.allow_indexers!r}\n" f"max_size_gb = {cfg.policy.max_size_gb}\n" f"freeleech_only = {str(cfg.policy.freeleech_only).lower()}\n" f"min_seeders = {cfg.policy.min_seeders}\n" ) target.write_text(body, encoding="utf-8") target.chmod(0o600) return target - The `confirm_required` decorator used on the setup tool. When called without confirm=True, it returns a Preview response instead of executing the handler, acting as a confirmation gate for destructive operations (the setup tool has mutates: true).
def confirm_required(action: str) -> Callable[[F], F]: def decorator(fn: F) -> F: @wraps(fn) def wrapper(*args: Any, **kwargs: Any) -> Any: if not kwargs.get("confirm", False): target = ( kwargs.get("query") or kwargs.get("infohash") or kwargs.get("target") or "<unknown>" ) return Preview(action=action, target=str(target), would_do=f"{action} on {target}") return fn(*args, **kwargs) return wrapper # type: ignore[return-value] return decorator