forked from HomeLab/unraid-mcp
refactor: remove Docker and HTTP transport support, fix hypothesis cache directory
This commit is contained in:
@@ -1,188 +0,0 @@
|
||||
# Authentication Setup Guide
|
||||
|
||||
This document covers both Google OAuth 2.0 and API key bearer token authentication for the Unraid MCP HTTP server. It explains how to protect the server using FastMCP's built-in `GoogleProvider` for OAuth, or a static bearer token for headless/machine access.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
By default the MCP server is **open** — any client on the network can call tools. Setting three environment variables enables Google OAuth 2.1 authentication: clients must complete a Google login flow before the server will execute any tool.
|
||||
|
||||
OAuth state (issued tokens, refresh tokens) is persisted to an encrypted file store at `~/.local/share/fastmcp/oauth-proxy/`, so sessions survive server restarts when `UNRAID_MCP_JWT_SIGNING_KEY` is set.
|
||||
|
||||
> **Transport requirement**: OAuth only works with HTTP transports (`streamable-http` or `sse`). It has no effect on `stdio` — the server logs a warning if you configure both.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Google account with access to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- MCP server reachable at a known URL from your browser (LAN IP, Tailscale IP, or public domain)
|
||||
- `UNRAID_MCP_TRANSPORT=streamable-http` (the default)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create a Google OAuth Client
|
||||
|
||||
1. Open [Google Cloud Console](https://console.cloud.google.com/) → **APIs & Services** → **Credentials**
|
||||
2. Click **Create Credentials** → **OAuth 2.0 Client ID**
|
||||
3. Application type: **Web application**
|
||||
4. Name: anything (e.g. `Unraid MCP`)
|
||||
5. **Authorized redirect URIs** — add exactly:
|
||||
```
|
||||
http://<your-server-ip>:6970/auth/callback
|
||||
```
|
||||
Replace `<your-server-ip>` with the IP/hostname your browser uses to reach the MCP server (e.g. `10.1.0.2`, `100.x.x.x` for Tailscale, or a domain name).
|
||||
6. Click **Create** — copy the **Client ID** and **Client Secret**
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Configure Environment Variables
|
||||
|
||||
Add these to `~/.unraid-mcp/.env` (the canonical credential file for all runtimes):
|
||||
|
||||
```bash
|
||||
# Google OAuth (optional — enables authentication)
|
||||
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=GOCSPX-your-client-secret
|
||||
|
||||
# Public base URL of this MCP server (must match the redirect URI above)
|
||||
UNRAID_MCP_BASE_URL=http://10.1.0.2:6970
|
||||
|
||||
# Stable JWT signing key — prevents token invalidation on server restart
|
||||
# Generate one: python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
UNRAID_MCP_JWT_SIGNING_KEY=your-64-char-hex-string
|
||||
```
|
||||
|
||||
**All four variables at once** (copy-paste template):
|
||||
|
||||
```bash
|
||||
cat >> ~/.unraid-mcp/.env <<'EOF'
|
||||
|
||||
# Google OAuth
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
UNRAID_MCP_BASE_URL=http://10.1.0.2:6970
|
||||
UNRAID_MCP_JWT_SIGNING_KEY=
|
||||
EOF
|
||||
```
|
||||
|
||||
Then fill in the blanks.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Generate a Stable JWT Signing Key
|
||||
|
||||
Without `UNRAID_MCP_JWT_SIGNING_KEY`, FastMCP derives a key on startup. Any server restart invalidates all existing tokens and forces every client to re-authenticate.
|
||||
|
||||
Generate a stable key once:
|
||||
|
||||
```bash
|
||||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||
```
|
||||
|
||||
Paste the output into `UNRAID_MCP_JWT_SIGNING_KEY`. This value never needs to change unless you intentionally want to invalidate all sessions.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Restart the Server
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker compose restart unraid-mcp
|
||||
|
||||
# Direct / uv
|
||||
uv run unraid-mcp-server
|
||||
```
|
||||
|
||||
On startup you should see:
|
||||
|
||||
```
|
||||
INFO [SERVER] Google OAuth enabled — base_url=http://10.1.0.2:6970, redirect_uri=http://10.1.0.2:6970/auth/callback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How Authentication Works
|
||||
|
||||
1. An MCP client connects to `http://<server>:6970/mcp`
|
||||
2. The server responds with a `401 Unauthorized` and an OAuth authorization URL
|
||||
3. The client opens the URL in a browser; the user logs in with Google
|
||||
4. Google redirects to `<UNRAID_MCP_BASE_URL>/auth/callback` with an authorization code
|
||||
5. FastMCP exchanges the code for tokens, issues a signed JWT, and returns it to the client
|
||||
6. The client includes the JWT in subsequent requests — the server validates it without hitting Google again
|
||||
7. Tokens persist to `~/.local/share/fastmcp/oauth-proxy/` — sessions survive server restarts
|
||||
|
||||
---
|
||||
|
||||
## Environment Variable Reference
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `GOOGLE_CLIENT_ID` | For OAuth | `""` | OAuth 2.0 Client ID from Google Cloud Console |
|
||||
| `GOOGLE_CLIENT_SECRET` | For OAuth | `""` | OAuth 2.0 Client Secret from Google Cloud Console |
|
||||
| `UNRAID_MCP_BASE_URL` | For OAuth | `""` | Public base URL of this server — must match the authorized redirect URI |
|
||||
| `UNRAID_MCP_JWT_SIGNING_KEY` | Recommended | auto-derived | Stable 32+ char secret for JWT signing — prevents token invalidation on restart |
|
||||
|
||||
OAuth is activated only when **all three** of `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `UNRAID_MCP_BASE_URL` are non-empty. Omit any one to run without authentication.
|
||||
|
||||
---
|
||||
|
||||
## Disabling OAuth
|
||||
|
||||
Remove (or empty) `GOOGLE_CLIENT_ID` from `~/.unraid-mcp/.env` and restart. The server reverts to unauthenticated mode and logs:
|
||||
|
||||
```
|
||||
WARNING [SERVER] No authentication configured — MCP server is open to all clients on the network.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`redirect_uri_mismatch` from Google**
|
||||
The redirect URI in Google Cloud Console must exactly match `<UNRAID_MCP_BASE_URL>/auth/callback` — same scheme, host, port, and path. Trailing slashes matter.
|
||||
|
||||
**Tokens invalidated after restart**
|
||||
Set `UNRAID_MCP_JWT_SIGNING_KEY` to a stable secret (see Step 3). Without it, FastMCP generates a new key on every start.
|
||||
|
||||
**`stdio` transport warning**
|
||||
OAuth requires an HTTP transport. Set `UNRAID_MCP_TRANSPORT=streamable-http` (the default) or `sse`.
|
||||
|
||||
**Client cannot reach the callback URL**
|
||||
`UNRAID_MCP_BASE_URL` must be the address your browser uses to reach the server — not `localhost` or `0.0.0.0`. Use the LAN IP, Tailscale IP, or a domain name.
|
||||
|
||||
**OAuth configured but server not starting**
|
||||
Check `logs/unraid-mcp.log` or `docker compose logs unraid-mcp` for startup errors.
|
||||
|
||||
---
|
||||
|
||||
## API Key Authentication (Alternative / Combined)
|
||||
|
||||
For machine-to-machine access (scripts, CI, other agents) without a browser-based OAuth flow, set `UNRAID_MCP_API_KEY`:
|
||||
|
||||
```bash
|
||||
# In ~/.unraid-mcp/.env
|
||||
UNRAID_MCP_API_KEY=your-secret-token
|
||||
```
|
||||
|
||||
Clients present it as a standard bearer token:
|
||||
|
||||
```
|
||||
Authorization: Bearer your-secret-token
|
||||
```
|
||||
|
||||
**Combining with Google OAuth**: set both `GOOGLE_CLIENT_ID` and `UNRAID_MCP_API_KEY`. The server activates `MultiAuth` and accepts either method — Google OAuth for interactive clients, API key for headless clients.
|
||||
|
||||
**Reusing the Unraid API key**: you can set `UNRAID_MCP_API_KEY` to the same value as `UNRAID_API_KEY` for simplicity. The two vars are kept separate so each concern has its own name.
|
||||
|
||||
**Standalone API key** (no Google OAuth): set only `UNRAID_MCP_API_KEY`. The server validates bearer tokens directly with no OAuth redirect flow.
|
||||
|
||||
---
|
||||
|
||||
## Security Notes
|
||||
|
||||
- OAuth protects the MCP HTTP interface — the Unraid GraphQL API itself still uses `UNRAID_API_KEY`
|
||||
- OAuth state files at `~/.local/share/fastmcp/oauth-proxy/` should be on a private filesystem; do not expose them
|
||||
- Restrict Google OAuth to specific accounts via the Google Cloud Console **OAuth consent screen** → **Test users** if you don't want to publish the app
|
||||
- `UNRAID_MCP_JWT_SIGNING_KEY` is a credential — store it in `~/.unraid-mcp/.env` (mode 600), never in source control
|
||||
@@ -1,14 +1,14 @@
|
||||
# Destructive Actions
|
||||
|
||||
**Last Updated:** 2026-03-16
|
||||
**Last Updated:** 2026-03-24
|
||||
**Total destructive actions:** 12 across 8 domains (single `unraid` tool)
|
||||
|
||||
All destructive actions require `confirm=True` at the call site. There is no additional environment variable gate — `confirm` is the sole guard.
|
||||
|
||||
> **mcporter commands below** use `$MCP_URL` (default: `http://localhost:6970/mcp`). Run `test-actions.sh` for automated non-destructive coverage; destructive actions are always skipped there and tested manually per the strategies below.
|
||||
> **mcporter commands below** use stdio transport. Run `test-tools.sh` for automated non-destructive coverage; destructive actions are always skipped there and tested manually per the strategies below.
|
||||
>
|
||||
> **Calling convention (v1.0.0+):** All operations use the single `unraid` tool with `action` (domain) + `subaction` (operation). For example:
|
||||
> `mcporter call --http-url "$MCP_URL" --tool unraid --args '{"action":"docker","subaction":"list"}'`
|
||||
> `mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid --args '{"action":"docker","subaction":"list"}'`
|
||||
|
||||
---
|
||||
|
||||
@@ -26,7 +26,7 @@ Stopping the array unmounts all shares and can interrupt running containers and
|
||||
```bash
|
||||
# Prerequisite: array must already be stopped; use a disk you intend to remove
|
||||
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"array","subaction":"remove_disk","disk_id":"<DISK_ID>","confirm":true}' --output json
|
||||
```
|
||||
|
||||
@@ -36,11 +36,11 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
|
||||
```bash
|
||||
# Discover disk IDs
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"disk","subaction":"disks"}' --output json
|
||||
|
||||
# Clear stats for a specific disk
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"array","subaction":"clear_disk_stats","disk_id":"<DISK_ID>","confirm":true}' --output json
|
||||
```
|
||||
|
||||
@@ -54,15 +54,15 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
# Prerequisite: create a minimal Alpine test VM in Unraid VM manager
|
||||
# (Alpine ISO, 512MB RAM, no persistent disk, name contains "mcp-test")
|
||||
|
||||
VID=$(mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
VID=$(mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"vm","subaction":"list"}' --output json \
|
||||
| python3 -c "import json,sys; vms=json.load(sys.stdin).get('vms',[]); print(next(v.get('uuid',v.get('id','')) for v in vms if 'mcp-test' in v.get('name','')))")
|
||||
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"vm\",\"subaction\":\"force_stop\",\"vm_id\":\"$VID\",\"confirm\":true}" --output json
|
||||
|
||||
# Verify: VM state should return to stopped
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"vm\",\"subaction\":\"details\",\"vm_id\":\"$VID\"}" --output json
|
||||
```
|
||||
|
||||
@@ -72,11 +72,11 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
|
||||
```bash
|
||||
# Same minimal Alpine test VM as above
|
||||
VID=$(mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
VID=$(mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"vm","subaction":"list"}' --output json \
|
||||
| python3 -c "import json,sys; vms=json.load(sys.stdin).get('vms',[]); print(next(v.get('uuid',v.get('id','')) for v in vms if 'mcp-test' in v.get('name','')))")
|
||||
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"vm\",\"subaction\":\"reset\",\"vm_id\":\"$VID\",\"confirm\":true}" --output json
|
||||
```
|
||||
|
||||
@@ -89,9 +89,9 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
```bash
|
||||
# 1. Create a test notification, then list to get the real stored ID (create response
|
||||
# ID is ULID-based; stored filename uses a unix timestamp, so IDs differ)
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"create","title":"mcp-test-delete","subject":"safe to delete","description":"MCP destructive action test","importance":"INFO"}' --output json
|
||||
NID=$(mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
NID=$(mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"list","notification_type":"UNREAD"}' --output json \
|
||||
| python3 -c "
|
||||
import json,sys
|
||||
@@ -100,11 +100,11 @@ matches=[n['id'] for n in reversed(notifs) if n.get('title')=='mcp-test-delete']
|
||||
print(matches[0] if matches else '')")
|
||||
|
||||
# 2. Delete it (notification_type required)
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"notification\",\"subaction\":\"delete\",\"notification_id\":\"$NID\",\"notification_type\":\"UNREAD\",\"confirm\":true}" --output json
|
||||
|
||||
# 3. Verify
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"list"}' --output json | python3 -c \
|
||||
"import json,sys; ns=[n for n in json.load(sys.stdin).get('notifications',[]) if 'mcp-test' in n.get('title','')]; print('clean' if not ns else ns)"
|
||||
```
|
||||
@@ -115,21 +115,21 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
|
||||
```bash
|
||||
# 1. Create and archive a test notification
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"create","title":"mcp-test-archive-wipe","subject":"archive me","description":"safe to delete","importance":"INFO"}' --output json
|
||||
AID=$(mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
AID=$(mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"list","notification_type":"UNREAD"}' --output json \
|
||||
| python3 -c "
|
||||
import json,sys
|
||||
notifs=json.load(sys.stdin).get('notifications',[])
|
||||
matches=[n['id'] for n in reversed(notifs) if n.get('title')=='mcp-test-archive-wipe']
|
||||
print(matches[0] if matches else '')")
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"notification\",\"subaction\":\"archive\",\"notification_id\":\"$AID\"}" --output json
|
||||
|
||||
# 2. Wipe all archived
|
||||
# NOTE: this deletes ALL archived notifications, not just the test one
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"notification","subaction":"delete_archived","confirm":true}' --output json
|
||||
```
|
||||
|
||||
@@ -144,15 +144,15 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
```bash
|
||||
# 1. Create a throwaway local remote (points to /tmp — no real data)
|
||||
# Parameters: name (str), provider_type (str), config_data (dict)
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"rclone","subaction":"create_remote","name":"mcp-test-remote","provider_type":"local","config_data":{"root":"/tmp"}}' --output json
|
||||
|
||||
# 2. Delete it
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"rclone","subaction":"delete_remote","name":"mcp-test-remote","confirm":true}' --output json
|
||||
|
||||
# 3. Verify
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"rclone","subaction":"list_remotes"}' --output json | python3 -c \
|
||||
"import json,sys; remotes=json.load(sys.stdin).get('remotes',[]); print('clean' if 'mcp-test-remote' not in remotes else 'FOUND — cleanup failed')"
|
||||
```
|
||||
@@ -167,16 +167,16 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
|
||||
```bash
|
||||
# 1. Create a test key (names cannot contain hyphens; ID is at key.id)
|
||||
KID=$(mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
KID=$(mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"key","subaction":"create","name":"mcp test key","roles":["VIEWER"]}' --output json \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin).get('key',{}).get('id',''))")
|
||||
|
||||
# 2. Delete it
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args "{\"action\":\"key\",\"subaction\":\"delete\",\"key_id\":\"$KID\",\"confirm\":true}" --output json
|
||||
|
||||
# 3. Verify
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"key","subaction":"list"}' --output json | python3 -c \
|
||||
"import json,sys; ks=json.load(sys.stdin).get('keys',[]); print('clean' if not any('mcp test key' in k.get('name','') for k in ks) else 'FOUND — cleanup failed')"
|
||||
```
|
||||
@@ -191,7 +191,7 @@ mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
# Prerequisite: create a dedicated test remote pointing away from real backup destination
|
||||
# (use rclone create_remote first, or configure mcp-test-remote manually)
|
||||
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"disk","subaction":"flash_backup","remote_name":"mcp-test-remote","source_path":"/boot","destination_path":"/flash-backup-test","confirm":true}' --output json
|
||||
```
|
||||
|
||||
@@ -217,7 +217,7 @@ Removing a plugin cannot be undone without a full re-install. Test via `tests/sa
|
||||
|
||||
```bash
|
||||
# If live testing is necessary (intentional removal only):
|
||||
mcporter call --http-url "$MCP_URL" --tool unraid \
|
||||
mcporter call --stdio-cmd "uv run unraid-mcp-server" --tool unraid \
|
||||
--args '{"action":"plugin","subaction":"remove","names":["<plugin-name>"],"confirm":true}' --output json
|
||||
```
|
||||
|
||||
|
||||
@@ -11,37 +11,77 @@ The marketplace catalog that lists all available plugins in this repository.
|
||||
|
||||
**Contents:**
|
||||
- Marketplace metadata (name, version, owner, repository)
|
||||
- Plugin catalog with the "unraid" skill
|
||||
- Plugin catalog with the "unraid" plugin
|
||||
- Categories and tags for discoverability
|
||||
|
||||
### 2. Plugin Manifest (`.claude-plugin/plugin.json`)
|
||||
The individual plugin configuration for the Unraid skill.
|
||||
The individual plugin configuration for the Unraid MCP server.
|
||||
|
||||
**Location:** `.claude-plugin/plugin.json`
|
||||
|
||||
**Contents:**
|
||||
- Plugin name, version, author
|
||||
- Plugin name (`unraid`), version (`1.1.2`), author
|
||||
- Repository and homepage links
|
||||
- Plugin-specific metadata
|
||||
- `mcpServers` block that configures the server to run via `uv run unraid-mcp-server` in stdio mode
|
||||
|
||||
### 3. Documentation
|
||||
- `.claude-plugin/README.md` - Marketplace installation guide
|
||||
- Updated root `README.md` with plugin installation section
|
||||
### 3. Validation Script
|
||||
- `scripts/validate-marketplace.sh` — Automated validation of marketplace structure
|
||||
|
||||
### 4. Validation Script
|
||||
- `scripts/validate-marketplace.sh` - Automated validation of marketplace structure
|
||||
## MCP Tools Exposed
|
||||
|
||||
The plugin registers **3 MCP tools**:
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `unraid` | Primary tool — `action` (domain) + `subaction` (operation) routing, ~107 subactions across 15 domains |
|
||||
| `diagnose_subscriptions` | Inspect WebSocket subscription connection states and errors |
|
||||
| `test_subscription_query` | Test a specific GraphQL subscription query (allowlisted fields only) |
|
||||
|
||||
### Calling Convention
|
||||
|
||||
All Unraid operations go through the single `unraid` tool:
|
||||
|
||||
```
|
||||
unraid(action="docker", subaction="list")
|
||||
unraid(action="system", subaction="overview")
|
||||
unraid(action="array", subaction="parity_status")
|
||||
unraid(action="vm", subaction="list")
|
||||
unraid(action="live", subaction="cpu")
|
||||
```
|
||||
|
||||
### Domains (action=)
|
||||
|
||||
| action | example subactions |
|
||||
|--------|--------------------|
|
||||
| `system` | overview, array, network, metrics, services, ups_devices |
|
||||
| `health` | check, test_connection, diagnose, setup |
|
||||
| `array` | parity_status, parity_start, start_array, add_disk |
|
||||
| `disk` | shares, disks, disk_details, logs |
|
||||
| `docker` | list, details, start, stop, restart |
|
||||
| `vm` | list, details, start, stop, pause, resume |
|
||||
| `notification` | overview, list, create, archive, archive_all |
|
||||
| `key` | list, get, create, update, delete |
|
||||
| `plugin` | list, add, remove |
|
||||
| `rclone` | list_remotes, config_form, create_remote |
|
||||
| `setting` | update, configure_ups |
|
||||
| `customization` | theme, set_theme, sso_enabled |
|
||||
| `oidc` | providers, configuration, validate_session |
|
||||
| `user` | me |
|
||||
| `live` | cpu, memory, array_state, log_tail, notification_feed |
|
||||
|
||||
Destructive subactions (e.g. `stop_array`, `force_stop`, `delete`) require `confirm=True`.
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Method 1: GitHub Distribution (Recommended for Users)
|
||||
|
||||
Once you push this to GitHub, users can install via:
|
||||
Once pushed to GitHub, users install via:
|
||||
|
||||
```bash
|
||||
# Add your marketplace
|
||||
# Add the marketplace
|
||||
/plugin marketplace add jmagar/unraid-mcp
|
||||
|
||||
# Install the Unraid skill
|
||||
# Install the Unraid plugin
|
||||
/plugin install unraid @unraid-mcp
|
||||
```
|
||||
|
||||
@@ -59,7 +99,7 @@ For testing locally before publishing:
|
||||
|
||||
### Method 3: Direct URL
|
||||
|
||||
Users can also install from a specific commit or branch:
|
||||
Install from a specific branch or commit:
|
||||
|
||||
```bash
|
||||
# From specific branch
|
||||
@@ -75,14 +115,14 @@ Users can also install from a specific commit or branch:
|
||||
unraid-mcp/
|
||||
├── .claude-plugin/ # Plugin manifest + marketplace manifest
|
||||
│ ├── plugin.json # Plugin configuration (name, version, mcpServers)
|
||||
│ ├── marketplace.json # Marketplace catalog
|
||||
│ └── README.md # Marketplace installation guide
|
||||
├── skills/unraid/ # Skill documentation and helpers
|
||||
│ ├── SKILL.md # Skill documentation
|
||||
│ ├── README.md # Plugin documentation
|
||||
│ ├── examples/ # Example scripts
|
||||
│ ├── scripts/ # Helper scripts
|
||||
│ └── references/ # API reference docs
|
||||
│ └── marketplace.json # Marketplace catalog
|
||||
├── unraid_mcp/ # Python package (the actual MCP server)
|
||||
│ ├── main.py # Entry point
|
||||
│ ├── server.py # FastMCP server registration
|
||||
│ ├── tools/unraid.py # Consolidated tool (all 3 tools registered here)
|
||||
│ ├── config/ # Settings management
|
||||
│ ├── core/ # GraphQL client, exceptions, shared types
|
||||
│ └── subscriptions/ # Real-time WebSocket subscription manager
|
||||
└── scripts/
|
||||
└── validate-marketplace.sh # Validation tool
|
||||
```
|
||||
@@ -90,15 +130,15 @@ unraid-mcp/
|
||||
## Marketplace Metadata
|
||||
|
||||
### Categories
|
||||
- `infrastructure` - Server management and monitoring tools
|
||||
- `infrastructure` — Server management and monitoring tools
|
||||
|
||||
### Tags
|
||||
- `unraid` - Unraid-specific functionality
|
||||
- `monitoring` - System monitoring capabilities
|
||||
- `homelab` - Homelab automation
|
||||
- `graphql` - GraphQL API integration
|
||||
- `docker` - Docker container management
|
||||
- `virtualization` - VM management
|
||||
- `unraid` — Unraid-specific functionality
|
||||
- `monitoring` — System monitoring capabilities
|
||||
- `homelab` — Homelab automation
|
||||
- `graphql` — GraphQL API integration
|
||||
- `docker` — Docker container management
|
||||
- `virtualization` — VM management
|
||||
|
||||
## Publishing Checklist
|
||||
|
||||
@@ -109,10 +149,10 @@ Before publishing to GitHub:
|
||||
./scripts/validate-marketplace.sh
|
||||
```
|
||||
|
||||
2. **Update Version Numbers**
|
||||
- Bump version in `.claude-plugin/marketplace.json`
|
||||
- Bump version in `.claude-plugin/plugin.json`
|
||||
- Update version in `README.md` if needed
|
||||
2. **Update Version Numbers** (must be in sync)
|
||||
- `pyproject.toml` → `version = "X.Y.Z"` under `[project]`
|
||||
- `.claude-plugin/plugin.json` → `"version": "X.Y.Z"`
|
||||
- `.claude-plugin/marketplace.json` → `"version"` in both `metadata` and `plugins[]`
|
||||
|
||||
3. **Test Locally**
|
||||
```bash
|
||||
@@ -123,33 +163,38 @@ Before publishing to GitHub:
|
||||
4. **Commit and Push**
|
||||
```bash
|
||||
git add .claude-plugin/
|
||||
git commit -m "feat: add Claude Code marketplace configuration"
|
||||
git commit -m "chore: bump marketplace to vX.Y.Z"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
5. **Create Release Tag** (Optional)
|
||||
5. **Create Release Tag**
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
git tag -a vX.Y.Z -m "Release vX.Y.Z"
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
## User Experience
|
||||
|
||||
After installation, users will:
|
||||
After installation, users can:
|
||||
|
||||
1. **See the skill in their skill list**
|
||||
```bash
|
||||
/skill list
|
||||
1. **Invoke Unraid operations directly in Claude Code**
|
||||
```
|
||||
unraid(action="system", subaction="overview")
|
||||
unraid(action="docker", subaction="list")
|
||||
unraid(action="health", subaction="check")
|
||||
```
|
||||
|
||||
2. **Access Unraid functionality directly**
|
||||
- Claude Code will automatically detect when to invoke the skill
|
||||
- Users can explicitly invoke with `/unraid`
|
||||
2. **Use the credential setup tool on first run**
|
||||
```
|
||||
unraid(action="health", subaction="setup")
|
||||
```
|
||||
This triggers elicitation to collect and persist credentials to `~/.unraid-mcp/.env`.
|
||||
|
||||
3. **Have access to all helper scripts**
|
||||
- Example scripts in `examples/`
|
||||
- Utility scripts in `scripts/`
|
||||
- API reference in `references/`
|
||||
3. **Monitor live data via subscriptions**
|
||||
```
|
||||
unraid(action="live", subaction="cpu")
|
||||
unraid(action="live", subaction="log_tail")
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
@@ -157,31 +202,21 @@ After installation, users will:
|
||||
|
||||
To release a new version:
|
||||
|
||||
1. Make changes to the plugin
|
||||
2. Update version in `.claude-plugin/plugin.json`
|
||||
3. Update marketplace catalog in `.claude-plugin/marketplace.json`
|
||||
4. Run validation: `./scripts/validate-marketplace.sh`
|
||||
5. Commit and push
|
||||
1. Make changes to the plugin code
|
||||
2. Update version in `pyproject.toml`, `.claude-plugin/plugin.json`, and `.claude-plugin/marketplace.json`
|
||||
3. Run validation: `./scripts/validate-marketplace.sh`
|
||||
4. Commit and push
|
||||
|
||||
Users with the plugin installed will see the update available and can upgrade with:
|
||||
Users with the plugin installed will see the update available and can upgrade:
|
||||
```bash
|
||||
/plugin update unraid
|
||||
```
|
||||
|
||||
### Adding More Plugins
|
||||
|
||||
To add additional plugins to this marketplace:
|
||||
|
||||
1. Create new plugin directory: `skills/new-plugin/`
|
||||
2. Add plugin manifest: `skills/new-plugin/.claude-plugin/plugin.json`
|
||||
3. Update marketplace catalog: add entry to `.plugins[]` array in `.claude-plugin/marketplace.json`
|
||||
4. Validate: `./scripts/validate-marketplace.sh`
|
||||
|
||||
## Support
|
||||
|
||||
- **Repository:** https://github.com/jmagar/unraid-mcp
|
||||
- **Issues:** https://github.com/jmagar/unraid-mcp/issues
|
||||
- **Documentation:** See `.claude-plugin/README.md` and `skills/unraid/README.md`
|
||||
- **Destructive Actions:** `docs/DESTRUCTIVE_ACTIONS.md`
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -198,5 +233,3 @@ This checks:
|
||||
- Plugin structure
|
||||
- Source path accuracy
|
||||
- Documentation completeness
|
||||
|
||||
All 17 checks must pass before publishing.
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
|
||||
This guide covers how to publish `unraid-mcp` to PyPI so it can be installed via `uvx` or `pip` from anywhere.
|
||||
|
||||
## Package Overview
|
||||
|
||||
**PyPI package name:** `unraid-mcp`
|
||||
**Entry point binary:** `unraid-mcp-server` (also aliased as `unraid-mcp`)
|
||||
**Current version:** `1.1.2`
|
||||
|
||||
The package ships a FastMCP server exposing **3 MCP tools**:
|
||||
- `unraid` — primary tool with `action` + `subaction` routing (~107 subactions, 15 domains)
|
||||
- `diagnose_subscriptions` — WebSocket subscription diagnostics
|
||||
- `test_subscription_query` — test individual GraphQL subscription queries
|
||||
|
||||
Tool call convention: `unraid(action="docker", subaction="list")`
|
||||
|
||||
### Version Sync Requirement
|
||||
|
||||
When bumping the version, **all three files must be updated together**:
|
||||
- `pyproject.toml` → `version = "X.Y.Z"` under `[project]`
|
||||
- `.claude-plugin/plugin.json` → `"version": "X.Y.Z"`
|
||||
- `.claude-plugin/marketplace.json` → `"version"` in both `metadata` and `plugins[]`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **PyPI Account**: Create accounts on both:
|
||||
@@ -40,7 +60,7 @@ Before publishing, update the version in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
version = "1.0.0" # Follow semantic versioning: MAJOR.MINOR.PATCH
|
||||
version = "1.1.2" # Follow semantic versioning: MAJOR.MINOR.PATCH
|
||||
```
|
||||
|
||||
**Semantic Versioning Guide:**
|
||||
@@ -82,8 +102,8 @@ uv run python -m build
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `dist/unraid_mcp-VERSION-py3-none-any.whl` (wheel)
|
||||
- `dist/unraid_mcp-VERSION.tar.gz` (source distribution)
|
||||
- `dist/unraid_mcp-1.1.2-py3-none-any.whl` (wheel)
|
||||
- `dist/unraid_mcp-1.1.2.tar.gz` (source distribution)
|
||||
|
||||
### 4. Validate the Package
|
||||
|
||||
@@ -156,7 +176,7 @@ UNRAID_API_URL=https://your-server uvx unraid-mcp-server
|
||||
**Benefits of uvx:**
|
||||
- No installation required
|
||||
- Automatic virtual environment management
|
||||
- Always uses the latest version (or specify version: `uvx unraid-mcp-server@1.0.0`)
|
||||
- Always uses the latest version (or specify version: `uvx unraid-mcp-server@1.1.2`)
|
||||
- Clean execution environment
|
||||
|
||||
## Automation with GitHub Actions (Future)
|
||||
|
||||
Reference in New Issue
Block a user