From 9be46750b83d7288d97be689c6f970582b9b056d Mon Sep 17 00:00:00 2001 From: Jacob Magar Date: Sat, 14 Mar 2026 04:02:15 -0400 Subject: [PATCH] feat(elicitation): add setup action to unraid_health --- tests/test_health.py | 33 +++++++++++++++++++++++++++++++++ unraid_mcp/tools/health.py | 26 ++++++++++++++++++++------ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/tests/test_health.py b/tests/test_health.py index 8b58732..fdf9a67 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -194,3 +194,36 @@ class TestSafeDisplayUrl: # raises ValueError: Invalid IPv6 URL — this triggers the except branch. result = safe_display_url("https://[invalid") assert result == "" + + +@pytest.mark.asyncio +async def test_health_setup_action_calls_elicitation() -> None: + """setup action triggers elicit_and_configure and returns success message.""" + from unittest.mock import AsyncMock, MagicMock + + tool_fn = _make_tool() + + with patch( + "unraid_mcp.tools.health.elicit_and_configure", new=AsyncMock(return_value=True) + ) as mock_elicit: + result = await tool_fn(action="setup", ctx=MagicMock()) + + assert mock_elicit.called + assert "configured" in result.lower() or "success" in result.lower() + + +@pytest.mark.asyncio +async def test_health_setup_action_returns_declined_message() -> None: + """setup action with declined elicitation returns appropriate message.""" + from unittest.mock import AsyncMock, MagicMock + + tool_fn = _make_tool() + + with patch("unraid_mcp.tools.health.elicit_and_configure", new=AsyncMock(return_value=False)): + result = await tool_fn(action="setup", ctx=MagicMock()) + + assert ( + "not configured" in result.lower() + or "declined" in result.lower() + or "cancel" in result.lower() + ) diff --git a/unraid_mcp/tools/health.py b/unraid_mcp/tools/health.py index 4c93292..5b03aca 100644 --- a/unraid_mcp/tools/health.py +++ b/unraid_mcp/tools/health.py @@ -1,14 +1,14 @@ """Health monitoring and diagnostics. -Provides the `unraid_health` tool with 3 actions for system health checks, -connection testing, and subscription diagnostics. +Provides the `unraid_health` tool with 4 actions for system health checks, +connection testing, subscription diagnostics, and credential setup. """ import datetime import time from typing import Any, Literal, get_args -from fastmcp import FastMCP +from fastmcp import Context, FastMCP from ..config.logging import logger from ..config.settings import ( @@ -20,13 +20,14 @@ from ..config.settings import ( ) from ..core.client import make_graphql_request from ..core.exceptions import ToolError, tool_error_handler +from ..core.setup import elicit_and_configure from ..core.utils import safe_display_url from ..subscriptions.utils import _analyze_subscription_status -ALL_ACTIONS = {"check", "test_connection", "diagnose"} +ALL_ACTIONS = {"check", "test_connection", "diagnose", "setup"} -HEALTH_ACTIONS = Literal["check", "test_connection", "diagnose"] +HEALTH_ACTIONS = Literal["check", "test_connection", "diagnose", "setup"] if set(get_args(HEALTH_ACTIONS)) != ALL_ACTIONS: _missing = ALL_ACTIONS - set(get_args(HEALTH_ACTIONS)) @@ -57,10 +58,12 @@ def register_health_tool(mcp: FastMCP) -> None: @mcp.tool() async def unraid_health( action: HEALTH_ACTIONS, - ) -> dict[str, Any]: + ctx: Context | None = None, + ) -> dict[str, Any] | str: """Monitor Unraid MCP server and system health. Actions: + setup - Configure Unraid credentials via interactive elicitation check - Comprehensive health check (API latency, array, notifications, Docker) test_connection - Quick connectivity test (just checks { online }) diagnose - Subscription system diagnostics @@ -68,6 +71,17 @@ def register_health_tool(mcp: FastMCP) -> None: if action not in ALL_ACTIONS: raise ToolError(f"Invalid action '{action}'. Must be one of: {sorted(ALL_ACTIONS)}") + if action == "setup": + configured = await elicit_and_configure(ctx) + if configured: + return ( + "✅ Credentials configured successfully. You can now use all Unraid MCP tools." + ) + return ( + "⚠️ Credentials not configured. " + "Run `unraid_health action=setup` again to provide credentials." + ) + with tool_error_handler("health", action, logger): logger.info(f"Executing unraid_health action={action}")