4 Commits
0.0.3 ... 0.0.5

Author SHA1 Message Date
202354bbc1 release: version 0.0.5 🚀
All checks were successful
Build Docker image / Create Release (push) Successful in 7s
Build Docker image / deploy (push) Successful in 1m7s
2026-02-28 16:49:10 +01:00
0d876564cc fix: runtime error due to broken import 2026-02-28 16:49:06 +01:00
df2db88e0e release: version 0.0.4 🚀
All checks were successful
Build Docker image / Create Release (push) Successful in 6s
Build Docker image / deploy (push) Successful in 54s
2026-02-28 16:45:22 +01:00
88983c6736 fix: even more changes to accommodate older GraphQL schema 2026-02-28 16:45:19 +01:00
20 changed files with 44 additions and 220 deletions

View File

@@ -84,17 +84,16 @@ docker compose down
- **Health Monitoring**: Comprehensive health check tool for system monitoring - **Health Monitoring**: Comprehensive health check tool for system monitoring
- **Real-time Subscriptions**: WebSocket-based live data streaming - **Real-time Subscriptions**: WebSocket-based live data streaming
### Tool Categories (10 Tools, 76 Actions) ### Tool Categories (9 Tools, 70 Actions)
1. **`unraid_info`** (19 actions): overview, array, network, registration, connect, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config 1. **`unraid_info`** (19 actions): overview, array, network, registration, connect, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config
2. **`unraid_array`** (5 actions): parity_start, parity_pause, parity_resume, parity_cancel, parity_status 2. **`unraid_storage`** (6 actions): shares, disks, disk_details, log_files, logs
3. **`unraid_storage`** (6 actions): shares, disks, disk_details, unassigned, log_files, logs 3. **`unraid_docker`** (15 actions): list, details, start, stop, restart, pause, unpause, remove, update, update_all, logs, networks, network_details, port_conflicts, check_updates
4. **`unraid_docker`** (15 actions): list, details, start, stop, restart, pause, unpause, remove, update, update_all, logs, networks, network_details, port_conflicts, check_updates 4. **`unraid_vm`** (9 actions): list, details, start, stop, pause, resume, force_stop, reboot, reset
5. **`unraid_vm`** (9 actions): list, details, start, stop, pause, resume, force_stop, reboot, reset 5. **`unraid_notifications`** (9 actions): overview, list, warnings, create, archive, unread, delete, delete_archived, archive_all
6. **`unraid_notifications`** (9 actions): overview, list, warnings, create, archive, unread, delete, delete_archived, archive_all 6. **`unraid_rclone`** (4 actions): list_remotes, config_form, create_remote, delete_remote
7. **`unraid_rclone`** (4 actions): list_remotes, config_form, create_remote, delete_remote 7. **`unraid_users`** (1 action): me
8. **`unraid_users`** (1 action): me 8. **`unraid_keys`** (5 actions): list, get, create, update, delete
9. **`unraid_keys`** (5 actions): list, get, create, update, delete 9. **`unraid_health`** (3 actions): check, test_connection, diagnose
10. **`unraid_health`** (3 actions): check, test_connection, diagnose
### Environment Variable Hierarchy ### Environment Variable Hierarchy
The server loads environment variables from multiple locations in order: The server loads environment variables from multiple locations in order:

View File

@@ -5,10 +5,33 @@ Changelog
(unreleased) (unreleased)
------------ ------------
Fix
~~~
- Runtime error due to broken import. [Simon Diesenreiter]
0.0.4 (2026-02-28)
------------------
Fix
~~~
- Even more changes to accommodate older GraphQL schema. [Simon
Diesenreiter]
Other
~~~~~
0.0.3 (2026-02-28)
------------------
Fix Fix
~~~ ~~~
- Adapt for supported GraphQL schema on 6.12.13. [Simon Diesenreiter] - Adapt for supported GraphQL schema on 6.12.13. [Simon Diesenreiter]
Other
~~~~~
0.0.2 (2026-02-21) 0.0.2 (2026-02-21)
------------------ ------------------

View File

@@ -218,13 +218,12 @@ UNRAID_VERIFY_SSL=true # true, false, or path to CA bundle
Each tool uses a consolidated `action` parameter to expose multiple operations, reducing context window usage. Destructive actions require `confirm=True`. Each tool uses a consolidated `action` parameter to expose multiple operations, reducing context window usage. Destructive actions require `confirm=True`.
### Tool Categories (10 Tools, 76 Actions) ### Tool Categories (9 Tools, 70 Actions)
| Tool | Actions | Description | | Tool | Actions | Description |
|------|---------|-------------| |------|---------|-------------|
| **`unraid_info`** | 19 | overview, array, network, registration, connect, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config | | **`unraid_info`** | 19 | overview, array, network, registration, connect, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config |
| **`unraid_array`** | 5 | parity_start, parity_pause, parity_resume, parity_cancel, parity_status | | **`unraid_storage`** | 6 | shares, disks, disk_details, log_files, logs |
| **`unraid_storage`** | 6 | shares, disks, disk_details, unassigned, log_files, logs |
| **`unraid_docker`** | 15 | list, details, start, stop, restart, pause, unpause, remove, update, update_all, logs, networks, network_details, port_conflicts, check_updates | | **`unraid_docker`** | 15 | list, details, start, stop, restart, pause, unpause, remove, update, update_all, logs, networks, network_details, port_conflicts, check_updates |
| **`unraid_vm`** | 9 | list, details, start, stop, pause, resume, force_stop, reboot, reset | | **`unraid_vm`** | 9 | list, details, start, stop, pause, resume, force_stop, reboot, reset |
| **`unraid_notifications`** | 9 | overview, list, warnings, create, archive, unread, delete, delete_archived, archive_all | | **`unraid_notifications`** | 9 | overview, list, warnings, create, archive, unread, delete, delete_archived, archive_all |
@@ -242,14 +241,13 @@ Each tool uses a consolidated `action` parameter to expose multiple operations,
## 💬 Custom Slash Commands ## 💬 Custom Slash Commands
The project includes **10 custom slash commands** in `commands/` for quick access to Unraid operations: The project includes **9 custom slash commands** in `commands/` for quick access to Unraid operations:
### Available Commands ### Available Commands
| Command | Actions | Quick Access | | Command | Actions | Quick Access |
|---------|---------|--------------| |---------|---------|--------------|
| `/info` | 19 | System information, metrics, configuration | | `/info` | 19 | System information, metrics, configuration |
| `/array` | 5 | Parity check management |
| `/storage` | 6 | Shares, disks, logs | | `/storage` | 6 | Shares, disks, logs |
| `/docker` | 15 | Container management and monitoring | | `/docker` | 15 | Container management and monitoring |
| `/vm` | 9 | Virtual machine lifecycle | | `/vm` | 9 | Virtual machine lifecycle |

View File

@@ -1 +1 @@
0.0.3 0.0.5

View File

@@ -1,30 +0,0 @@
---
description: Manage Unraid array parity checks
argument-hint: [action] [correct=true/false]
---
Execute the `unraid_array` MCP tool with action: `$1`
## Available Actions (5)
**Parity Check Operations:**
- `parity_start` - Start parity check/sync (optional: correct=true to fix errors)
- `parity_pause` - Pause running parity operation
- `parity_resume` - Resume paused parity operation
- `parity_cancel` - Cancel running parity operation
- `parity_status` - Get current parity check status
## Example Usage
```
/array parity_start
/array parity_start correct=true
/array parity_pause
/array parity_resume
/array parity_cancel
/array parity_status
```
**Note:** Use `correct=true` with `parity_start` to automatically fix any parity errors found during the check.
Use the tool to execute the requested parity operation and report the results.

View File

@@ -18,7 +18,6 @@ Execute the `unraid_info` MCP tool with action: `$1`
**Network & Registration:** **Network & Registration:**
- `network` - Network configuration and interfaces - `network` - Network configuration and interfaces
- `registration` - Registration status and license info - `registration` - Registration status and license info
- `connect` - Connect service configuration
- `online` - Online status check - `online` - Online status check
**Configuration:** **Configuration:**

View File

@@ -11,7 +11,6 @@ Execute the `unraid_storage` MCP tool with action: `$1`
- `shares` - List all user shares with sizes and allocation - `shares` - List all user shares with sizes and allocation
- `disks` - List all disks in the array - `disks` - List all disks in the array
- `disk_details` - Get detailed info for a specific disk (requires disk identifier) - `disk_details` - Get detailed info for a specific disk (requires disk identifier)
- `unassigned` - List unassigned devices
**Logs:** **Logs:**
- `log_files` - List available system log files - `log_files` - List available system log files
@@ -23,7 +22,6 @@ Execute the `unraid_storage` MCP tool with action: `$1`
/unraid-storage shares /unraid-storage shares
/unraid-storage disks /unraid-storage disks
/unraid-storage disk_details disk1 /unraid-storage disk_details disk1
/unraid-storage unassigned
/unraid-storage log_files /unraid-storage log_files
/unraid-storage logs /var/log/syslog /unraid-storage logs /var/log/syslog
``` ```

View File

@@ -355,7 +355,6 @@ The project's documentation explicitly compares SSH vs API capabilities:
| Network config | Y | Y | Y | Y | N | N | N | | Network config | Y | Y | Y | Y | N | N | N |
| Network bandwidth | N | Y | N | Y | N | N | N | | Network bandwidth | N | Y | N | Y | N | N | N |
| Registration/license info | Y | Y | Y | N | N | N | N | | Registration/license info | Y | Y | Y | N | N | N | N |
| Connect settings | Y | Y | Y | N | N | N | N |
| Unraid variables | Y | Y | Y | N | N | N | N | | Unraid variables | Y | Y | Y | N | N | N | N |
| System services status | N | Y | Y | N | N | N | N | | System services status | N | Y | Y | N | N | N | N |
| Flash drive info | N | Y | Y | N | N | Y | N | | Flash drive info | N | Y | Y | N | N | Y | N |

View File

@@ -665,7 +665,6 @@ type Query {
servers: [Server!]! servers: [Server!]!
services: [Service!]! services: [Service!]!
shares: [Share] shares: [Share]
unassignedDevices: [UnassignedDevice]
me: Me me: Me
user(id: ID!): User user(id: ID!): User
users(input: usersInput): [User!]! users(input: usersInput): [User!]!
@@ -743,7 +742,6 @@ type Subscription {
service(name: String!): [Service!] service(name: String!): [Service!]
share(id: ID!): Share! share(id: ID!): Share!
shares: [Share!] shares: [Share!]
unassignedDevices: [UnassignedDevice!]
me: Me me: Me
user(id: ID!): User! user(id: ID!): User!
users: [User]! users: [User]!

View File

@@ -698,7 +698,8 @@ type Info implements Node {
} }
type MetricsCpu { type MetricsCpu {
used: Float percentTotal: Float!
cpus: [CPULoad!]!
} }
type MetricsMemory { type MetricsMemory {
@@ -715,7 +716,6 @@ type Metrics implements Node {
type Service implements Node { type Service implements Node {
id: PrefixedID! id: PrefixedID!
name: String name: String
state: String
online: Boolean online: Boolean
uptime: Uptime uptime: Uptime
version: String version: String
@@ -751,12 +751,6 @@ type Registration implements Node {
updateExpiration: String updateExpiration: String
} }
type ConnectSettings {
status: String
sandbox: Boolean
flashGuid: String
}
type Owner { type Owner {
username: String! username: String!
avatar: String! avatar: String!
@@ -1325,9 +1319,6 @@ type Query {
# Network (used by MCP tool) # Network (used by MCP tool)
network: Network network: Network
# Connect (used by MCP tool)
connect: ConnectSettings
} }
# ============================================================================ # ============================================================================

View File

@@ -783,17 +783,6 @@ class TestStorageToolRequests:
with pytest.raises(ToolError, match="log_path must start with"): with pytest.raises(ToolError, match="log_path must start with"):
await tool(action="logs", log_path="/etc/shadow") await tool(action="logs", log_path="/etc/shadow")
@respx.mock
async def test_unassigned_sends_correct_query(self) -> None:
route = respx.post(API_URL).mock(
return_value=_graphql_response({"unassignedDevices": []})
)
tool = self._get_tool()
result = await tool(action="unassigned")
body = _extract_request_body(route.calls.last.request)
assert "GetUnassignedDevices" in body["query"]
assert "devices" in result
# =========================================================================== # ===========================================================================
# Section 10: Notifications tool request construction # Section 10: Notifications tool request construction

View File

@@ -237,12 +237,6 @@ class TestStorageQueries:
errors = _validate_operation(schema, QUERIES["disk_details"]) errors = _validate_operation(schema, QUERIES["disk_details"])
assert not errors, f"disk_details query validation failed: {errors}" assert not errors, f"disk_details query validation failed: {errors}"
def test_unassigned_query(self, schema: GraphQLSchema) -> None:
from unraid_mcp.tools.storage import QUERIES
errors = _validate_operation(schema, QUERIES["unassigned"])
assert not errors, f"unassigned query validation failed: {errors}"
def test_log_files_query(self, schema: GraphQLSchema) -> None: def test_log_files_query(self, schema: GraphQLSchema) -> None:
from unraid_mcp.tools.storage import QUERIES from unraid_mcp.tools.storage import QUERIES
@@ -258,7 +252,7 @@ class TestStorageQueries:
def test_all_storage_queries_covered(self, schema: GraphQLSchema) -> None: def test_all_storage_queries_covered(self, schema: GraphQLSchema) -> None:
from unraid_mcp.tools.storage import QUERIES from unraid_mcp.tools.storage import QUERIES
expected = {"shares", "disks", "disk_details", "unassigned", "log_files", "logs"} expected = {"shares", "disks", "disk_details", "log_files", "logs"}
assert set(QUERIES.keys()) == expected assert set(QUERIES.keys()) == expected

View File

@@ -154,12 +154,6 @@ class TestStorageActions:
with pytest.raises(ToolError, match="not found"): with pytest.raises(ToolError, match="not found"):
await tool_fn(action="disk_details", disk_id="d:missing") await tool_fn(action="disk_details", disk_id="d:missing")
async def test_unassigned(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"unassignedDevices": []}
tool_fn = _make_tool()
result = await tool_fn(action="unassigned")
assert result["devices"] == []
async def test_log_files(self, _mock_graphql: AsyncMock) -> None: async def test_log_files(self, _mock_graphql: AsyncMock) -> None:
_mock_graphql.return_value = {"logFiles": [{"name": "syslog", "path": "/var/log/syslog"}]} _mock_graphql.return_value = {"logFiles": [{"name": "syslog", "path": "/var/log/syslog"}]}
tool_fn = _make_tool() tool_fn = _make_tool()

View File

@@ -18,7 +18,6 @@ from .config.settings import (
VERSION, VERSION,
) )
from .subscriptions.resources import register_subscription_resources from .subscriptions.resources import register_subscription_resources
from .tools.array import register_array_tool
from .tools.docker import register_docker_tool from .tools.docker import register_docker_tool
from .tools.health import register_health_tool from .tools.health import register_health_tool
from .tools.info import register_info_tool from .tools.info import register_info_tool
@@ -51,7 +50,6 @@ def register_all_modules() -> None:
# Register all consolidated tools # Register all consolidated tools
registrars = [ registrars = [
register_info_tool, register_info_tool,
register_array_tool,
register_storage_tool, register_storage_tool,
register_docker_tool, register_docker_tool,
register_vm_tool, register_vm_tool,

View File

@@ -2,7 +2,6 @@
10 consolidated tools with ~90 actions total: 10 consolidated tools with ~90 actions total:
unraid_info - System information queries (19 actions) unraid_info - System information queries (19 actions)
unraid_array - Array operations and power management (12 actions)
unraid_storage - Storage, disks, and logs (6 actions) unraid_storage - Storage, disks, and logs (6 actions)
unraid_docker - Docker container management (15 actions) unraid_docker - Docker container management (15 actions)
unraid_vm - Virtual machine management (9 actions) unraid_vm - Virtual machine management (9 actions)

View File

@@ -1,104 +0,0 @@
"""Array parity check operations.
Provides the `unraid_array` tool with 5 actions for parity check management.
"""
from typing import Any, Literal
from fastmcp import FastMCP
from ..config.logging import logger
from ..core.client import make_graphql_request
from ..core.exceptions import ToolError
QUERIES: dict[str, str] = {
"parity_status": """
query GetParityStatus {
array { parityCheckStatus { progress speed errors } }
}
""",
}
MUTATIONS: dict[str, str] = {
"parity_start": """
mutation StartParityCheck($correct: Boolean) {
parityCheck { start(correct: $correct) }
}
""",
"parity_pause": """
mutation PauseParityCheck {
parityCheck { pause }
}
""",
"parity_resume": """
mutation ResumeParityCheck {
parityCheck { resume }
}
""",
"parity_cancel": """
mutation CancelParityCheck {
parityCheck { cancel }
}
""",
}
ALL_ACTIONS = set(QUERIES) | set(MUTATIONS)
ARRAY_ACTIONS = Literal[
"parity_start",
"parity_pause",
"parity_resume",
"parity_cancel",
"parity_status",
]
def register_array_tool(mcp: FastMCP) -> None:
"""Register the unraid_array tool with the FastMCP instance."""
@mcp.tool()
async def unraid_array(
action: ARRAY_ACTIONS,
correct: bool | None = None,
) -> dict[str, Any]:
"""Manage Unraid array parity checks.
Actions:
parity_start - Start parity check (optional correct=True to fix errors)
parity_pause - Pause running parity check
parity_resume - Resume paused parity check
parity_cancel - Cancel running parity check
parity_status - Get current parity check status
"""
if action not in ALL_ACTIONS:
raise ToolError(f"Invalid action '{action}'. Must be one of: {sorted(ALL_ACTIONS)}")
try:
logger.info(f"Executing unraid_array action={action}")
if action in QUERIES:
data = await make_graphql_request(QUERIES[action])
return {"success": True, "action": action, "data": data}
query = MUTATIONS[action]
variables: dict[str, Any] | None = None
if action == "parity_start" and correct is not None:
variables = {"correct": correct}
data = await make_graphql_request(query, variables)
return {
"success": True,
"action": action,
"data": data,
}
except ToolError:
raise
except Exception as e:
logger.error(f"Error in unraid_array action={action}: {e}", exc_info=True)
raise ToolError(f"Failed to execute array/{action}: {e!s}") from e
logger.info("Array tool registered successfully")

View File

@@ -103,7 +103,6 @@ async def _comprehensive_check() -> dict[str, Any]:
query ComprehensiveHealthCheck { query ComprehensiveHealthCheck {
info { info {
machineId time machineId time
versions { unraid }
os { uptime } os { uptime }
} }
array { state } array { state }

View File

@@ -63,11 +63,6 @@ QUERIES: dict[str, str] = {
} }
} }
""", """,
"connect": """
query GetConnectSettings {
connect { status sandbox flashGuid }
}
""",
"variables": """ "variables": """
query GetSelectiveUnraidVariables { query GetSelectiveUnraidVariables {
vars { vars {
@@ -85,12 +80,12 @@ QUERIES: dict[str, str] = {
""", """,
"metrics": """ "metrics": """
query GetMetrics { query GetMetrics {
metrics { cpu { used } memory { used total } } metrics { cpu { percentTotal cpus { percentTotal } } memory { used total } }
} }
""", """,
"services": """ "services": """
query GetServices { query GetServices {
services { name state } services { name online uptime }
} }
""", """,
"display": """ "display": """
@@ -159,7 +154,6 @@ INFO_ACTIONS = Literal[
"array", "array",
"network", "network",
"registration", "registration",
"connect",
"variables", "variables",
"metrics", "metrics",
"services", "services",
@@ -327,7 +321,6 @@ def register_info_tool(mcp: FastMCP) -> None:
array - Array state, capacity, disk health array - Array state, capacity, disk health
network - Access URLs, interfaces network - Access URLs, interfaces
registration - License type, state, expiration registration - License type, state, expiration
connect - Unraid Connect settings
variables - System variables and configuration variables - System variables and configuration
metrics - CPU and memory utilization metrics - CPU and memory utilization
services - Running services services - Running services
@@ -359,7 +352,6 @@ def register_info_tool(mcp: FastMCP) -> None:
dict_actions: dict[str, str] = { dict_actions: dict[str, str] = {
"network": "network", "network": "network",
"registration": "registration", "registration": "registration",
"connect": "connect",
"variables": "vars", "variables": "vars",
"metrics": "metrics", "metrics": "metrics",
"config": "config", "config": "config",

View File

@@ -16,12 +16,12 @@ from ..core.exceptions import ToolError
QUERIES: dict[str, str] = { QUERIES: dict[str, str] = {
"list": """ "list": """
query ListApiKeys { query ListApiKeys {
apiKeys { id name roles permissions createdAt lastUsed } apiKeys { id name roles permissions { resource actions } createdAt lastUsed }
} }
""", """,
"get": """ "get": """
query GetApiKey($id: PrefixedID!) { query GetApiKey($id: PrefixedID!) {
apiKey(id: $id) { id name roles permissions createdAt lastUsed } apiKey(id: $id) { id name roles permissions { resource actions } createdAt lastUsed }
} }
""", """,
} }

View File

@@ -1,7 +1,6 @@
"""Storage and disk management. """Storage and disk management.
Provides the `unraid_storage` tool with 6 actions for shares, physical disks, Provides the `unraid_storage` tool with 6 actions for shares, physical disks, log files, and log content retrieval.
unassigned devices, log files, and log content retrieval.
""" """
from typing import Any, Literal from typing import Any, Literal
@@ -37,11 +36,6 @@ QUERIES: dict[str, str] = {
} }
} }
""", """,
"unassigned": """
query GetUnassignedDevices {
unassignedDevices { id device name size type }
}
""",
"log_files": """ "log_files": """
query ListLogFiles { query ListLogFiles {
logFiles { name path size modifiedAt } logFiles { name path size modifiedAt }
@@ -60,7 +54,6 @@ STORAGE_ACTIONS = Literal[
"shares", "shares",
"disks", "disks",
"disk_details", "disk_details",
"unassigned",
"log_files", "log_files",
"logs", "logs",
] ]
@@ -97,7 +90,6 @@ def register_storage_tool(mcp: FastMCP) -> None:
shares - List all user shares with capacity info shares - List all user shares with capacity info
disks - List all physical disks disks - List all physical disks
disk_details - Detailed SMART info for a disk (requires disk_id) disk_details - Detailed SMART info for a disk (requires disk_id)
unassigned - List unassigned devices
log_files - List available log files log_files - List available log files
logs - Retrieve log content (requires log_path, optional tail_lines) logs - Retrieve log content (requires log_path, optional tail_lines)
""" """
@@ -158,10 +150,6 @@ def register_storage_tool(mcp: FastMCP) -> None:
} }
return {"summary": summary, "details": raw} return {"summary": summary, "details": raw}
if action == "unassigned":
devices = data.get("unassignedDevices", [])
return {"devices": list(devices) if isinstance(devices, list) else []}
if action == "log_files": if action == "log_files":
files = data.get("logFiles", []) files = data.get("logFiles", [])
return {"log_files": list(files) if isinstance(files, list) else []} return {"log_files": list(files) if isinstance(files, list) else []}