unraid-mcp 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. unraid_mcp-1.2.2/.env.example +38 -0
  2. unraid_mcp-1.2.2/.gitignore +90 -0
  3. unraid_mcp-1.2.2/AGENTS.md +1 -0
  4. unraid_mcp-1.2.2/CLAUDE.md +301 -0
  5. unraid_mcp-1.2.2/GEMINI.md +1 -0
  6. unraid_mcp-1.2.2/LICENSE +21 -0
  7. unraid_mcp-1.2.2/PKG-INFO +659 -0
  8. unraid_mcp-1.2.2/README.md +597 -0
  9. unraid_mcp-1.2.2/pyproject.toml +303 -0
  10. unraid_mcp-1.2.2/skills/unraid/README.md +160 -0
  11. unraid_mcp-1.2.2/skills/unraid/SKILL.md +332 -0
  12. unraid_mcp-1.2.2/skills/unraid/examples/disk-health.sh +36 -0
  13. unraid_mcp-1.2.2/skills/unraid/examples/read-logs.sh +47 -0
  14. unraid_mcp-1.2.2/skills/unraid/references/api-reference.md +969 -0
  15. unraid_mcp-1.2.2/skills/unraid/references/endpoints.md +51 -0
  16. unraid_mcp-1.2.2/skills/unraid/references/introspection-schema.md +3116 -0
  17. unraid_mcp-1.2.2/skills/unraid/references/quick-reference.md +125 -0
  18. unraid_mcp-1.2.2/skills/unraid/references/schema.graphql +3114 -0
  19. unraid_mcp-1.2.2/skills/unraid/references/troubleshooting.md +109 -0
  20. unraid_mcp-1.2.2/skills/unraid/scripts/dashboard.sh +229 -0
  21. unraid_mcp-1.2.2/skills/unraid/scripts/unraid-query.sh +159 -0
  22. unraid_mcp-1.2.2/skills/unraid/setup.sh +39 -0
  23. unraid_mcp-1.2.2/tests/conftest.py +65 -0
  24. unraid_mcp-1.2.2/tests/contract/__init__.py +0 -0
  25. unraid_mcp-1.2.2/tests/contract/test_response_contracts.py +976 -0
  26. unraid_mcp-1.2.2/tests/http_layer/__init__.py +0 -0
  27. unraid_mcp-1.2.2/tests/http_layer/test_request_construction.py +1287 -0
  28. unraid_mcp-1.2.2/tests/integration/__init__.py +0 -0
  29. unraid_mcp-1.2.2/tests/integration/test_subscriptions.py +922 -0
  30. unraid_mcp-1.2.2/tests/mcporter/README.md +163 -0
  31. unraid_mcp-1.2.2/tests/mcporter/test-destructive.sh +338 -0
  32. unraid_mcp-1.2.2/tests/mcporter/test-http.sh +456 -0
  33. unraid_mcp-1.2.2/tests/mcporter/test-tools.sh +814 -0
  34. unraid_mcp-1.2.2/tests/property/__init__.py +0 -0
  35. unraid_mcp-1.2.2/tests/property/test_input_validation.py +755 -0
  36. unraid_mcp-1.2.2/tests/safety/__init__.py +0 -0
  37. unraid_mcp-1.2.2/tests/safety/test_destructive_guards.py +414 -0
  38. unraid_mcp-1.2.2/tests/schema/__init__.py +0 -0
  39. unraid_mcp-1.2.2/tests/schema/test_query_validation.py +1025 -0
  40. unraid_mcp-1.2.2/tests/test_array.py +256 -0
  41. unraid_mcp-1.2.2/tests/test_auth.py +707 -0
  42. unraid_mcp-1.2.2/tests/test_client.py +720 -0
  43. unraid_mcp-1.2.2/tests/test_customization.py +74 -0
  44. unraid_mcp-1.2.2/tests/test_docker.py +219 -0
  45. unraid_mcp-1.2.2/tests/test_guards.py +104 -0
  46. unraid_mcp-1.2.2/tests/test_health.py +445 -0
  47. unraid_mcp-1.2.2/tests/test_info.py +306 -0
  48. unraid_mcp-1.2.2/tests/test_keys.py +137 -0
  49. unraid_mcp-1.2.2/tests/test_live.py +162 -0
  50. unraid_mcp-1.2.2/tests/test_live.sh +139 -0
  51. unraid_mcp-1.2.2/tests/test_notifications.py +328 -0
  52. unraid_mcp-1.2.2/tests/test_oidc.py +93 -0
  53. unraid_mcp-1.2.2/tests/test_plugins.py +63 -0
  54. unraid_mcp-1.2.2/tests/test_rclone.py +193 -0
  55. unraid_mcp-1.2.2/tests/test_resources.py +167 -0
  56. unraid_mcp-1.2.2/tests/test_review_regressions.py +92 -0
  57. unraid_mcp-1.2.2/tests/test_settings.py +141 -0
  58. unraid_mcp-1.2.2/tests/test_setup.py +511 -0
  59. unraid_mcp-1.2.2/tests/test_snapshot.py +125 -0
  60. unraid_mcp-1.2.2/tests/test_storage.py +356 -0
  61. unraid_mcp-1.2.2/tests/test_subscription_manager.py +156 -0
  62. unraid_mcp-1.2.2/tests/test_subscription_validation.py +131 -0
  63. unraid_mcp-1.2.2/tests/test_users.py +75 -0
  64. unraid_mcp-1.2.2/tests/test_validation.py +58 -0
  65. unraid_mcp-1.2.2/tests/test_vm.py +195 -0
  66. unraid_mcp-1.2.2/unraid_mcp/__init__.py +6 -0
  67. unraid_mcp-1.2.2/unraid_mcp/config/__init__.py +1 -0
  68. unraid_mcp-1.2.2/unraid_mcp/config/logging.py +247 -0
  69. unraid_mcp-1.2.2/unraid_mcp/config/settings.py +183 -0
  70. unraid_mcp-1.2.2/unraid_mcp/core/__init__.py +1 -0
  71. unraid_mcp-1.2.2/unraid_mcp/core/auth.py +316 -0
  72. unraid_mcp-1.2.2/unraid_mcp/core/client.py +413 -0
  73. unraid_mcp-1.2.2/unraid_mcp/core/exceptions.py +77 -0
  74. unraid_mcp-1.2.2/unraid_mcp/core/guards.py +110 -0
  75. unraid_mcp-1.2.2/unraid_mcp/core/middleware_refs.py +27 -0
  76. unraid_mcp-1.2.2/unraid_mcp/core/setup.py +166 -0
  77. unraid_mcp-1.2.2/unraid_mcp/core/types.py +27 -0
  78. unraid_mcp-1.2.2/unraid_mcp/core/utils.py +94 -0
  79. unraid_mcp-1.2.2/unraid_mcp/core/validation.py +24 -0
  80. unraid_mcp-1.2.2/unraid_mcp/main.py +57 -0
  81. unraid_mcp-1.2.2/unraid_mcp/server.py +280 -0
  82. unraid_mcp-1.2.2/unraid_mcp/subscriptions/__init__.py +1 -0
  83. unraid_mcp-1.2.2/unraid_mcp/subscriptions/diagnostics.py +292 -0
  84. unraid_mcp-1.2.2/unraid_mcp/subscriptions/manager.py +757 -0
  85. unraid_mcp-1.2.2/unraid_mcp/subscriptions/queries.py +52 -0
  86. unraid_mcp-1.2.2/unraid_mcp/subscriptions/resources.py +154 -0
  87. unraid_mcp-1.2.2/unraid_mcp/subscriptions/snapshot.py +162 -0
  88. unraid_mcp-1.2.2/unraid_mcp/subscriptions/utils.py +110 -0
  89. unraid_mcp-1.2.2/unraid_mcp/tools/__init__.py +19 -0
  90. unraid_mcp-1.2.2/unraid_mcp/tools/_array.py +110 -0
  91. unraid_mcp-1.2.2/unraid_mcp/tools/_customization.py +60 -0
  92. unraid_mcp-1.2.2/unraid_mcp/tools/_disk.py +190 -0
  93. unraid_mcp-1.2.2/unraid_mcp/tools/_docker.py +183 -0
  94. unraid_mcp-1.2.2/unraid_mcp/tools/_health.py +172 -0
  95. unraid_mcp-1.2.2/unraid_mcp/tools/_key.py +130 -0
  96. unraid_mcp-1.2.2/unraid_mcp/tools/_live.py +120 -0
  97. unraid_mcp-1.2.2/unraid_mcp/tools/_notification.py +195 -0
  98. unraid_mcp-1.2.2/unraid_mcp/tools/_oidc.py +70 -0
  99. unraid_mcp-1.2.2/unraid_mcp/tools/_plugin.py +76 -0
  100. unraid_mcp-1.2.2/unraid_mcp/tools/_rclone.py +126 -0
  101. unraid_mcp-1.2.2/unraid_mcp/tools/_setting.py +136 -0
  102. unraid_mcp-1.2.2/unraid_mcp/tools/_system.py +268 -0
  103. unraid_mcp-1.2.2/unraid_mcp/tools/_user.py +38 -0
  104. unraid_mcp-1.2.2/unraid_mcp/tools/_vm.py +90 -0
  105. unraid_mcp-1.2.2/unraid_mcp/tools/unraid.py +479 -0
  106. unraid_mcp-1.2.2/unraid_mcp/version.py +11 -0
@@ -0,0 +1,38 @@
1
+ # Unraid MCP Server Configuration
2
+ # =================================
3
+
4
+ # Core API Configuration (Required)
5
+ # ---------------------------------
6
+ UNRAID_API_KEY=your_api_key
7
+ UNRAID_API_URL=http://your-unraid-server
8
+
9
+ # MCP Server Settings
10
+ # -------------------
11
+ # Default transport is streamable-http (v1.2.0+).
12
+ # Use stdio for Claude Desktop / local process-based clients.
13
+ # Options: streamable-http (default), stdio, sse (deprecated)
14
+ UNRAID_MCP_HOST=0.0.0.0
15
+ UNRAID_MCP_PORT=6970
16
+ UNRAID_MCP_TRANSPORT=streamable-http
17
+
18
+ # HTTP Bearer Token Authentication (streamable-http / sse transports)
19
+ # -------------------------------------------------------------------
20
+ # Generate a token and paste it here AND into the plugin's userConfig field:
21
+ # openssl rand -hex 32
22
+ # Required when UNRAID_MCP_TRANSPORT is not "stdio" and UNRAID_MCP_DISABLE_HTTP_AUTH is not "true".
23
+ UNRAID_MCP_BEARER_TOKEN=your_bearer_token
24
+
25
+ # Safety flags
26
+ # ------------
27
+ UNRAID_MCP_ALLOW_DESTRUCTIVE=false
28
+ UNRAID_MCP_ALLOW_YOLO=false
29
+ UNRAID_MCP_DISABLE_HTTP_AUTH=false
30
+
31
+ # Docker user / network
32
+ # ---------------------
33
+ # DOCKER_NETWORK is optional. If set, the container joins that external network.
34
+ # Leave blank (or unset) to use only the default bridge network.
35
+ # Create the network first: docker network create <name>
36
+ DOCKER_NETWORK=
37
+ PGID=1000
38
+ PUID=1000
@@ -0,0 +1,90 @@
1
+ # ── Secrets ──────────────────────────────────────────────────────────────────
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # ── Runtime artifacts ────────────────────────────────────────────────────────
7
+ logs/
8
+ backups/
9
+ *.log
10
+ *.pid
11
+ *.bak
12
+ *.bak-*
13
+
14
+ # ── Claude Code / AI tooling ────────────────────────────────────────────────
15
+ .claude/settings.local.json
16
+ .claude/worktrees/
17
+ .omc/
18
+ .lavra/
19
+ .beads/
20
+ .dolt/
21
+ *.db
22
+ *.db-shm
23
+ *.db-wal
24
+ .beads-credential-key
25
+ .serena/
26
+ .worktrees/
27
+ .full-review/
28
+ .full-review-archive-*
29
+ .bivvy
30
+
31
+ # ── IDE / editor ─────────────────────────────────────────────────────────────
32
+ .vscode/
33
+ .cursor/
34
+ .windsurf/
35
+ .1code/
36
+ .idea/
37
+ .zed/
38
+ *.iml
39
+ *.swp
40
+ *.swo
41
+ *~
42
+ .emdash.json
43
+
44
+ # ── OS generated ─────────────────────────────────────────────────────────────
45
+ .DS_Store
46
+ Thumbs.db
47
+
48
+ # ── Caches (ALL tool caches go here) ─────────────────────────────────────────
49
+ .cache/
50
+
51
+ # ── Documentation artifacts (session/plan docs, not reference) ───────────────
52
+ .docs/
53
+ docs/plans/
54
+ docs/sessions/
55
+ docs/reports/
56
+ docs/research/
57
+ docs/superpowers/
58
+ specs/
59
+
60
+ # ── Python ───────────────────────────────────────────────────────────────────
61
+ .venv/
62
+ __pycache__/
63
+ *.py[oc]
64
+ *.egg-info/
65
+ *.egg
66
+ build/
67
+ dist/
68
+ sdist/
69
+ wheels/
70
+ pip-wheel-metadata/
71
+ *.whl
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ .ruff_cache/
75
+ .ty_cache/
76
+ .mypy_cache/
77
+ .pytype/
78
+ .pyre/
79
+ .pyright/
80
+ htmlcov/
81
+ .coverage
82
+ .coverage.*
83
+ coverage.xml
84
+ .tox/
85
+ .nox/
86
+ pip-log.txt
87
+
88
+ # ── Repo-specific ────────────────────────────────────────────────────────────
89
+ /DESTRUCTIVE_ACTIONS.md
90
+ client_secret_*.apps.googleusercontent.com.json
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,301 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+ This is an MCP (Model Context Protocol) server that provides tools to interact with an Unraid server's GraphQL API. The server is built using FastMCP with a **modular architecture** consisting of separate packages for configuration, core functionality, subscriptions, and tools.
7
+
8
+ ## Development Commands
9
+
10
+ ### Setup
11
+ ```bash
12
+ # Initialize uv virtual environment and install dependencies
13
+ uv sync
14
+
15
+ # Install dev dependencies
16
+ uv sync --group dev
17
+ ```
18
+
19
+ ### Running the Server
20
+ ```bash
21
+ # Local development with uv (recommended)
22
+ uv run unraid-mcp-server
23
+
24
+ # Direct module execution
25
+ uv run -m unraid_mcp.main
26
+ ```
27
+
28
+ ### Code Quality
29
+ ```bash
30
+ # Lint and format with ruff
31
+ uv run ruff check unraid_mcp/
32
+ uv run ruff format unraid_mcp/
33
+
34
+ # Type checking with ty (Astral's fast type checker)
35
+ uv run ty check unraid_mcp/
36
+
37
+ # Run tests
38
+ uv run pytest
39
+ ```
40
+
41
+ ### Environment Setup
42
+ Copy `.env.example` to `.env` and configure:
43
+
44
+ **Required:**
45
+ - `UNRAID_API_URL`: Unraid GraphQL endpoint
46
+ - `UNRAID_API_KEY`: Unraid API key
47
+
48
+ **Server:**
49
+ - `UNRAID_MCP_LOG_LEVEL`: Log verbosity (default: INFO)
50
+ - `UNRAID_MCP_LOG_FILE`: Log filename in logs/ (default: unraid-mcp.log)
51
+
52
+ **SSL/TLS:**
53
+ - `UNRAID_VERIFY_SSL`: SSL verification (default: true; set `false` for self-signed certs)
54
+
55
+ **Subscriptions:**
56
+ - `UNRAID_AUTO_START_SUBSCRIPTIONS`: Auto-start live subscriptions on startup (default: true)
57
+ - `UNRAID_MAX_RECONNECT_ATTEMPTS`: WebSocket reconnect limit (default: 10)
58
+
59
+ **Credentials override:**
60
+ - `UNRAID_CREDENTIALS_DIR`: Override the `~/.unraid-mcp/` credentials directory path
61
+
62
+ ## Architecture
63
+
64
+ ### Core Components
65
+ - **Main Server**: `unraid_mcp/server.py` - Modular MCP server with FastMCP integration
66
+ - **Entry Point**: `unraid_mcp/main.py` - Application entry point and startup logic
67
+ - **Configuration**: `unraid_mcp/config/` - Settings management and logging configuration
68
+ - **Core Infrastructure**: `unraid_mcp/core/` - GraphQL client, exceptions, and shared types
69
+ - `guards.py` — destructive action gating via MCP elicitation
70
+ - `utils.py` — shared helpers (`safe_get`, `safe_display_url`, path validation)
71
+ - `setup.py` — elicitation-based credential setup flow
72
+ - **Subscriptions**: `unraid_mcp/subscriptions/` - Real-time WebSocket subscriptions and diagnostics
73
+ - **Tools**: `unraid_mcp/tools/` - Domain-specific tool implementations
74
+ - **GraphQL Client**: Uses httpx for async HTTP requests to Unraid API
75
+ - **Version Helper**: `unraid_mcp/version.py` - Reads version from package metadata via importlib
76
+
77
+ ### Key Design Patterns
78
+ - **Consolidated Action Pattern**: Each tool uses `action: Literal[...]` parameter to expose multiple operations via a single MCP tool, reducing context window usage
79
+ - **Pre-built Query Dicts**: `QUERIES` and `MUTATIONS` dicts prevent GraphQL injection and organize operations
80
+ - **Destructive Action Safety**: `DESTRUCTIVE_ACTIONS` sets require `confirm=True` for dangerous operations
81
+ - **Modular Architecture**: Clean separation of concerns across focused modules
82
+ - **Error Handling**: Uses ToolError for user-facing errors, detailed logging for debugging
83
+ - **Timeout Management**: Custom timeout configurations for different query types (90s for disk ops)
84
+ - **Data Processing**: Tools return both human-readable summaries and detailed raw data
85
+ - **Health Monitoring**: Comprehensive health check tool for system monitoring
86
+ - **Real-time Subscriptions**: WebSocket-based live data streaming
87
+ - **Persistent Subscription Manager**: `live` action subactions use a shared `SubscriptionManager`
88
+ that maintains persistent WebSocket connections. Resources serve cached data via
89
+ `subscription_manager.get_resource_data(action)`. A "connecting" placeholder is returned
90
+ while the subscription starts — callers should retry in a moment. When
91
+ `UNRAID_AUTO_START_SUBSCRIPTIONS=false`, resources fall back to on-demand `subscribe_once`.
92
+
93
+ ### Tool Categories (3 Tools: 1 Primary + 2 Diagnostic)
94
+
95
+ The server registers **3 MCP tools**:
96
+ - **`unraid`** — primary tool with `action` (domain) + `subaction` (operation) routing, 107 subactions. Call it as `unraid(action="docker", subaction="list")`.
97
+ - **`diagnose_subscriptions`** — inspect subscription connection states, errors, and WebSocket URLs.
98
+ - **`test_subscription_query`** — test a specific GraphQL subscription query (allowlisted fields only).
99
+
100
+ | action | subactions |
101
+ |--------|-----------|
102
+ | **system** (18) | overview, array, network, registration, variables, metrics, services, display, config, online, owner, settings, server, servers, flash, ups_devices, ups_device, ups_config |
103
+ | **health** (4) | check, test_connection, diagnose, setup |
104
+ | **array** (13) | 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* |
105
+ | **disk** (6) | shares, disks, disk_details, log_files, logs, flash_backup* |
106
+ | **docker** (7) | list, details, start, stop, restart, networks, network_details |
107
+ | **vm** (9) | list, details, start, stop, pause, resume, force_stop*, reboot, reset* |
108
+ | **notification** (12) | overview, list, create, archive, mark_unread, recalculate, archive_all, archive_many, unarchive_many, unarchive_all, delete*, delete_archived* |
109
+ | **key** (7) | list, get, create, update, delete*, add_role, remove_role |
110
+ | **plugin** (3) | list, add, remove* |
111
+ | **rclone** (4) | list_remotes, config_form, create_remote, delete_remote* |
112
+ | **setting** (2) | update, configure_ups* |
113
+ | **customization** (5) | theme, public_theme, is_initial_setup, sso_enabled, set_theme |
114
+ | **oidc** (5) | providers, provider, configuration, public_providers, validate_session |
115
+ | **user** (1) | me |
116
+ | **live** (11) | cpu, memory, cpu_telemetry, array_state, parity_progress, ups_status, notifications_overview, notification_feed, log_tail, owner, server_status |
117
+
118
+ `*` = destructive, requires `confirm=True`
119
+
120
+ ### Destructive Actions (require `confirm=True`)
121
+ - **array**: stop_array, remove_disk, clear_disk_stats
122
+ - **vm**: force_stop, reset
123
+ - **notification**: delete, delete_archived
124
+ - **rclone**: delete_remote
125
+ - **key**: delete
126
+ - **disk**: flash_backup
127
+ - **setting**: configure_ups
128
+ - **plugin**: remove
129
+
130
+ ### Environment Variable Hierarchy
131
+ The server loads environment variables from multiple locations in order:
132
+ 1. `~/.unraid-mcp/.env` (primary — canonical credentials dir, all runtimes)
133
+ 2. `~/.unraid-mcp/.env.local` (local overrides, only used if primary is absent)
134
+ 3. `../.env.local` (project root local overrides)
135
+ 4. `../.env` (project root fallback)
136
+ 5. `unraid_mcp/.env` (last resort)
137
+
138
+ ### Error Handling Strategy
139
+ - GraphQL errors are converted to ToolError with descriptive messages
140
+ - HTTP errors include status codes and response details
141
+ - Network errors are caught and wrapped with connection context
142
+ - All errors are logged with full context for debugging
143
+
144
+ ### Middleware Chain
145
+ `server.py` wraps all tools in a 4-layer stack (order matters — outermost first):
146
+ 1. **LoggingMiddleware** — logs every `tools/call` and `resources/read` with duration
147
+ 2. **ErrorHandlingMiddleware** — converts unhandled exceptions to proper MCP errors
148
+ 3. **SlidingWindowRateLimitingMiddleware** — 540 req/min sliding window
149
+ 4. **ResponseLimitingMiddleware** — truncates responses > 512 KB with a clear suffix
150
+
151
+ Note: `ResponseCachingMiddleware` was removed — the consolidated `unraid` tool mixes reads and mutations under one name, making per-subaction cache exclusion impossible.
152
+
153
+ ### Performance Considerations
154
+ - Increased timeouts for disk operations (90s read timeout)
155
+ - Selective queries to avoid GraphQL type overflow issues
156
+ - Optional caching controls for Docker container queries
157
+ - Log file overwrite at 10MB cap to prevent disk space issues
158
+
159
+ ## Critical Gotchas
160
+
161
+ ### Mutation Handler Ordering
162
+ **Mutation handlers MUST return before the domain query dict lookup.** Mutations are not in the domain `_*_QUERIES` dicts (e.g., `_DOCKER_QUERIES`, `_ARRAY_QUERIES`) — reaching that line for a mutation subaction causes a `KeyError`. Always add early-return `if subaction == "mutation_name": ... return` blocks BEFORE the queries lookup.
163
+
164
+ ### Test Patching
165
+ - Patch at the **tool module level**: `unraid_mcp.tools.unraid.make_graphql_request` (not core)
166
+ - `conftest.py`'s `mock_graphql_request` patches the core module — wrong for tool-level tests
167
+ - Use `conftest.py`'s `make_tool_fn()` helper or local `_make_tool()` pattern
168
+
169
+ ### Test Suite Structure
170
+ ```
171
+ tests/
172
+ ├── conftest.py # Shared fixtures + make_tool_fn() helper
173
+ ├── test_*.py # Unit tests (mock at tool module level)
174
+ ├── http_layer/ # httpx-level request/response tests (respx)
175
+ ├── integration/ # WebSocket subscription lifecycle tests (slow)
176
+ ├── safety/ # Destructive action guard tests
177
+ ├── schema/ # GraphQL query validation (119 tests)
178
+ ├── contract/ # Response shape contract tests
179
+ └── property/ # Input validation property-based tests
180
+ ```
181
+
182
+ ### Running Targeted Tests
183
+ ```bash
184
+ uv run pytest tests/safety/ # Destructive action guards only
185
+ uv run pytest tests/schema/ # GraphQL query validation only
186
+ uv run pytest tests/http_layer/ # HTTP/httpx layer only
187
+ uv run pytest tests/test_docker.py # Single tool only
188
+ uv run pytest -x # Fail fast on first error
189
+ ```
190
+
191
+ ### Scripts
192
+ ```bash
193
+ # HTTP smoke-test against a live server (non-destructive actions, all domains)
194
+ ./tests/mcporter/test-actions.sh [MCP_URL] # default: http://localhost:6970/mcp
195
+
196
+ # stdio smoke-test, no running server needed (good for CI)
197
+ ./tests/mcporter/test-tools.sh [--parallel] [--timeout-ms N] [--verbose]
198
+
199
+ # Destructive action smoke-test (confirms guard blocks without confirm=True)
200
+ ./tests/mcporter/test-destructive.sh [MCP_URL]
201
+ ```
202
+ See `tests/mcporter/README.md` for transport differences and `docs/DESTRUCTIVE_ACTIONS.md` for exact destructive-action test commands.
203
+
204
+ ### API Reference Docs
205
+ - `docs/UNRAID_API_COMPLETE_REFERENCE.md` — Full GraphQL schema reference
206
+ - `docs/UNRAID_API_OPERATIONS.md` — All supported operations with examples
207
+ - `docs/MARKETPLACE.md` — Plugin marketplace listing and publishing guide
208
+ - `docs/PUBLISHING.md` — Step-by-step instructions for publishing to Claude plugin registry
209
+
210
+ Use these when adding new queries/mutations.
211
+
212
+ ### Version Bumps
213
+ When bumping the version, **always update both files** — they must stay in sync:
214
+ - `pyproject.toml` → `version = "X.Y.Z"` under `[project]`
215
+ - `.claude-plugin/plugin.json` → `"version": "X.Y.Z"`
216
+
217
+ ### Credential Storage (`~/.unraid-mcp/.env`)
218
+ All runtimes (plugin, direct `uv run`) load credentials from `~/.unraid-mcp/.env`.
219
+ - **Plugin/direct:** `unraid action=health subaction=setup` writes this file automatically via elicitation,
220
+ **Safe to re-run**: always prompts for confirmation before overwriting existing credentials,
221
+ whether the connection is working or not (failed probe may be a transient outage, not bad creds).
222
+ or manual: `mkdir -p ~/.unraid-mcp && cp .env.example ~/.unraid-mcp/.env` then edit.
223
+ - **No symlinks needed.** Version bumps do not affect this path.
224
+ - **Permissions:** dir=700, file=600 (set automatically by elicitation; set manually if
225
+ using `cp`: `chmod 700 ~/.unraid-mcp && chmod 600 ~/.unraid-mcp/.env`).
226
+
227
+ ### Symlinks
228
+ `AGENTS.md` and `GEMINI.md` are symlinks to `CLAUDE.md` for Codex/Gemini compatibility:
229
+ ```bash
230
+ ln -sf CLAUDE.md AGENTS.md && ln -sf CLAUDE.md GEMINI.md
231
+ ```
232
+
233
+ <!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f -->
234
+ ## Beads Issue Tracker
235
+
236
+ This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
237
+
238
+ ### Quick Reference
239
+
240
+ ```bash
241
+ bd ready # Find available work
242
+ bd show <id> # View issue details
243
+ bd update <id> --claim # Claim work
244
+ bd close <id> # Complete work
245
+ ```
246
+
247
+ ### Rules
248
+
249
+ - Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
250
+ - Run `bd prime` for detailed command reference and session close protocol
251
+ - Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
252
+
253
+ ## Session Completion
254
+
255
+ **When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
256
+
257
+ **MANDATORY WORKFLOW:**
258
+
259
+ 1. **File issues for remaining work** - Create issues for anything that needs follow-up
260
+ 2. **Run quality gates** (if code changed) - Tests, linters, builds
261
+ 3. **Update issue status** - Close finished work, update in-progress items
262
+ 4. **PUSH TO REMOTE** - This is MANDATORY:
263
+ ```bash
264
+ git pull --rebase
265
+ bd dolt push
266
+ git push
267
+ git status # MUST show "up to date with origin"
268
+ ```
269
+ 5. **Clean up** - Clear stashes, prune remote branches
270
+ 6. **Verify** - All changes committed AND pushed
271
+ 7. **Hand off** - Provide context for next session
272
+
273
+ **CRITICAL RULES:**
274
+ - Work is NOT complete until `git push` succeeds
275
+ - NEVER stop before pushing - that leaves work stranded locally
276
+ - NEVER say "ready to push when you are" - YOU must push
277
+ - If push fails, resolve and retry until it succeeds
278
+ <!-- END BEADS INTEGRATION -->
279
+
280
+
281
+ ## Version Bumping
282
+
283
+ **Every feature branch push MUST bump the version in ALL version-bearing files.**
284
+
285
+ Bump type is determined by the commit message prefix:
286
+ - `feat!:` or `BREAKING CHANGE` → **major** (X+1.0.0)
287
+ - `feat` or `feat(...)` → **minor** (X.Y+1.0)
288
+ - Everything else (`fix`, `chore`, `refactor`, `test`, `docs`, etc.) → **patch** (X.Y.Z+1)
289
+
290
+ **Files to update (if they exist in this repo):**
291
+ - `Cargo.toml` — `version = "X.Y.Z"` in `[package]`
292
+ - `package.json` — `"version": "X.Y.Z"`
293
+ - `pyproject.toml` — `version = "X.Y.Z"` in `[project]`
294
+ - `.claude-plugin/plugin.json` — `"version": "X.Y.Z"`
295
+ - `.codex-plugin/plugin.json` — `"version": "X.Y.Z"`
296
+ - `gemini-extension.json` — `"version": "X.Y.Z"`
297
+ - `README.md` — version badge or header
298
+ - `CHANGELOG.md` — new entry under the bumped version
299
+
300
+ All files MUST have the same version. Never bump only one file.
301
+ CHANGELOG.md must have an entry for every version bump.
@@ -0,0 +1 @@
1
+ CLAUDE.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jmagar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.