mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
- setup.py: elicit_and_configure now accepts Context | None; returns False immediately when ctx is None instead of crashing with AttributeError - settings.py: added CredentialsNotConfiguredError try/except guard around make_graphql_request calls in all 8 previously-unguarded actions (update_temperature, update_time, configure_ups, update_api, connect_sign_in, connect_sign_out, setup_remote_access, enable_dynamic_remote_access) - docker.py: added guards to all 20 previously-unguarded make_graphql_request calls (details, logs, networks, network_details, port_conflicts, check_updates, restart, update_all, all 11 organizer mutations, and single-container fallback) - notifications.py: added guards to all 11 previously-unguarded calls (list, warnings, create, archive/unread, delete, delete_archived, archive_all, archive_many, create_unique, unarchive_many, unarchive_all, recalculate)
90 lines
2.8 KiB
Python
90 lines
2.8 KiB
Python
"""Interactive credential setup via MCP elicitation.
|
|
|
|
When UNRAID_API_URL or UNRAID_API_KEY are absent, tools call
|
|
`elicit_and_configure(ctx)` to collect them from the user and persist
|
|
them to .env in the server root directory.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from fastmcp import Context
|
|
|
|
from ..config.logging import logger
|
|
from ..config.settings import PROJECT_ROOT, apply_runtime_config
|
|
|
|
|
|
@dataclass
|
|
class _UnraidCredentials:
|
|
api_url: str
|
|
api_key: str
|
|
|
|
|
|
async def elicit_and_configure(ctx: Context | None) -> bool:
|
|
"""Prompt the user for Unraid credentials via MCP elicitation.
|
|
|
|
Writes accepted credentials to .env in PROJECT_ROOT and applies them
|
|
to the running process via apply_runtime_config().
|
|
|
|
Args:
|
|
ctx: The MCP context for elicitation. If None, returns False immediately
|
|
(no context available to prompt the user).
|
|
|
|
Returns:
|
|
True if credentials were accepted and applied, False if declined/cancelled.
|
|
"""
|
|
if ctx is None:
|
|
logger.warning(
|
|
"Cannot elicit credentials: no MCP context available. "
|
|
"Run unraid_health action=setup to configure credentials."
|
|
)
|
|
return False
|
|
|
|
result = await ctx.elicit(
|
|
message=(
|
|
"Unraid MCP needs your Unraid server credentials to connect.\n\n"
|
|
"• **API URL**: Your Unraid GraphQL endpoint "
|
|
"(e.g. `https://10-1-0-2.xxx.myunraid.net:31337`)\n"
|
|
"• **API Key**: Found in Unraid → Settings → Management Access → API Keys"
|
|
),
|
|
response_type=_UnraidCredentials,
|
|
)
|
|
|
|
if result.action != "accept":
|
|
logger.warning("Credential elicitation %s — server remains unconfigured.", result.action)
|
|
return False
|
|
|
|
api_url: str = result.data.api_url.rstrip("/")
|
|
api_key: str = result.data.api_key.strip()
|
|
|
|
_write_env(api_url, api_key)
|
|
apply_runtime_config(api_url, api_key)
|
|
|
|
logger.info("Credentials configured via elicitation and persisted to .env.")
|
|
return True
|
|
|
|
|
|
def _write_env(api_url: str, api_key: str) -> None:
|
|
"""Write or update .env in PROJECT_ROOT with credential values.
|
|
|
|
Preserves any existing lines that are not UNRAID_API_URL or UNRAID_API_KEY.
|
|
"""
|
|
env_path = PROJECT_ROOT / ".env"
|
|
existing_lines: list[str] = []
|
|
|
|
if env_path.exists():
|
|
for line in env_path.read_text().splitlines():
|
|
stripped = line.strip()
|
|
if stripped.startswith("UNRAID_API_URL=") or stripped.startswith("UNRAID_API_KEY="):
|
|
continue # Will be replaced below
|
|
existing_lines.append(line)
|
|
|
|
new_lines = [
|
|
f"UNRAID_API_URL={api_url}",
|
|
f"UNRAID_API_KEY={api_key}",
|
|
*existing_lines,
|
|
]
|
|
env_path.write_text("\n".join(new_lines) + "\n")
|
|
logger.debug("Wrote credentials to %s", env_path)
|