romarr-mcp: indexer and download-client write tools
NixOS Configuration CI / Nix Flake Check (pull_request) Successful in 20s

This commit is contained in:
Hermes committed 2026-11-05 23:30:32 +01:00
1 parent 2393f594b3
commit 34b6e36787
1 file changed
+110
+110
View File
@@ -226,6 +226,58 @@ def t_manual_scan(a):
return get("/api/v1/manualimport", path=a["path"], platform=a.get("platform")) return get("/api/v1/manualimport", path=a["path"], platform=a.get("platform"))
def t_indexer_schema(_):
return get("/api/v1/indexer/schema")
def _secret_from(body, field, literal, env_name):
"""Set a credential field, optionally resolved from the server's own env.
Passing a key by *reference* means the value never enters the transcript: the
gateway already exposes Prowlarr's and SABnzbd's keys to this process, so the
caller names the variable instead of quoting the secret.
"""
if env_name:
value = os.environ.get(env_name, "")
if not value:
raise ToolError(f"{env_name} is not set in this server's environment")
body[field] = value
elif literal:
body[field] = literal
def t_indexer_save(a):
kind = (a.get("type") or "").strip()
if not kind:
raise ToolError("type is required (see indexer_schema), e.g. 'prowlarr'")
body = dict(a.get("extra") or {})
body["type"] = kind
for field in ("name", "url", "enable", "id"):
if a.get(field) not in (None, ""):
body[field] = a[field]
_secret_from(body, "api_key", a.get("api_key"), a.get("api_key_env"))
return post("/api/v1/indexer", body)
def t_client_schema(_):
return get("/api/v1/downloadclient/schema")
def t_client_save(a):
kind = (a.get("type") or "").strip()
if not kind:
raise ToolError("type is required (see download_client_schema), e.g. 'qbittorrent'")
body = dict(a.get("extra") or {})
body["type"] = kind
for field in ("name", "host", "port", "use_ssl", "url_base", "username",
"category", "id"):
if a.get(field) not in (None, ""):
body[field] = a[field]
_secret_from(body, "password", a.get("password"), a.get("password_env"))
_secret_from(body, "api_key", a.get("api_key"), a.get("api_key_env"))
return post("/api/v1/downloadclient", body)
def _obj(props, required=None): def _obj(props, required=None):
return { return {
"type": "object", "type": "object",
@@ -410,6 +462,64 @@ TOOLS = [
"write": False, "write": False,
"fn": t_manual_scan, "fn": t_manual_scan,
}, },
{
"name": "indexer_schema",
"description": "Every indexer type ROMarr supports, with the fields each one needs — read this before indexer_save.",
"schema": _obj({}),
"write": False,
"fn": t_indexer_schema,
},
{
"name": "indexer_save",
"description": "Create or update an indexer. Prefer api_key_env over api_key: the server resolves the variable from its own environment, so the secret never appears in the call.",
"schema": _obj(
{
"type": {"type": "string", "description": "Indexer type, e.g. 'prowlarr', 'torznab', 'newznab'."},
"name": {"type": "string"},
"url": {"type": "string", "description": "Base URL, e.g. http://127.0.0.1:9696 for Prowlarr."},
"api_key": {"type": "string", "description": "Inline key. Avoid: prefer api_key_env."},
"api_key_env": {"type": "string", "description": "Name of an env var holding the key, e.g. 'PROWLARR_API_KEY'."},
"enable": {"type": "boolean"},
"id": {"type": "string", "description": "Existing indexer id to update."},
"extra": {"type": "object", "description": "Any other fields the type needs, verbatim."},
},
["type"],
),
"write": True,
"fn": t_indexer_save,
},
{
"name": "download_client_schema",
"description": "Every download client type ROMarr supports, with its fields and default port.",
"schema": _obj({}),
"write": False,
"fn": t_client_schema,
},
{
"name": "download_client_save",
"description": "Create or update a torrent/usenet download client. Credentials may be passed by reference (password_env / api_key_env) instead of inline.",
"schema": _obj(
{
"type": {"type": "string", "description": "Client type, e.g. 'qbittorrent', 'sabnzbd', 'transmission'."},
"name": {"type": "string"},
"host": {"type": "string"},
"port": {"type": "integer"},
"use_ssl": {"type": "boolean"},
"url_base": {"type": "string"},
"username": {"type": "string"},
"password": {"type": "string", "description": "Inline. Avoid: prefer password_env."},
"password_env": {"type": "string"},
"api_key": {"type": "string", "description": "Inline. Avoid: prefer api_key_env."},
"api_key_env": {"type": "string", "description": "e.g. 'SABNZBD_API_KEY'."},
"category": {"type": "string"},
"id": {"type": "string", "description": "Existing client id to update."},
"extra": {"type": "object", "description": "Any other fields the type needs, verbatim."},
},
["type"],
),
"write": True,
"fn": t_client_save,
},
] ]
BY_NAME = {t["name"]: t for t in TOOLS} BY_NAME = {t["name"]: t for t in TOOLS}