forked from HomeLab/unraid-mcp
fix: address markdown lint and doc accuracy issues (threads 6-13)
Thread 6: Add python language specifier to .claude-plugin/README.md code fence Thread 7: Fix MD022/MD031 spacing in docs/MARKETPLACE.md Thread 8: Blank line before Destructive Actions heading in README.md Thread 9: Move H1 to first line in skills/unraid/references/api-reference.md Thread 10+11+12: Rewrite quick-reference.md: - Fix MD022/MD031 (blank lines around headings/fences) - Add python language specifiers to all code fences - Fix disk/logs params: path/lines → log_path/tail_lines - Add path param to live/log_tail example - Remove invalid notification/unread subaction - Fix notification/list filter= → list_type= Thread 13: Add python/text language specifiers to troubleshooting.md fences Thread 14: Update test-tools.sh header comment (in separate commit) Also: Remove 'connect' from README system subactions table (it was removed from the tool in previous commit), fix notification 'unread' → 'mark_unread' in table
This commit is contained in:
@@ -49,7 +49,7 @@ Query and monitor Unraid servers via GraphQL API - array status, disk health, co
|
|||||||
|
|
||||||
After installation, run setup to configure credentials interactively:
|
After installation, run setup to configure credentials interactively:
|
||||||
|
|
||||||
```
|
```python
|
||||||
unraid(action="health", subaction="setup")
|
unraid(action="health", subaction="setup")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
122
README.md
122
README.md
@@ -1,7 +1,7 @@
|
|||||||
# 🚀 Unraid MCP Server
|
# 🚀 Unraid MCP Server
|
||||||
|
|
||||||
[](https://www.python.org/downloads/)
|
[](https://www.python.org/downloads/)
|
||||||
[](https://github.com/jlowin/fastmcp)
|
[](https://github.com/jlowin/fastmcp)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
**A powerful MCP (Model Context Protocol) server that provides comprehensive tools to interact with an Unraid server's GraphQL API.**
|
**A powerful MCP (Model Context Protocol) server that provides comprehensive tools to interact with an Unraid server's GraphQL API.**
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
- [Installation](#-installation)
|
- [Installation](#-installation)
|
||||||
- [Configuration](#-configuration)
|
- [Configuration](#-configuration)
|
||||||
- [Available Tools & Resources](#-available-tools--resources)
|
- [Available Tools & Resources](#-available-tools--resources)
|
||||||
- [Custom Slash Commands](#-custom-slash-commands)
|
|
||||||
- [Development](#-development)
|
- [Development](#-development)
|
||||||
- [Architecture](#-architecture)
|
- [Architecture](#-architecture)
|
||||||
- [Troubleshooting](#-troubleshooting)
|
- [Troubleshooting](#-troubleshooting)
|
||||||
@@ -47,7 +46,6 @@
|
|||||||
|
|
||||||
This provides instant access to Unraid monitoring and management through Claude Code with:
|
This provides instant access to Unraid monitoring and management through Claude Code with:
|
||||||
- **1 MCP tool** (`unraid`) exposing **~108 actions** via `action` + `subaction` routing
|
- **1 MCP tool** (`unraid`) exposing **~108 actions** via `action` + `subaction` routing
|
||||||
- **11 slash commands** for quick CLI-style access (`commands/`)
|
|
||||||
- Real-time system metrics and health monitoring
|
- Real-time system metrics and health monitoring
|
||||||
- Docker container and VM lifecycle management
|
- Docker container and VM lifecycle management
|
||||||
- Disk health monitoring and storage management
|
- Disk health monitoring and storage management
|
||||||
@@ -97,8 +95,13 @@ cd unraid-mcp
|
|||||||
|
|
||||||
### 2. Configure Environment
|
### 2. Configure Environment
|
||||||
```bash
|
```bash
|
||||||
|
# For Docker/production use — canonical credential location (all runtimes)
|
||||||
|
mkdir -p ~/.unraid-mcp && chmod 700 ~/.unraid-mcp
|
||||||
|
cp .env.example ~/.unraid-mcp/.env && chmod 600 ~/.unraid-mcp/.env
|
||||||
|
# Edit ~/.unraid-mcp/.env with your values
|
||||||
|
|
||||||
|
# For local development only
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Edit .env with your Unraid API details
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Deploy with Docker (Recommended)
|
### 3. Deploy with Docker (Recommended)
|
||||||
@@ -130,7 +133,6 @@ unraid-mcp/ # ${CLAUDE_PLUGIN_ROOT}
|
|||||||
├── .claude-plugin/
|
├── .claude-plugin/
|
||||||
│ ├── marketplace.json # Marketplace catalog
|
│ ├── marketplace.json # Marketplace catalog
|
||||||
│ └── plugin.json # Plugin manifest
|
│ └── plugin.json # Plugin manifest
|
||||||
├── commands/ # 10 custom slash commands
|
|
||||||
├── unraid_mcp/ # MCP server Python package
|
├── unraid_mcp/ # MCP server Python package
|
||||||
├── skills/unraid/ # Skill and documentation
|
├── skills/unraid/ # Skill and documentation
|
||||||
├── pyproject.toml # Dependencies and entry points
|
├── pyproject.toml # Dependencies and entry points
|
||||||
@@ -138,7 +140,6 @@ unraid-mcp/ # ${CLAUDE_PLUGIN_ROOT}
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **MCP Server**: 1 `unraid` tool with ~108 actions via GraphQL API
|
- **MCP Server**: 1 `unraid` tool with ~108 actions via GraphQL API
|
||||||
- **Slash Commands**: 11 commands in `commands/` for quick CLI-style access
|
|
||||||
- **Skill**: `/unraid` skill for monitoring and queries
|
- **Skill**: `/unraid` skill for monitoring and queries
|
||||||
- **Entry Point**: `unraid-mcp-server` defined in pyproject.toml
|
- **Entry Point**: `unraid-mcp-server` defined in pyproject.toml
|
||||||
|
|
||||||
@@ -226,8 +227,12 @@ UNRAID_MCP_LOG_FILE=unraid-mcp.log
|
|||||||
# SSL/TLS Configuration
|
# SSL/TLS Configuration
|
||||||
UNRAID_VERIFY_SSL=true # true, false, or path to CA bundle
|
UNRAID_VERIFY_SSL=true # true, false, or path to CA bundle
|
||||||
|
|
||||||
|
# Subscription Configuration
|
||||||
|
UNRAID_AUTO_START_SUBSCRIPTIONS=true # Auto-start WebSocket subscriptions on startup (default: true)
|
||||||
|
UNRAID_MAX_RECONNECT_ATTEMPTS=5 # Max WebSocket reconnection attempts (default: 5)
|
||||||
|
|
||||||
# Optional: Log Stream Configuration
|
# Optional: Log Stream Configuration
|
||||||
# UNRAID_AUTOSTART_LOG_PATH=/var/log/syslog # Path for log streaming resource
|
# UNRAID_AUTOSTART_LOG_PATH=/var/log/syslog # Path for log streaming resource (unraid://logs/stream)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Transport Options
|
### Transport Options
|
||||||
@@ -250,13 +255,13 @@ Call pattern: `unraid(action="<domain>", subaction="<operation>")`
|
|||||||
|
|
||||||
| action= | Subactions | Description |
|
| action= | Subactions | Description |
|
||||||
|---------|-----------|-------------|
|
|---------|-----------|-------------|
|
||||||
| **`system`** | overview, array, network, registration, connect, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config | Server info, metrics, network, UPS (19 subactions) |
|
| **`system`** | overview, array, network, registration, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config | Server info, metrics, network, UPS (18 subactions) |
|
||||||
| **`health`** | check, test_connection, diagnose, setup | Health checks, connection test, diagnostics, interactive setup (4 subactions) |
|
| **`health`** | check, test_connection, diagnose, setup | Health checks, connection test, diagnostics, interactive setup (4 subactions) |
|
||||||
| **`array`** | parity_status, parity_history, parity_start, parity_pause, parity_resume, parity_cancel, start_array, stop_array, add_disk, remove_disk, mount_disk, unmount_disk, clear_disk_stats | Parity checks, array state, disk operations (13 subactions) |
|
| **`array`** | parity_status, parity_history, parity_start, parity_pause, parity_resume, parity_cancel, start_array, stop_array, add_disk, remove_disk, mount_disk, unmount_disk, clear_disk_stats | Parity checks, array state, disk operations (13 subactions) |
|
||||||
| **`disk`** | shares, disks, disk_details, log_files, logs, flash_backup | Shares, physical disks, log files (6 subactions) |
|
| **`disk`** | shares, disks, disk_details, log_files, logs, flash_backup | Shares, physical disks, log files (6 subactions) |
|
||||||
| **`docker`** | list, details, start, stop, restart, networks, network_details | Container lifecycle and network inspection (7 subactions) |
|
| **`docker`** | list, details, start, stop, restart, networks, network_details | Container lifecycle and network inspection (7 subactions) |
|
||||||
| **`vm`** | list, details, start, stop, pause, resume, force_stop, reboot, reset | Virtual machine lifecycle (9 subactions) |
|
| **`vm`** | list, details, start, stop, pause, resume, force_stop, reboot, reset | Virtual machine lifecycle (9 subactions) |
|
||||||
| **`notification`** | overview, list, create, archive, unread, delete, delete_archived, archive_all, archive_many, unarchive_many, unarchive_all, recalculate | System notifications CRUD (12 subactions) |
|
| **`notification`** | overview, list, create, archive, mark_unread, delete, delete_archived, archive_all, archive_many, unarchive_many, unarchive_all, recalculate | System notifications CRUD (12 subactions) |
|
||||||
| **`key`** | list, get, create, update, delete, add_role, remove_role | API key management (7 subactions) |
|
| **`key`** | list, get, create, update, delete, add_role, remove_role | API key management (7 subactions) |
|
||||||
| **`plugin`** | list, add, remove | Plugin management (3 subactions) |
|
| **`plugin`** | list, add, remove | Plugin management (3 subactions) |
|
||||||
| **`rclone`** | list_remotes, config_form, create_remote, delete_remote | Cloud storage remote management (4 subactions) |
|
| **`rclone`** | list_remotes, config_form, create_remote, delete_remote | Cloud storage remote management (4 subactions) |
|
||||||
@@ -278,8 +283,9 @@ Call pattern: `unraid(action="<domain>", subaction="<operation>")`
|
|||||||
|
|
||||||
### MCP Resources (Real-time Cached Data)
|
### MCP Resources (Real-time Cached Data)
|
||||||
|
|
||||||
The `unraid://live/*` resources expose cached subscription data from persistent WebSocket connections:
|
The server exposes two classes of MCP resources backed by persistent WebSocket connections:
|
||||||
|
|
||||||
|
**`unraid://live/*` — 9 snapshot resources** (auto-started, always-cached):
|
||||||
- `unraid://live/cpu` — CPU utilization
|
- `unraid://live/cpu` — CPU utilization
|
||||||
- `unraid://live/memory` — Memory usage
|
- `unraid://live/memory` — Memory usage
|
||||||
- `unraid://live/cpu_telemetry` — Detailed CPU telemetry
|
- `unraid://live/cpu_telemetry` — Detailed CPU telemetry
|
||||||
@@ -289,68 +295,12 @@ The `unraid://live/*` resources expose cached subscription data from persistent
|
|||||||
- `unraid://live/notifications_overview` — Notification counts
|
- `unraid://live/notifications_overview` — Notification counts
|
||||||
- `unraid://live/owner` — Owner info changes
|
- `unraid://live/owner` — Owner info changes
|
||||||
- `unraid://live/server_status` — Server status changes
|
- `unraid://live/server_status` — Server status changes
|
||||||
- `unraid://live/log_tail` — Live syslog tail
|
|
||||||
- `unraid://live/notification_feed` — Real-time notification events
|
**`unraid://logs/stream`** — Live log file tail (path controlled by `UNRAID_AUTOSTART_LOG_PATH`)
|
||||||
|
|
||||||
> **Note**: Resources return cached data from persistent WebSocket subscriptions. A `{"status": "connecting"}` placeholder is returned while the subscription initializes — retry in a moment.
|
> **Note**: Resources return cached data from persistent WebSocket subscriptions. A `{"status": "connecting"}` placeholder is returned while the subscription initializes — retry in a moment.
|
||||||
|
|
||||||
---
|
> **`log_tail` and `notification_feed`** are accessible as tool subactions (`unraid(action="live", subaction="log_tail")`) but are not registered as MCP resources — they use transient one-shot subscriptions and require parameters.
|
||||||
|
|
||||||
## 💬 Custom Slash Commands
|
|
||||||
|
|
||||||
The project includes **11 custom slash commands** in `commands/` for quick access to Unraid operations. Each command maps to a domain of the `unraid` tool.
|
|
||||||
|
|
||||||
### Available Commands
|
|
||||||
|
|
||||||
| Command | Domain (`action=`) | Quick Access |
|
|
||||||
|---------|-------------------|--------------|
|
|
||||||
| `/info` | `system` | System information, metrics, UPS, network |
|
|
||||||
| `/array` | `array` | Parity checks, array state, disk operations |
|
|
||||||
| `/storage` | `disk` | Shares, disks, log files |
|
|
||||||
| `/docker` | `docker` | Container lifecycle and network inspection |
|
|
||||||
| `/vm` | `vm` | Virtual machine lifecycle |
|
|
||||||
| `/notifications` | `notification` | Alert management |
|
|
||||||
| `/rclone` | `rclone` | Cloud storage remotes |
|
|
||||||
| `/users` | `user` | Current user query |
|
|
||||||
| `/keys` | `key` | API key management |
|
|
||||||
| `/health` | `health` | System health checks and setup |
|
|
||||||
| `/settings` | `setting` | System settings configuration |
|
|
||||||
|
|
||||||
### Example Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# System monitoring
|
|
||||||
/info overview
|
|
||||||
/health check
|
|
||||||
/storage shares
|
|
||||||
|
|
||||||
# Container management
|
|
||||||
/docker list
|
|
||||||
/docker start plex
|
|
||||||
|
|
||||||
# VM operations
|
|
||||||
/vm list
|
|
||||||
/vm start windows-10
|
|
||||||
|
|
||||||
# Notifications
|
|
||||||
/notifications list
|
|
||||||
/notifications archive_all
|
|
||||||
|
|
||||||
# API key management
|
|
||||||
/keys list
|
|
||||||
/keys create "Automation Key" "For CI/CD"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Features
|
|
||||||
|
|
||||||
Each slash command provides:
|
|
||||||
- **Comprehensive documentation** of all available actions
|
|
||||||
- **Argument hints** for required parameters
|
|
||||||
- **Safety warnings** for destructive operations (⚠️)
|
|
||||||
- **Usage examples** for common scenarios
|
|
||||||
- **Action categorization** (Query, Lifecycle, Management, Destructive)
|
|
||||||
|
|
||||||
Run any command without arguments to see full documentation, or type `/help` to list all available commands.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -362,6 +312,8 @@ Run any command without arguments to see full documentation, or type `/help` to
|
|||||||
unraid-mcp/
|
unraid-mcp/
|
||||||
├── unraid_mcp/ # Main package
|
├── unraid_mcp/ # Main package
|
||||||
│ ├── main.py # Entry point
|
│ ├── main.py # Entry point
|
||||||
|
│ ├── server.py # FastMCP server setup
|
||||||
|
│ ├── version.py # Version management (importlib.metadata)
|
||||||
│ ├── config/ # Configuration management
|
│ ├── config/ # Configuration management
|
||||||
│ │ ├── settings.py # Environment & settings
|
│ │ ├── settings.py # Environment & settings
|
||||||
│ │ └── logging.py # Logging setup
|
│ │ └── logging.py # Logging setup
|
||||||
@@ -369,18 +321,34 @@ unraid-mcp/
|
|||||||
│ │ ├── client.py # GraphQL client
|
│ │ ├── client.py # GraphQL client
|
||||||
│ │ ├── exceptions.py # Custom exceptions
|
│ │ ├── exceptions.py # Custom exceptions
|
||||||
│ │ ├── guards.py # Destructive action guards
|
│ │ ├── guards.py # Destructive action guards
|
||||||
│ │ └── types.py # Shared data types
|
│ │ ├── setup.py # Interactive credential setup
|
||||||
|
│ │ ├── types.py # Shared data types
|
||||||
|
│ │ └── utils.py # Utility functions
|
||||||
│ ├── subscriptions/ # Real-time subscriptions
|
│ ├── subscriptions/ # Real-time subscriptions
|
||||||
│ │ ├── manager.py # Persistent WebSocket manager
|
│ │ ├── manager.py # Persistent WebSocket manager
|
||||||
│ │ ├── resources.py # MCP resources (unraid://live/*)
|
│ │ ├── resources.py # MCP resources (unraid://live/*)
|
||||||
│ │ ├── snapshot.py # Transient subscribe_once helpers
|
│ │ ├── snapshot.py # Transient subscribe_once helpers
|
||||||
│ │ └── diagnostics.py # Diagnostic tools
|
│ │ ├── queries.py # Subscription query constants
|
||||||
│ ├── tools/ # Single consolidated tool (~108 actions)
|
│ │ ├── diagnostics.py # Diagnostic tools
|
||||||
│ │ └── unraid.py # All 15 domains in one file
|
│ │ └── utils.py # Subscription utility functions
|
||||||
│ └── server.py # FastMCP server setup
|
│ └── tools/ # Single consolidated tool (~108 actions)
|
||||||
├── commands/ # 11 custom slash commands
|
│ └── unraid.py # All 15 domains in one file
|
||||||
├── logs/ # Log files (auto-created)
|
├── tests/ # Test suite
|
||||||
└── docker-compose.yml # Docker Compose deployment
|
│ ├── conftest.py # Shared fixtures
|
||||||
|
│ ├── test_*.py # Unit tests (per domain)
|
||||||
|
│ ├── http_layer/ # httpx-level request tests
|
||||||
|
│ ├── integration/ # WebSocket lifecycle tests
|
||||||
|
│ ├── safety/ # Destructive action guard tests
|
||||||
|
│ └── schema/ # GraphQL query validation
|
||||||
|
├── docs/ # Documentation & API references
|
||||||
|
├── scripts/ # Build and utility scripts
|
||||||
|
├── skills/unraid/ # Claude skill assets
|
||||||
|
├── .claude-plugin/ # Plugin manifest & marketplace config
|
||||||
|
├── .env.example # Environment template
|
||||||
|
├── Dockerfile # Container image definition
|
||||||
|
├── docker-compose.yml # Docker Compose deployment
|
||||||
|
├── pyproject.toml # Project config & dependencies
|
||||||
|
└── logs/ # Log files (auto-created, gitignored)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Code Quality Commands
|
### Code Quality Commands
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
> **⚠️ DEVELOPER REFERENCE ONLY** — This file documents the raw GraphQL API schema for development and maintenance purposes (adding new queries/mutations). Do NOT use these curl/GraphQL examples for MCP tool usage. Use `unraid(action=..., subaction=...)` calls instead. See `SKILL.md` for the correct calling convention.
|
|
||||||
|
|
||||||
# Unraid API - Complete Reference Guide
|
# Unraid API - Complete Reference Guide
|
||||||
|
|
||||||
|
> **⚠️ DEVELOPER REFERENCE ONLY** — This file documents the raw GraphQL API schema for development and maintenance purposes (adding new queries/mutations). Do NOT use these curl/GraphQL examples for MCP tool usage. Use `unraid(action=..., subaction=...)` calls instead. See `SKILL.md` for the correct calling convention.
|
||||||
|
|
||||||
**Tested on:** Unraid 7.2 x86_64
|
**Tested on:** Unraid 7.2 x86_64
|
||||||
**Date:** 2026-01-21
|
**Date:** 2026-01-21
|
||||||
**API Type:** GraphQL
|
**API Type:** GraphQL
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ All operations use: `unraid(action="<domain>", subaction="<operation>", [params]
|
|||||||
## Most Common Operations
|
## Most Common Operations
|
||||||
|
|
||||||
### Health & Status
|
### Health & Status
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="health", subaction="setup") # First-time credential setup
|
unraid(action="health", subaction="setup") # First-time credential setup
|
||||||
unraid(action="health", subaction="check") # Full health check
|
unraid(action="health", subaction="check") # Full health check
|
||||||
unraid(action="health", subaction="test_connection") # Quick connectivity test
|
unraid(action="health", subaction="test_connection") # Quick connectivity test
|
||||||
@@ -15,7 +16,8 @@ unraid(action="system", subaction="online") # Online status
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Array & Disks
|
### Array & Disks
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="system", subaction="array") # Array status overview
|
unraid(action="system", subaction="array") # Array status overview
|
||||||
unraid(action="disk", subaction="disks") # All disks with temps & health
|
unraid(action="disk", subaction="disks") # All disks with temps & health
|
||||||
unraid(action="array", subaction="parity_status") # Current parity check
|
unraid(action="array", subaction="parity_status") # Current parity check
|
||||||
@@ -25,14 +27,17 @@ unraid(action="array", subaction="stop_array", confirm=True) # ⚠️ Stop
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Logs
|
### Logs
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="disk", subaction="log_files") # List available logs
|
unraid(action="disk", subaction="log_files") # List available logs
|
||||||
unraid(action="disk", subaction="logs", path="syslog", lines=50) # Read syslog
|
unraid(action="disk", subaction="logs", log_path="syslog", tail_lines=50) # Read syslog
|
||||||
unraid(action="disk", subaction="logs", path="/var/log/syslog") # Full path also works
|
unraid(action="disk", subaction="logs", log_path="/var/log/syslog") # Full path also works
|
||||||
|
unraid(action="live", subaction="log_tail", log_path="/var/log/syslog") # Live tail
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Containers
|
### Docker Containers
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="docker", subaction="list")
|
unraid(action="docker", subaction="list")
|
||||||
unraid(action="docker", subaction="details", container_id="plex")
|
unraid(action="docker", subaction="details", container_id="plex")
|
||||||
unraid(action="docker", subaction="start", container_id="nginx")
|
unraid(action="docker", subaction="start", container_id="nginx")
|
||||||
@@ -42,7 +47,8 @@ unraid(action="docker", subaction="networks")
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Virtual Machines
|
### Virtual Machines
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="vm", subaction="list")
|
unraid(action="vm", subaction="list")
|
||||||
unraid(action="vm", subaction="details", vm_id="<id>")
|
unraid(action="vm", subaction="details", vm_id="<id>")
|
||||||
unraid(action="vm", subaction="start", vm_id="<id>")
|
unraid(action="vm", subaction="start", vm_id="<id>")
|
||||||
@@ -52,37 +58,41 @@ unraid(action="vm", subaction="force_stop", vm_id="<id>", confirm=True) # ⚠
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Notifications
|
### Notifications
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="notification", subaction="overview")
|
unraid(action="notification", subaction="overview")
|
||||||
unraid(action="notification", subaction="unread")
|
unraid(action="notification", subaction="list", list_type="UNREAD", limit=10)
|
||||||
unraid(action="notification", subaction="list", filter="UNREAD", limit=10)
|
|
||||||
unraid(action="notification", subaction="archive", notification_id="<id>")
|
unraid(action="notification", subaction="archive", notification_id="<id>")
|
||||||
unraid(action="notification", subaction="create", title="Test", subject="Subject",
|
unraid(action="notification", subaction="create", title="Test", subject="Subject",
|
||||||
description="Body", importance="normal")
|
description="Body", importance="normal")
|
||||||
```
|
```
|
||||||
|
|
||||||
### API Keys
|
### API Keys
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="key", subaction="list")
|
unraid(action="key", subaction="list")
|
||||||
unraid(action="key", subaction="create", name="my-key", roles=["viewer"])
|
unraid(action="key", subaction="create", name="my-key", roles=["viewer"])
|
||||||
unraid(action="key", subaction="delete", key_id="<id>", confirm=True) # ⚠️
|
unraid(action="key", subaction="delete", key_id="<id>", confirm=True) # ⚠️
|
||||||
```
|
```
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="plugin", subaction="list")
|
unraid(action="plugin", subaction="list")
|
||||||
unraid(action="plugin", subaction="add", names=["community.applications"])
|
unraid(action="plugin", subaction="add", names=["community.applications"])
|
||||||
unraid(action="plugin", subaction="remove", names=["old.plugin"], confirm=True) # ⚠️
|
unraid(action="plugin", subaction="remove", names=["old.plugin"], confirm=True) # ⚠️
|
||||||
```
|
```
|
||||||
|
|
||||||
### rclone
|
### rclone
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="rclone", subaction="list_remotes")
|
unraid(action="rclone", subaction="list_remotes")
|
||||||
unraid(action="rclone", subaction="delete_remote", name="<remote>", confirm=True) # ⚠️
|
unraid(action="rclone", subaction="delete_remote", name="<remote>", confirm=True) # ⚠️
|
||||||
```
|
```
|
||||||
|
|
||||||
### Live Subscriptions (real-time)
|
### Live Subscriptions (real-time)
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="live", subaction="cpu")
|
unraid(action="live", subaction="cpu")
|
||||||
unraid(action="live", subaction="memory")
|
unraid(action="live", subaction="memory")
|
||||||
unraid(action="live", subaction="parity_progress")
|
unraid(action="live", subaction="parity_progress")
|
||||||
@@ -90,6 +100,7 @@ unraid(action="live", subaction="log_tail")
|
|||||||
unraid(action="live", subaction="notification_feed")
|
unraid(action="live", subaction="notification_feed")
|
||||||
unraid(action="live", subaction="ups_status")
|
unraid(action="live", subaction="ups_status")
|
||||||
```
|
```
|
||||||
|
|
||||||
> Returns `{"status": "connecting"}` on first call — retry momentarily.
|
> Returns `{"status": "connecting"}` on first call — retry momentarily.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
**Error:** `CredentialsNotConfiguredError` or message containing `~/.unraid-mcp/.env`
|
**Error:** `CredentialsNotConfiguredError` or message containing `~/.unraid-mcp/.env`
|
||||||
|
|
||||||
**Fix:** Run setup to configure credentials interactively:
|
**Fix:** Run setup to configure credentials interactively:
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="health", subaction="setup")
|
unraid(action="health", subaction="setup")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -20,12 +21,14 @@ This writes `UNRAID_API_URL` and `UNRAID_API_KEY` to `~/.unraid-mcp/.env`. Re-ru
|
|||||||
**Diagnostic steps:**
|
**Diagnostic steps:**
|
||||||
|
|
||||||
1. Test basic connectivity:
|
1. Test basic connectivity:
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="health", subaction="test_connection")
|
unraid(action="health", subaction="test_connection")
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Full diagnostic report:
|
2. Full diagnostic report:
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="health", subaction="diagnose")
|
unraid(action="health", subaction="diagnose")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -57,7 +60,8 @@ unraid(action="health", subaction="diagnose")
|
|||||||
**Error:** `Action 'X' was not confirmed. Re-run with confirm=True to bypass elicitation.`
|
**Error:** `Action 'X' was not confirmed. Re-run with confirm=True to bypass elicitation.`
|
||||||
|
|
||||||
**Fix:** Add `confirm=True` to the call:
|
**Fix:** Add `confirm=True` to the call:
|
||||||
```
|
|
||||||
|
```python
|
||||||
unraid(action="array", subaction="stop_array", confirm=True)
|
unraid(action="array", subaction="stop_array", confirm=True)
|
||||||
unraid(action="vm", subaction="force_stop", vm_id="<id>", confirm=True)
|
unraid(action="vm", subaction="force_stop", vm_id="<id>", confirm=True)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# test-tools.sh — Integration smoke-test for unraid-mcp MCP server tools
|
# test-tools.sh — Integration smoke-test for unraid-mcp MCP server tools
|
||||||
#
|
#
|
||||||
# Exercises every non-destructive action using the consolidated `unraid` tool
|
# Exercises broad non-destructive smoke coverage of the consolidated `unraid` tool
|
||||||
# (action + subaction pattern). The server is launched ad-hoc via mcporter's
|
# (action + subaction pattern). The server is launched ad-hoc via mcporter's
|
||||||
# --stdio flag so no persistent process or registered server entry is required.
|
# --stdio flag so no persistent process or registered server entry is required.
|
||||||
#
|
#
|
||||||
@@ -298,6 +298,24 @@ skip_test() {
|
|||||||
SKIP_COUNT=$(( SKIP_COUNT + 1 ))
|
SKIP_COUNT=$(( SKIP_COUNT + 1 ))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Safe JSON payload builder
|
||||||
|
# Usage: _json_payload '<jq-template-with-$vars>' key1=value1 key2=value2 ...
|
||||||
|
# Uses jq --arg to safely encode shell values into JSON, preventing injection
|
||||||
|
# via special characters in variable values (e.g., quotes, backslashes).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
_json_payload() {
|
||||||
|
local template="${1:?template required}"; shift
|
||||||
|
local jq_args=()
|
||||||
|
local pair k v
|
||||||
|
for pair in "$@"; do
|
||||||
|
k="${pair%%=*}"
|
||||||
|
v="${pair#*=}"
|
||||||
|
jq_args+=(--arg "$k" "$v")
|
||||||
|
done
|
||||||
|
jq -n "${jq_args[@]}" "$template"
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# ID extractors
|
# ID extractors
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -441,9 +459,9 @@ suite_system() {
|
|||||||
ups_id="$(get_ups_id)" || ups_id=''
|
ups_id="$(get_ups_id)" || ups_id=''
|
||||||
if [[ -n "${ups_id}" ]]; then
|
if [[ -n "${ups_id}" ]]; then
|
||||||
run_test "system: ups_device" \
|
run_test "system: ups_device" \
|
||||||
"$(printf '{"action":"system","subaction":"ups_device","device_id":"%s"}' "${ups_id}")"
|
"$(_json_payload '{"action":"system","subaction":"ups_device","device_id":$v}' v="${ups_id}")"
|
||||||
run_test "system: ups_config" \
|
run_test "system: ups_config" \
|
||||||
"$(printf '{"action":"system","subaction":"ups_config","device_id":"%s"}' "${ups_id}")"
|
"$(_json_payload '{"action":"system","subaction":"ups_config","device_id":$v}' v="${ups_id}")"
|
||||||
else
|
else
|
||||||
skip_test "system: ups_device" "no UPS devices found"
|
skip_test "system: ups_device" "no UPS devices found"
|
||||||
skip_test "system: ups_config" "no UPS devices found"
|
skip_test "system: ups_config" "no UPS devices found"
|
||||||
@@ -469,7 +487,7 @@ suite_disk() {
|
|||||||
disk_id="$(get_disk_id)" || disk_id=''
|
disk_id="$(get_disk_id)" || disk_id=''
|
||||||
if [[ -n "${disk_id}" ]]; then
|
if [[ -n "${disk_id}" ]]; then
|
||||||
run_test "disk: disk_details" \
|
run_test "disk: disk_details" \
|
||||||
"$(printf '{"action":"disk","subaction":"disk_details","disk_id":"%s"}' "${disk_id}")"
|
"$(_json_payload '{"action":"disk","subaction":"disk_details","disk_id":$v}' v="${disk_id}")"
|
||||||
else
|
else
|
||||||
skip_test "disk: disk_details" "no disks found"
|
skip_test "disk: disk_details" "no disks found"
|
||||||
fi
|
fi
|
||||||
@@ -478,7 +496,7 @@ suite_disk() {
|
|||||||
log_path="$(get_log_path)" || log_path=''
|
log_path="$(get_log_path)" || log_path=''
|
||||||
if [[ -n "${log_path}" ]]; then
|
if [[ -n "${log_path}" ]]; then
|
||||||
run_test "disk: logs" \
|
run_test "disk: logs" \
|
||||||
"$(printf '{"action":"disk","subaction":"logs","log_path":"%s","tail_lines":20}' "${log_path}")"
|
"$(_json_payload '{"action":"disk","subaction":"logs","log_path":$v,"tail_lines":20}' v="${log_path}")"
|
||||||
else
|
else
|
||||||
skip_test "disk: logs" "no log files found"
|
skip_test "disk: logs" "no log files found"
|
||||||
fi
|
fi
|
||||||
@@ -495,7 +513,7 @@ suite_docker() {
|
|||||||
container_id="$(get_docker_id)" || container_id=''
|
container_id="$(get_docker_id)" || container_id=''
|
||||||
if [[ -n "${container_id}" ]]; then
|
if [[ -n "${container_id}" ]]; then
|
||||||
run_test "docker: details" \
|
run_test "docker: details" \
|
||||||
"$(printf '{"action":"docker","subaction":"details","container_id":"%s"}' "${container_id}")"
|
"$(_json_payload '{"action":"docker","subaction":"details","container_id":$v}' v="${container_id}")"
|
||||||
else
|
else
|
||||||
skip_test "docker: details" "no containers found"
|
skip_test "docker: details" "no containers found"
|
||||||
fi
|
fi
|
||||||
@@ -504,7 +522,7 @@ suite_docker() {
|
|||||||
network_id="$(get_network_id)" || network_id=''
|
network_id="$(get_network_id)" || network_id=''
|
||||||
if [[ -n "${network_id}" ]]; then
|
if [[ -n "${network_id}" ]]; then
|
||||||
run_test "docker: network_details" \
|
run_test "docker: network_details" \
|
||||||
"$(printf '{"action":"docker","subaction":"network_details","network_id":"%s"}' "${network_id}")"
|
"$(_json_payload '{"action":"docker","subaction":"network_details","network_id":$v}' v="${network_id}")"
|
||||||
else
|
else
|
||||||
skip_test "docker: network_details" "no networks found"
|
skip_test "docker: network_details" "no networks found"
|
||||||
fi
|
fi
|
||||||
@@ -520,7 +538,7 @@ suite_vm() {
|
|||||||
vm_id="$(get_vm_id)" || vm_id=''
|
vm_id="$(get_vm_id)" || vm_id=''
|
||||||
if [[ -n "${vm_id}" ]]; then
|
if [[ -n "${vm_id}" ]]; then
|
||||||
run_test "vm: details" \
|
run_test "vm: details" \
|
||||||
"$(printf '{"action":"vm","subaction":"details","vm_id":"%s"}' "${vm_id}")"
|
"$(_json_payload '{"action":"vm","subaction":"details","vm_id":$v}' v="${vm_id}")"
|
||||||
else
|
else
|
||||||
skip_test "vm: details" "no VMs found (or VM service unavailable)"
|
skip_test "vm: details" "no VMs found (or VM service unavailable)"
|
||||||
fi
|
fi
|
||||||
@@ -558,7 +576,7 @@ suite_key() {
|
|||||||
key_id="$(get_key_id)" || key_id=''
|
key_id="$(get_key_id)" || key_id=''
|
||||||
if [[ -n "${key_id}" ]]; then
|
if [[ -n "${key_id}" ]]; then
|
||||||
run_test "key: get" \
|
run_test "key: get" \
|
||||||
"$(printf '{"action":"key","subaction":"get","key_id":"%s"}' "${key_id}")"
|
"$(_json_payload '{"action":"key","subaction":"get","key_id":$v}' v="${key_id}")"
|
||||||
else
|
else
|
||||||
skip_test "key: get" "no API keys found"
|
skip_test "key: get" "no API keys found"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user