mirror of
https://github.com/jmagar/unraid-mcp.git
synced 2026-03-23 12:39:24 -07:00
fix: add confirm guard for update_ssh, fix avatar dropped without username/email
- info.py: add DESTRUCTIVE_ACTIONS set with update_ssh, add confirm param to unraid_info signature, add destructive guard before mutation handlers - settings.py: build user_info dict unconditionally so avatar is included even when username/email are absent; only attach userInfo when non-empty Resolves review threads PRRT_kwDOO6Hdxs50FgO0 PRRT_kwDOO6Hdxs50FgPC
This commit is contained in:
@@ -171,6 +171,7 @@ MUTATIONS: dict[str, str] = {
|
|||||||
""",
|
""",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DESTRUCTIVE_ACTIONS = {"update_ssh"}
|
||||||
ALL_ACTIONS = set(QUERIES) | set(MUTATIONS)
|
ALL_ACTIONS = set(QUERIES) | set(MUTATIONS)
|
||||||
|
|
||||||
INFO_ACTIONS = Literal[
|
INFO_ACTIONS = Literal[
|
||||||
@@ -326,6 +327,7 @@ def register_info_tool(mcp: FastMCP) -> None:
|
|||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def unraid_info(
|
async def unraid_info(
|
||||||
action: INFO_ACTIONS,
|
action: INFO_ACTIONS,
|
||||||
|
confirm: bool = False,
|
||||||
device_id: str | None = None,
|
device_id: str | None = None,
|
||||||
server_name: str | None = None,
|
server_name: str | None = None,
|
||||||
server_comment: str | None = None,
|
server_comment: str | None = None,
|
||||||
@@ -361,6 +363,9 @@ def register_info_tool(mcp: FastMCP) -> None:
|
|||||||
if action not in ALL_ACTIONS:
|
if action not in ALL_ACTIONS:
|
||||||
raise ToolError(f"Invalid action '{action}'. Must be one of: {sorted(ALL_ACTIONS)}")
|
raise ToolError(f"Invalid action '{action}'. Must be one of: {sorted(ALL_ACTIONS)}")
|
||||||
|
|
||||||
|
if action in DESTRUCTIVE_ACTIONS and not confirm:
|
||||||
|
raise ToolError(f"Action '{action}' is destructive. Set confirm=True to proceed.")
|
||||||
|
|
||||||
if action == "ups_device" and not device_id:
|
if action == "ups_device" and not device_id:
|
||||||
raise ToolError("device_id is required for ups_device action")
|
raise ToolError("device_id is required for ups_device action")
|
||||||
|
|
||||||
|
|||||||
@@ -142,11 +142,17 @@ def register_settings_tool(mcp: FastMCP) -> None:
|
|||||||
|
|
||||||
if action == "update_temperature":
|
if action == "update_temperature":
|
||||||
if temperature_config is None:
|
if temperature_config is None:
|
||||||
raise ToolError("temperature_config is required for 'update_temperature' action")
|
raise ToolError(
|
||||||
|
"temperature_config is required for 'update_temperature' action"
|
||||||
|
)
|
||||||
data = await make_graphql_request(
|
data = await make_graphql_request(
|
||||||
MUTATIONS["update_temperature"], {"input": temperature_config}
|
MUTATIONS["update_temperature"], {"input": temperature_config}
|
||||||
)
|
)
|
||||||
return {"success": True, "action": "update_temperature", "result": data.get("updateTemperatureConfig")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "update_temperature",
|
||||||
|
"result": data.get("updateTemperatureConfig"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "update_time":
|
if action == "update_time":
|
||||||
time_input: dict[str, Any] = {}
|
time_input: dict[str, Any] = {}
|
||||||
@@ -163,13 +169,23 @@ def register_settings_tool(mcp: FastMCP) -> None:
|
|||||||
"update_time requires at least one of: time_zone, use_ntp, ntp_servers, manual_datetime"
|
"update_time requires at least one of: time_zone, use_ntp, ntp_servers, manual_datetime"
|
||||||
)
|
)
|
||||||
data = await make_graphql_request(MUTATIONS["update_time"], {"input": time_input})
|
data = await make_graphql_request(MUTATIONS["update_time"], {"input": time_input})
|
||||||
return {"success": True, "action": "update_time", "data": data.get("updateSystemTime")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "update_time",
|
||||||
|
"data": data.get("updateSystemTime"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "configure_ups":
|
if action == "configure_ups":
|
||||||
if ups_config is None:
|
if ups_config is None:
|
||||||
raise ToolError("ups_config is required for 'configure_ups' action")
|
raise ToolError("ups_config is required for 'configure_ups' action")
|
||||||
data = await make_graphql_request(MUTATIONS["configure_ups"], {"config": ups_config})
|
data = await make_graphql_request(
|
||||||
return {"success": True, "action": "configure_ups", "result": data.get("configureUps")}
|
MUTATIONS["configure_ups"], {"config": ups_config}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "configure_ups",
|
||||||
|
"result": data.get("configureUps"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "update_api":
|
if action == "update_api":
|
||||||
api_input: dict[str, Any] = {}
|
api_input: dict[str, Any] = {}
|
||||||
@@ -184,29 +200,41 @@ def register_settings_tool(mcp: FastMCP) -> None:
|
|||||||
"update_api requires at least one of: access_type, forward_type, port"
|
"update_api requires at least one of: access_type, forward_type, port"
|
||||||
)
|
)
|
||||||
data = await make_graphql_request(MUTATIONS["update_api"], {"input": api_input})
|
data = await make_graphql_request(MUTATIONS["update_api"], {"input": api_input})
|
||||||
return {"success": True, "action": "update_api", "data": data.get("updateApiSettings")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "update_api",
|
||||||
|
"data": data.get("updateApiSettings"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "connect_sign_in":
|
if action == "connect_sign_in":
|
||||||
if not api_key:
|
if not api_key:
|
||||||
raise ToolError("api_key is required for 'connect_sign_in' action")
|
raise ToolError("api_key is required for 'connect_sign_in' action")
|
||||||
sign_in_input: dict[str, Any] = {"apiKey": api_key}
|
sign_in_input: dict[str, Any] = {"apiKey": api_key}
|
||||||
if username or email:
|
user_info: dict[str, Any] = {}
|
||||||
user_info: dict[str, Any] = {}
|
if username:
|
||||||
if username:
|
user_info["preferred_username"] = username
|
||||||
user_info["preferred_username"] = username
|
if email:
|
||||||
if email:
|
user_info["email"] = email
|
||||||
user_info["email"] = email
|
if avatar:
|
||||||
if avatar:
|
user_info["avatar"] = avatar
|
||||||
user_info["avatar"] = avatar
|
if user_info:
|
||||||
sign_in_input["userInfo"] = user_info
|
sign_in_input["userInfo"] = user_info
|
||||||
data = await make_graphql_request(
|
data = await make_graphql_request(
|
||||||
MUTATIONS["connect_sign_in"], {"input": sign_in_input}
|
MUTATIONS["connect_sign_in"], {"input": sign_in_input}
|
||||||
)
|
)
|
||||||
return {"success": True, "action": "connect_sign_in", "result": data.get("connectSignIn")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "connect_sign_in",
|
||||||
|
"result": data.get("connectSignIn"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "connect_sign_out":
|
if action == "connect_sign_out":
|
||||||
data = await make_graphql_request(MUTATIONS["connect_sign_out"])
|
data = await make_graphql_request(MUTATIONS["connect_sign_out"])
|
||||||
return {"success": True, "action": "connect_sign_out", "result": data.get("connectSignOut")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "connect_sign_out",
|
||||||
|
"result": data.get("connectSignOut"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "setup_remote_access":
|
if action == "setup_remote_access":
|
||||||
if not access_type:
|
if not access_type:
|
||||||
@@ -219,7 +247,11 @@ def register_settings_tool(mcp: FastMCP) -> None:
|
|||||||
data = await make_graphql_request(
|
data = await make_graphql_request(
|
||||||
MUTATIONS["setup_remote_access"], {"input": remote_input}
|
MUTATIONS["setup_remote_access"], {"input": remote_input}
|
||||||
)
|
)
|
||||||
return {"success": True, "action": "setup_remote_access", "result": data.get("setupRemoteAccess")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "setup_remote_access",
|
||||||
|
"result": data.get("setupRemoteAccess"),
|
||||||
|
}
|
||||||
|
|
||||||
if action == "enable_dynamic_remote_access":
|
if action == "enable_dynamic_remote_access":
|
||||||
if not access_url_type:
|
if not access_url_type:
|
||||||
@@ -241,7 +273,11 @@ def register_settings_tool(mcp: FastMCP) -> None:
|
|||||||
MUTATIONS["enable_dynamic_remote_access"],
|
MUTATIONS["enable_dynamic_remote_access"],
|
||||||
{"input": {"url": url_input, "enabled": dynamic_enabled}},
|
{"input": {"url": url_input, "enabled": dynamic_enabled}},
|
||||||
)
|
)
|
||||||
return {"success": True, "action": "enable_dynamic_remote_access", "result": data.get("enableDynamicRemoteAccess")}
|
return {
|
||||||
|
"success": True,
|
||||||
|
"action": "enable_dynamic_remote_access",
|
||||||
|
"result": data.get("enableDynamicRemoteAccess"),
|
||||||
|
}
|
||||||
|
|
||||||
raise ToolError(f"Unhandled action '{action}' — this is a bug")
|
raise ToolError(f"Unhandled action '{action}' — this is a bug")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user