forked from HomeLab/unraid-mcp
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df2db88e0e | |||
| 88983c6736 | |||
| f89ed7275b | |||
| 3d23d3c5b4 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -57,3 +57,5 @@ client_secret_*.apps.googleusercontent.com.json
|
||||
web-ui/frontend/node_modules
|
||||
web-ui/backend/.venv-backend/
|
||||
.pnpm-store/
|
||||
|
||||
.continue
|
||||
19
CLAUDE.md
19
CLAUDE.md
@@ -84,17 +84,16 @@ docker compose down
|
||||
- **Health Monitoring**: Comprehensive health check tool for system monitoring
|
||||
- **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
|
||||
2. **`unraid_array`** (5 actions): parity_start, parity_pause, parity_resume, parity_cancel, parity_status
|
||||
3. **`unraid_storage`** (6 actions): shares, disks, disk_details, unassigned, log_files, logs
|
||||
4. **`unraid_docker`** (15 actions): list, details, start, stop, restart, pause, unpause, remove, update, update_all, logs, networks, network_details, port_conflicts, check_updates
|
||||
5. **`unraid_vm`** (9 actions): list, details, start, stop, pause, resume, force_stop, reboot, reset
|
||||
6. **`unraid_notifications`** (9 actions): overview, list, warnings, create, archive, unread, delete, delete_archived, archive_all
|
||||
7. **`unraid_rclone`** (4 actions): list_remotes, config_form, create_remote, delete_remote
|
||||
8. **`unraid_users`** (1 action): me
|
||||
9. **`unraid_keys`** (5 actions): list, get, create, update, delete
|
||||
10. **`unraid_health`** (3 actions): check, test_connection, diagnose
|
||||
2. **`unraid_storage`** (6 actions): shares, disks, disk_details, 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_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_rclone`** (4 actions): list_remotes, config_form, create_remote, delete_remote
|
||||
7. **`unraid_users`** (1 action): me
|
||||
8. **`unraid_keys`** (5 actions): list, get, create, update, delete
|
||||
9. **`unraid_health`** (3 actions): check, test_connection, diagnose
|
||||
|
||||
### Environment Variable Hierarchy
|
||||
The server loads environment variables from multiple locations in order:
|
||||
|
||||
23
HISTORY.md
23
HISTORY.md
@@ -5,11 +5,34 @@ Changelog
|
||||
(unreleased)
|
||||
------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Even more changes to accommodate older GraphQL schema. [Simon
|
||||
Diesenreiter]
|
||||
|
||||
|
||||
0.0.3 (2026-02-28)
|
||||
------------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Adapt for supported GraphQL schema on 6.12.13. [Simon Diesenreiter]
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
|
||||
|
||||
0.0.2 (2026-02-21)
|
||||
------------------
|
||||
|
||||
Fix
|
||||
~~~
|
||||
- Set scripts as executable, refs NOISSUE. [Simon Diesenreiter]
|
||||
- Initial test release. [Simon Diesenreiter]
|
||||
|
||||
Other
|
||||
~~~~~
|
||||
|
||||
|
||||
0.0.1 (2026-02-21)
|
||||
------------------
|
||||
|
||||
@@ -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`.
|
||||
|
||||
### Tool Categories (10 Tools, 76 Actions)
|
||||
### Tool Categories (9 Tools, 70 Actions)
|
||||
|
||||
| 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_array`** | 5 | parity_start, parity_pause, parity_resume, parity_cancel, parity_status |
|
||||
| **`unraid_storage`** | 6 | shares, disks, disk_details, unassigned, log_files, logs |
|
||||
| **`unraid_storage`** | 6 | shares, disks, disk_details, 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_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 |
|
||||
@@ -242,14 +241,13 @@ Each tool uses a consolidated `action` parameter to expose multiple operations,
|
||||
|
||||
## 💬 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
|
||||
|
||||
| Command | Actions | Quick Access |
|
||||
|---------|---------|--------------|
|
||||
| `/info` | 19 | System information, metrics, configuration |
|
||||
| `/array` | 5 | Parity check management |
|
||||
| `/storage` | 6 | Shares, disks, logs |
|
||||
| `/docker` | 15 | Container management and monitoring |
|
||||
| `/vm` | 9 | Virtual machine lifecycle |
|
||||
|
||||
@@ -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.
|
||||
@@ -18,7 +18,6 @@ Execute the `unraid_info` MCP tool with action: `$1`
|
||||
**Network & Registration:**
|
||||
- `network` - Network configuration and interfaces
|
||||
- `registration` - Registration status and license info
|
||||
- `connect` - Connect service configuration
|
||||
- `online` - Online status check
|
||||
|
||||
**Configuration:**
|
||||
|
||||
@@ -11,7 +11,6 @@ Execute the `unraid_storage` MCP tool with action: `$1`
|
||||
- `shares` - List all user shares with sizes and allocation
|
||||
- `disks` - List all disks in the array
|
||||
- `disk_details` - Get detailed info for a specific disk (requires disk identifier)
|
||||
- `unassigned` - List unassigned devices
|
||||
|
||||
**Logs:**
|
||||
- `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 disks
|
||||
/unraid-storage disk_details disk1
|
||||
/unraid-storage unassigned
|
||||
/unraid-storage log_files
|
||||
/unraid-storage logs /var/log/syslog
|
||||
```
|
||||
|
||||
@@ -541,8 +541,6 @@ Possible error states for configuration
|
||||
- Fields (3):
|
||||
- `api`: `String`
|
||||
- Unraid API version
|
||||
- `kernel`: `String`
|
||||
- Kernel version
|
||||
- `unraid`: `String`
|
||||
- Unraid version
|
||||
|
||||
@@ -1021,8 +1019,6 @@ The `ID` scalar type represents a unique identifier, often used to refetch an ob
|
||||
- `hostname`: `String`
|
||||
- Hostname
|
||||
- `id`: `PrefixedID!`
|
||||
- `kernel`: `String`
|
||||
- Kernel version
|
||||
- `logofile`: `String`
|
||||
- OS logo name
|
||||
- `platform`: `String`
|
||||
@@ -1426,8 +1422,6 @@ System metrics including CPU and memory utilization
|
||||
- Node.js version
|
||||
- `npm`: `String`
|
||||
- npm version
|
||||
- `openssl`: `String`
|
||||
- OpenSSL version
|
||||
- `php`: `String`
|
||||
- PHP version
|
||||
- `pm2`: `String`
|
||||
|
||||
@@ -355,7 +355,6 @@ The project's documentation explicitly compares SSH vs API capabilities:
|
||||
| Network config | Y | Y | Y | Y | N | N | N |
|
||||
| Network bandwidth | N | Y | N | Y | 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 |
|
||||
| System services status | N | Y | Y | N | N | N | N |
|
||||
| Flash drive info | N | Y | Y | N | N | Y | N |
|
||||
|
||||
@@ -27,7 +27,7 @@ Every query type identified across all research documents, with their fields and
|
||||
|
||||
| Query | Fields | Current MCP Coverage |
|
||||
|-------|--------|---------------------|
|
||||
| `info` | `time`, `baseboard { manufacturer, model, version, serial }`, `cpu { manufacturer, brand, vendor, family, model, stepping, revision, voltage, speed, speedmin, speedmax, threads, cores, processors, socket, cache, flags }`, `devices`, `display`, `machineId`, `memory { max, total, free, used, active, available, buffcache, swaptotal, swapused, swapfree, layout[] }`, `os { platform, distro, release, codename, kernel, arch, hostname, codepage, logofile, serial, build, uptime }`, `system { manufacturer, model, version, serial, uuid }`, `versions { kernel, docker, unraid, node }`, `apps { installed, started }` | **YES** - `get_system_info()` |
|
||||
| `info` | `time`, `baseboard { manufacturer, model, version, serial }`, `cpu { manufacturer, brand, vendor, family, model, stepping, revision, voltage, speed, speedmin, speedmax, threads, cores, processors, socket, cache, flags }`, `devices`, `display`, `machineId`, `memory { max, total, free, used, active, available, buffcache, swaptotal, swapused, swapfree, layout[] }`, `os { platform, distro, release, codename, arch, hostname, logofile, serial, build, uptime }`, `system { manufacturer, model, version, serial, uuid }`, `versions { docker, unraid, node }` | **YES** - `get_system_info()` |
|
||||
| `vars` | `id`, `version`, `name`, `timeZone`, `comment`, `security`, `workgroup`, `domain`, `useNtp`, `ntpServer1-4`, `useSsl`, `port`, `portssl`, `useTelnet`, `useSsh`, `portssh`, `startPage`, `startArray`, `spindownDelay`, `defaultFormat`, `defaultFsType`, `shutdownTimeout`, `shareDisk`, `shareUser`, `shareSmbEnabled`, `shareNfsEnabled`, `shareAfpEnabled`, `shareCacheEnabled`, `shareMoverSchedule`, `shareMoverLogging`, `safeMode`, `configValid`, `configError`, `deviceCount`, `flashGuid`, `flashProduct`, `flashVendor`, `regState`, `regTo`, `mdState`, `mdNumDisks`, `mdNumDisabled`, `mdNumInvalid`, `mdNumMissing`, `mdResync`, `mdResyncAction`, `fsState`, `fsProgress`, `fsCopyPrcnt`, `shareCount`, `shareSmbCount`, `shareNfsCount`, `csrfToken`, `maxArraysz`, `maxCachesz` | **YES** - `get_unraid_variables()` |
|
||||
| `online` | `Boolean` | **NO** |
|
||||
| `owner` | Server owner information | **NO** |
|
||||
@@ -378,15 +378,14 @@ GRAPHQL_PUBSUB_CHANNEL {
|
||||
| `ContainerHostConfig` | JSON host configuration | |
|
||||
| `VmDomain` | `uuid/id`, `name`, `state` | Implements Node |
|
||||
| `Vms` | `id`, `domain[]` | |
|
||||
| `Info` | `time`, `baseboard`, `cpu`, `devices`, `display`, `machineId`, `memory`, `os`, `system`, `versions`, `apps` | Implements Node |
|
||||
| `Info` | `time`, `baseboard`, `cpu`, `devices`, `display`, `machineId`, `memory`, `os`, `system`, `versions` | Implements Node |
|
||||
| `InfoCpu` | `manufacturer`, `brand`, `vendor`, `family`, `model`, `stepping`, `revision`, `voltage`, `speed`, `speedmin`, `speedmax`, `threads`, `cores`, `processors`, `socket`, `cache`, `flags` | |
|
||||
| `InfoMemory` | `max`, `total`, `free`, `used`, `active`, `available`, `buffcache`, `swaptotal`, `swapused`, `swapfree`, `layout[]` | |
|
||||
| `MemoryLayout` | `bank`, `type`, `clockSpeed`, `manufacturer` | Missing `size` field (known bug) |
|
||||
| `Os` | `platform`, `distro`, `release`, `codename`, `kernel`, `arch`, `hostname`, `codepage`, `logofile`, `serial`, `build`, `uptime` | |
|
||||
| `Os` | `platform`, `distro`, `release`, `codename`, `arch`, `hostname`, `logofile`, `serial`, `build`, `uptime` | |
|
||||
| `Baseboard` | `manufacturer`, `model`, `version`, `serial` | |
|
||||
| `SystemInfo` | `manufacturer`, `model`, `version`, `serial`, `uuid` | |
|
||||
| `Versions` | `kernel`, `docker`, `unraid`, `node` | |
|
||||
| `InfoApps` | `installed`, `started` | |
|
||||
| `Versions` | `docker`, `unraid`, `node` | |
|
||||
| `Network` | `iface`, `ifaceName`, `ipv4`, `ipv6`, `mac`, `internal`, `operstate`, `type`, `duplex`, `mtu`, `speed`, `carrierChanges`, `id`, `accessUrls[]` | Implements Node |
|
||||
| `AccessUrl` | `type`, `name`, `ipv4`, `ipv6` | |
|
||||
| `Share` | `name`, `free`, `used`, `size`, `include[]`, `exclude[]`, `cache`, `nameOrig`, `comment`, `allocator`, `splitLevel`, `floor`, `cow`, `color`, `luksStatus` | |
|
||||
|
||||
@@ -120,7 +120,7 @@ Learn More about the Unraid API
|
||||
|
||||
* Linux Kernel 6.12.54-Unraid
|
||||
* Samba 4.23.2
|
||||
* Updated versions of openssl, mesa, kernel-firmware, git, exfatprogs, and more
|
||||
* Updated versions of mesa, kernel-firmware, git, exfatprogs, and more
|
||||
|
||||
**Plugin Compatibility Notice**
|
||||
-------------------------------
|
||||
|
||||
@@ -665,7 +665,6 @@ type Query {
|
||||
servers: [Server!]!
|
||||
services: [Service!]!
|
||||
shares: [Share]
|
||||
unassignedDevices: [UnassignedDevice]
|
||||
me: Me
|
||||
user(id: ID!): User
|
||||
users(input: usersInput): [User!]!
|
||||
@@ -743,7 +742,6 @@ type Subscription {
|
||||
service(name: String!): [Service!]
|
||||
share(id: ID!): Share!
|
||||
shares: [Share!]
|
||||
unassignedDevices: [UnassignedDevice!]
|
||||
me: Me
|
||||
user(id: ID!): User!
|
||||
users: [User]!
|
||||
@@ -892,7 +890,6 @@ type DockerNetwork {
|
||||
|
||||
```graphql
|
||||
type Info implements Node {
|
||||
apps: InfoApps
|
||||
baseboard: Baseboard
|
||||
cpu: InfoCpu
|
||||
devices: Devices
|
||||
@@ -945,10 +942,8 @@ type Os {
|
||||
distro: String
|
||||
release: String
|
||||
codename: String
|
||||
kernel: String
|
||||
arch: String
|
||||
hostname: String
|
||||
codepage: String
|
||||
logofile: String
|
||||
serial: String
|
||||
build: String
|
||||
|
||||
@@ -220,13 +220,12 @@ Then access at `http://YOUR_SERVER_IP/graphql` to explore the schema via Apollo
|
||||
```graphql
|
||||
query {
|
||||
info {
|
||||
os { platform distro release uptime hostname arch kernel }
|
||||
os { platform distro release uptime hostname arch }
|
||||
cpu { manufacturer brand cores threads }
|
||||
memory { layout { bank type clockSpeed manufacturer } }
|
||||
baseboard { manufacturer model version serial }
|
||||
system { manufacturer model version serial uuid }
|
||||
versions { kernel docker unraid node }
|
||||
apps { installed started }
|
||||
versions { docker unraid node }
|
||||
machineId
|
||||
time
|
||||
}
|
||||
|
||||
@@ -468,7 +468,6 @@ type Config implements Node {
|
||||
|
||||
type CoreVersions {
|
||||
api: String
|
||||
kernel: String
|
||||
unraid: String
|
||||
}
|
||||
|
||||
@@ -478,7 +477,6 @@ type PackageVersions {
|
||||
nginx: String
|
||||
node: String
|
||||
npm: String
|
||||
openssl: String
|
||||
php: String
|
||||
pm2: String
|
||||
}
|
||||
@@ -487,33 +485,6 @@ type InfoVersions implements Node {
|
||||
id: PrefixedID!
|
||||
core: CoreVersions!
|
||||
packages: PackageVersions
|
||||
# Flattened fields used by the MCP tool queries (may exist in live API)
|
||||
kernel: String
|
||||
openssl: String
|
||||
systemOpenssl: String
|
||||
systemOpensslLib: String
|
||||
node: String
|
||||
v8: String
|
||||
npm: String
|
||||
yarn: String
|
||||
pm2: String
|
||||
gulp: String
|
||||
grunt: String
|
||||
git: String
|
||||
tsc: String
|
||||
mysql: String
|
||||
redis: String
|
||||
mongodb: String
|
||||
apache: String
|
||||
nginx: String
|
||||
php: String
|
||||
docker: String
|
||||
postfix: String
|
||||
postgresql: String
|
||||
perl: String
|
||||
python: String
|
||||
gcc: String
|
||||
unraid: String
|
||||
}
|
||||
|
||||
type InfoOs implements Node {
|
||||
@@ -522,7 +493,6 @@ type InfoOs implements Node {
|
||||
distro: String
|
||||
release: String
|
||||
codename: String
|
||||
kernel: String
|
||||
arch: String
|
||||
hostname: String
|
||||
logofile: String
|
||||
@@ -532,7 +502,6 @@ type InfoOs implements Node {
|
||||
fqdn: String
|
||||
servicepack: String
|
||||
uefi: Boolean
|
||||
codepage: String
|
||||
}
|
||||
|
||||
type InfoCpu implements Node {
|
||||
@@ -714,11 +683,6 @@ type InfoDisplay implements Node {
|
||||
wwn: Boolean!
|
||||
}
|
||||
|
||||
type Apps {
|
||||
installed: Int
|
||||
started: Int
|
||||
}
|
||||
|
||||
type Info implements Node {
|
||||
id: PrefixedID!
|
||||
os: InfoOs!
|
||||
@@ -729,13 +693,13 @@ type Info implements Node {
|
||||
versions: InfoVersions!
|
||||
devices: InfoDevices!
|
||||
display: InfoDisplay!
|
||||
apps: Apps
|
||||
machineId: ID
|
||||
time: DateTime!
|
||||
}
|
||||
|
||||
type MetricsCpu {
|
||||
used: Float
|
||||
percentTotal: Float!
|
||||
cpus: [CPULoad!]!
|
||||
}
|
||||
|
||||
type MetricsMemory {
|
||||
@@ -752,7 +716,6 @@ type Metrics implements Node {
|
||||
type Service implements Node {
|
||||
id: PrefixedID!
|
||||
name: String
|
||||
state: String
|
||||
online: Boolean
|
||||
uptime: Uptime
|
||||
version: String
|
||||
@@ -788,12 +751,6 @@ type Registration implements Node {
|
||||
updateExpiration: String
|
||||
}
|
||||
|
||||
type ConnectSettings {
|
||||
status: String
|
||||
sandbox: Boolean
|
||||
flashGuid: String
|
||||
}
|
||||
|
||||
type Owner {
|
||||
username: String!
|
||||
avatar: String!
|
||||
@@ -1362,9 +1319,6 @@ type Query {
|
||||
|
||||
# Network (used by MCP tool)
|
||||
network: Network
|
||||
|
||||
# Connect (used by MCP tool)
|
||||
connect: ConnectSettings
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -1900,9 +1900,6 @@ type InfoOs implements Node {
|
||||
"""OS codename"""
|
||||
codename: String
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
|
||||
"""OS architecture"""
|
||||
arch: String
|
||||
|
||||
@@ -1987,15 +1984,9 @@ type CoreVersions {
|
||||
|
||||
"""Unraid API version"""
|
||||
api: String
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
}
|
||||
|
||||
type PackageVersions {
|
||||
"""OpenSSL version"""
|
||||
openssl: String
|
||||
|
||||
"""Node.js version"""
|
||||
node: String
|
||||
|
||||
|
||||
@@ -1900,9 +1900,6 @@ type InfoOs implements Node {
|
||||
"""OS codename"""
|
||||
codename: String
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
|
||||
"""OS architecture"""
|
||||
arch: String
|
||||
|
||||
@@ -1987,15 +1984,9 @@ type CoreVersions {
|
||||
|
||||
"""Unraid API version"""
|
||||
api: String
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
}
|
||||
|
||||
type PackageVersions {
|
||||
"""OpenSSL version"""
|
||||
openssl: String
|
||||
|
||||
"""Node.js version"""
|
||||
node: String
|
||||
|
||||
|
||||
@@ -783,17 +783,6 @@ class TestStorageToolRequests:
|
||||
with pytest.raises(ToolError, match="log_path must start with"):
|
||||
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
|
||||
|
||||
@@ -237,12 +237,6 @@ class TestStorageQueries:
|
||||
errors = _validate_operation(schema, QUERIES["disk_details"])
|
||||
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:
|
||||
from unraid_mcp.tools.storage import QUERIES
|
||||
|
||||
@@ -258,7 +252,7 @@ class TestStorageQueries:
|
||||
def test_all_storage_queries_covered(self, schema: GraphQLSchema) -> None:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -154,12 +154,6 @@ class TestStorageActions:
|
||||
with pytest.raises(ToolError, match="not found"):
|
||||
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:
|
||||
_mock_graphql.return_value = {"logFiles": [{"name": "syslog", "path": "/var/log/syslog"}]}
|
||||
tool_fn = _make_tool()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
10 consolidated tools with ~90 actions total:
|
||||
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_docker - Docker container management (15 actions)
|
||||
unraid_vm - Virtual machine management (9 actions)
|
||||
|
||||
@@ -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")
|
||||
@@ -103,7 +103,6 @@ async def _comprehensive_check() -> dict[str, Any]:
|
||||
query ComprehensiveHealthCheck {
|
||||
info {
|
||||
machineId time
|
||||
versions { unraid }
|
||||
os { uptime }
|
||||
}
|
||||
array { state }
|
||||
|
||||
@@ -18,15 +18,13 @@ QUERIES: dict[str, str] = {
|
||||
"overview": """
|
||||
query GetSystemInfo {
|
||||
info {
|
||||
os { platform distro release codename kernel arch hostname codepage logofile serial build uptime }
|
||||
os { platform distro release codename arch hostname logofile serial build uptime }
|
||||
cpu { manufacturer brand vendor family model stepping revision voltage speed speedmin speedmax threads cores processors socket cache flags }
|
||||
memory {
|
||||
layout { bank type clockSpeed formFactor manufacturer partNum serialNum }
|
||||
}
|
||||
baseboard { manufacturer model version serial assetTag }
|
||||
system { manufacturer model version serial uuid sku }
|
||||
versions { kernel openssl systemOpenssl systemOpensslLib node v8 npm yarn pm2 gulp grunt git tsc mysql redis mongodb apache nginx php docker postfix postgresql perl python gcc unraid }
|
||||
apps { installed started }
|
||||
machineId
|
||||
time
|
||||
}
|
||||
@@ -65,11 +63,6 @@ QUERIES: dict[str, str] = {
|
||||
}
|
||||
}
|
||||
""",
|
||||
"connect": """
|
||||
query GetConnectSettings {
|
||||
connect { status sandbox flashGuid }
|
||||
}
|
||||
""",
|
||||
"variables": """
|
||||
query GetSelectiveUnraidVariables {
|
||||
vars {
|
||||
@@ -87,12 +80,12 @@ QUERIES: dict[str, str] = {
|
||||
""",
|
||||
"metrics": """
|
||||
query GetMetrics {
|
||||
metrics { cpu { used } memory { used total } }
|
||||
metrics { cpu { percentTotal cpus { percentTotal } } memory { used total } }
|
||||
}
|
||||
""",
|
||||
"services": """
|
||||
query GetServices {
|
||||
services { name state }
|
||||
services { name online uptime }
|
||||
}
|
||||
""",
|
||||
"display": """
|
||||
@@ -161,7 +154,6 @@ INFO_ACTIONS = Literal[
|
||||
"array",
|
||||
"network",
|
||||
"registration",
|
||||
"connect",
|
||||
"variables",
|
||||
"metrics",
|
||||
"services",
|
||||
@@ -329,7 +321,6 @@ def register_info_tool(mcp: FastMCP) -> None:
|
||||
array - Array state, capacity, disk health
|
||||
network - Access URLs, interfaces
|
||||
registration - License type, state, expiration
|
||||
connect - Unraid Connect settings
|
||||
variables - System variables and configuration
|
||||
metrics - CPU and memory utilization
|
||||
services - Running services
|
||||
@@ -361,7 +352,6 @@ def register_info_tool(mcp: FastMCP) -> None:
|
||||
dict_actions: dict[str, str] = {
|
||||
"network": "network",
|
||||
"registration": "registration",
|
||||
"connect": "connect",
|
||||
"variables": "vars",
|
||||
"metrics": "metrics",
|
||||
"config": "config",
|
||||
|
||||
@@ -16,12 +16,12 @@ from ..core.exceptions import ToolError
|
||||
QUERIES: dict[str, str] = {
|
||||
"list": """
|
||||
query ListApiKeys {
|
||||
apiKeys { id name roles permissions createdAt lastUsed }
|
||||
apiKeys { id name roles permissions { resource actions } createdAt lastUsed }
|
||||
}
|
||||
""",
|
||||
"get": """
|
||||
query GetApiKey($id: PrefixedID!) {
|
||||
apiKey(id: $id) { id name roles permissions createdAt lastUsed }
|
||||
apiKey(id: $id) { id name roles permissions { resource actions } createdAt lastUsed }
|
||||
}
|
||||
""",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Storage and disk management.
|
||||
|
||||
Provides the `unraid_storage` tool with 6 actions for shares, physical disks,
|
||||
unassigned devices, log files, and log content retrieval.
|
||||
Provides the `unraid_storage` tool with 6 actions for shares, physical disks, log files, and log content retrieval.
|
||||
"""
|
||||
|
||||
from typing import Any, Literal
|
||||
@@ -37,11 +36,6 @@ QUERIES: dict[str, str] = {
|
||||
}
|
||||
}
|
||||
""",
|
||||
"unassigned": """
|
||||
query GetUnassignedDevices {
|
||||
unassignedDevices { id device name size type }
|
||||
}
|
||||
""",
|
||||
"log_files": """
|
||||
query ListLogFiles {
|
||||
logFiles { name path size modifiedAt }
|
||||
@@ -60,7 +54,6 @@ STORAGE_ACTIONS = Literal[
|
||||
"shares",
|
||||
"disks",
|
||||
"disk_details",
|
||||
"unassigned",
|
||||
"log_files",
|
||||
"logs",
|
||||
]
|
||||
@@ -97,7 +90,6 @@ def register_storage_tool(mcp: FastMCP) -> None:
|
||||
shares - List all user shares with capacity info
|
||||
disks - List all physical disks
|
||||
disk_details - Detailed SMART info for a disk (requires disk_id)
|
||||
unassigned - List unassigned devices
|
||||
log_files - List available log files
|
||||
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}
|
||||
|
||||
if action == "unassigned":
|
||||
devices = data.get("unassignedDevices", [])
|
||||
return {"devices": list(devices) if isinstance(devices, list) else []}
|
||||
|
||||
if action == "log_files":
|
||||
files = data.get("logFiles", [])
|
||||
return {"log_files": list(files) if isinstance(files, list) else []}
|
||||
|
||||
Reference in New Issue
Block a user