- diagnostics.py: fix allow-list vs field name mismatch in subscription validator
(_ALLOWED_SUBSCRIPTION_FIELDS now contains schema field names like "logFile",
not operation names like "logFileSubscription", matching what _SUBSCRIPTION_NAME_PATTERN
extracts); add _validate_subscription_query() called before any network I/O;
replace chained .replace() URL building with build_ws_url(); gate connection_issues
on current failure state via _analyze_subscription_status()
- manager.py: add _cap_log_content() with byte-count pre-check
(len(value.encode("utf-8", errors="replace")) > _MAX_RESOURCE_DATA_BYTES) so
multibyte UTF-8 content cannot bypass the 1 MB cap
- resources.py: add double-checked locking (_startup_lock) in ensure_subscriptions_started();
propagate exception from auto_start_all_subscriptions() via raise so
_subscriptions_started=True is never set after a failed init
- utils.py: add build_ws_url() that raises ValueError on unknown/missing URL scheme
instead of silently falling through; add _analyze_subscription_status() helper
that gates connection_issues on current failure state
Resolves review threads PRRT_kwDOO6Hdxs50E50Y PRRT_kwDOO6Hdxs50E50a PRRT_kwDOO6Hdxs50E50c PRRT_kwDOO6Hdxs50E50d PRRT_kwDOO6Hdxs50E2iN PRRT_kwDOO6Hdxs50E2h8
- info.py: add DESTRUCTIVE_ACTIONS set with update_ssh, add confirm param to
unraid_info signature, add destructive guard before mutation handlers
- settings.py: build user_info dict unconditionally so avatar is included
even when username/email are absent; only attach userInfo when non-empty
Resolves review threads PRRT_kwDOO6Hdxs50FgO0 PRRT_kwDOO6Hdxs50FgPC
- create_folder_with_items: forward source_entry_ids not entry_ids to sourceEntryIds
- set_folder_children: use `is not None` guard to allow children_ids=[]
- _resolve_container_id: allow short hex ID matching independent of strict mode
Resolves review threads PRRT_kwDOO6Hdxs50FgOr PRRT_kwDOO6Hdxs50FgPO PRRT_kwDOO6Hdxs50E2iH
These directories contain session-specific research artifacts and plan
documents — ephemeral content that doesn't belong in the repo history.
Resolves review threads PRRT_kwDOO6Hdxs50E50H PRRT_kwDOO6Hdxs50E2iX
PRRT_kwDOO6Hdxs50E2if PRRT_kwDOO6Hdxs50E2ig PRRT_kwDOO6Hdxs50E2ii
PRRT_kwDOO6Hdxs50E2iq
P-01: Replace single subscription_lock with two fine-grained locks:
- _task_lock guards active_subscriptions (task lifecycle operations)
- _data_lock guards resource_data (WebSocket message writes and reads)
Eliminates serialization between WebSocket updates and tool reads.
CQ-05: safe_get now preserves explicit None at terminal key.
Uses sentinel _MISSING to distinguish "key absent" (returns default)
from "key=null" (returns None). Fixes conflation that masked
intentional null values from the Unraid API.
SEC-M04: Validate list_type, importance, and notification_type against
known enums before dispatching to GraphQL. Prevents wasting rate-limited
requests on invalid values and avoids leaking schema details in errors.
_QueryCache.get/put/invalidate_all are async (use asyncio.Lock internally).
Updated 6 sync test methods to async def with proper await calls so they
test the actual async interface rather than calling unawaited coroutines.
- http_layer/test_request_construction.py: tighten JSON error match from
"invalid response" to "invalid response.*not valid JSON" to prevent
false positives
- safety/test_destructive_guards.py: add test_docker_update_all_with_confirm
to TestConfirmAllowsExecution (was missing positive coverage for update_all)
- safety/test_destructive_guards.py: expand conftest import comment to explain
why the direct conftest import is intentional and correct
Resolves review threads:
- PRRT_kwDOO6Hdxs5vNroH (Thread 36): tests now verify generic ToolError message
instead of raw exception text (security: no sensitive data in user-facing errors)
- PRRT_kwDOO6Hdxs5vNuYg (Thread 14): format_kb KB branch now uses :.2f like all
other branches (consistency fix)
- I001/F841/PERF401: fix ruff violations in http_layer, integration, safety tests
Changes:
- tests/test_array.py: match "Failed to execute array/parity_status" (not raw error)
- tests/test_keys.py: match "Failed to execute keys/list" (not raw error)
- tests/test_notifications.py: match "Failed to execute notifications/overview" (not raw error)
- tests/test_storage.py: update format_kb assertion to "512.00 KB" (:.2f format)
- tests/http_layer/test_request_construction.py: remove unused result var (F841)
+ fix import sort (I001)
- tests/safety/test_destructive_guards.py: use list.extend (PERF401) + fix import sort
- unraid_mcp/core/utils.py: format_kb returns f"{k:.2f} KB" for sub-MB values
Co-authored-by: @coderabbitai
Co-authored-by: @cubic-dev-ai
Co-authored-by: @copilot-pull-request-reviewer
Move MCP server configuration from standalone .mcp.json to inline
definition in plugin.json. This consolidates all plugin metadata
in a single location.
- Add type: stdio and env fields to inline config
- Remove redundant .mcp.json file
- Maintains same functionality with cleaner structure
Update .mcp.json to use environment variable
for the --directory argument, ensuring the MCP server works correctly
regardless of where the plugin is installed.
This follows Claude Code plugin best practices for MCP server bundling.
Add .mcp.json to configure the Unraid MCP server as a stdio-based MCP
server for Claude Code plugin integration. This allows Claude Code to
automatically start and connect to the server when the plugin is loaded.
- Type: stdio (standard input/output communication)
- Command: uv run unraid-mcp-server
- Forces stdio transport mode via UNRAID_MCP_TRANSPORT env var
Change source from absolute GitHub URL to relative path "./"
This follows Claude Code marketplace convention where source paths
are relative to the cloned repository root, not external URLs.
Matches pattern from working examples like claude-homelab marketplace.
- Fix marketplace.json: change source from relative path to GitHub URL
(was "skills/unraid", now "https://github.com/jmagar/unraid-mcp")
This resolves the "Invalid input" schema validation error when adding
the marketplace to Claude Code
- Refactor subscriptions autostart to use anyio.Path for async file checks
(replaces blocking pathlib.Path.exists() with async anyio.Path.exists())
- Update dependencies: anyio 4.11.0→4.12.1, attrs 25.3.0→25.4.0
- Rename marketplace from "unraid-mcp" to "jmagar-unraid-mcp" to match expected directory structure
- Wrap description, version, homepage, and repository in metadata object per standard format
- Fixes "Marketplace file not found" error when adding marketplace to Claude Code
Resolves marketplace installation issues by aligning with format used by other Claude Code marketplaces.
Resolves review thread PRRT_kwDOO6Hdxs5uu7z7
- Removed redundant User-Agent header from per-request headers in make_graphql_request()
- User-Agent is already set as default header on the shared HTTP client
- httpx merges per-request headers with client defaults, so client-level default is sufficient
- Fix broken ToC anchors in competitive-analysis.md (MD051)
- Add blank lines before code blocks in api-reference.md (MD031)
- Add language identifiers to directory tree code blocks in MARKETPLACE.md and skills/unraid/README.md (MD040)
- Fix size unit guidance conflict: clarify disk sizes are KB, memory is bytes
- Update stale "90 actions" references to "76 actions" across research docs
- Fix coverage table terminology and clarify 22% coverage calculation
- Recommend PyPI Trusted Publishing (OIDC) over API token secrets in PUBLISHING.md
- Update action count in .claude-plugin/README.md
Resolves review threads: PRRT_kwDOO6Hdxs5uvO2m, PRRT_kwDOO6Hdxs5uvO2o,
PRRT_kwDOO6Hdxs5uvO2r, PRRT_kwDOO6Hdxs5uvOcl, PRRT_kwDOO6Hdxs5uvOcr,
PRRT_kwDOO6Hdxs5uvKrq, PRRT_kwDOO6Hdxs5uvO2u, PRRT_kwDOO6Hdxs5uvO2w,
PRRT_kwDOO6Hdxs5uvO2z, PRRT_kwDOO6Hdxs5uu7zl
- Remove slashes from LOG_NAME regex to block path traversal (e.g.
../../etc/passwd). Only alphanumeric, dots, hyphens, underscores allowed.
- Cap LINES to 1-10000 range to prevent resource exhaustion.
- Add query script existence check before execution.
- Add query failure, empty response, and invalid JSON guards.
Resolves review thread PRRT_kwDOO6Hdxs5uvKrj
- Remove set -e from validate-marketplace.sh to prevent early exit on
check failures, allowing the summary to always be displayed (PRRT_kwDOO6Hdxs5uvKrc)
- Fix marketplace.json source path to point to skills/unraid instead of
./ for correct plugin directory resolution (PRRT_kwDOO6Hdxs5uvKrg)
- Fix misleading trap registration comment in unraid-api-crawl.md and
add auth note to Apollo Studio URL (PRRT_kwDOO6Hdxs5uvO2t)
- Extract duplicated cleanup-with-error-handling in main.py into
_run_shutdown_cleanup() helper (PRRT_kwDOO6Hdxs5uvO3A)
- Add input validation to read-logs.sh to prevent GraphQL injection
via LOG_NAME and LINES parameters (PRRT_kwDOO6Hdxs5uvKrj)
- Rename test_start_http_401_unauthorized to test_list_http_401_unauthorized
to match the actual action="list" being tested (threads #19, #23)
- Use consistent PrefixedID format ("a"*64+":local") in test_start_container
instead of "abc123def456"*4 concatenation (thread #37)
- Refactor container_actions_require_id to use @pytest.mark.parametrize
so each action runs independently (thread #18)
- Fix docstring claiming ToolError for test that asserts success in
test_stop_mutation_returns_null (thread #26)
- Fix inaccurate comment about `in` operator checking truthiness;
it checks key existence (thread #25)
- Add edge case tests for temperature=0, temperature=null, and
logFile=null in test_storage.py (thread #31)
Resolves review threads: PRRT_kwDOO6Hdxs5uvO2-, PRRT_kwDOO6Hdxs5uvOcf,
PRRT_kwDOO6Hdxs5uu7zx, PRRT_kwDOO6Hdxs5uvO28, PRRT_kwDOO6Hdxs5uvOcp,
PRRT_kwDOO6Hdxs5uvOcn, PRRT_kwDOO6Hdxs5uvKr3
- Move plugin.json from skills/unraid/.claude-plugin/ to .claude-plugin/
- Update validation script to use correct plugin manifest path
- Add plugin structure section to root README.md
- Add installation instructions to skills/unraid/README.md
- Aligns with Claude Code's expectation for source: './' in marketplace.json
- Change Subprotocol import from deprecated websockets.legacy.protocol
to websockets.typing (canonical location in websockets 13.x)
- Fix SSL context handling in diagnostics.py to properly build
ssl.SSLContext objects, matching the pattern in manager.py
(previously passed UNRAID_VERIFY_SSL directly which breaks
when it's a CA bundle path string)
- Move _ALLOWED_LOG_PREFIXES to module level (N806: constant naming)
- Use f-string conversion flag {e!s} instead of {str(e)} (RUF010)
- Fix import block sorting in both files (I001)
**Project Configuration:**
- Enhance pyproject.toml with comprehensive metadata, keywords, and classifiers
- Add LICENSE file (MIT) for proper open-source distribution
- Add PUBLISHING.md with comprehensive publishing guidelines
- Update .gitignore to exclude tool artifacts (.cache, .pytest_cache, .ruff_cache, .ty_cache)
- Ignore documentation working directories (.docs, .full-review, docs/plans, docs/sessions)
**Documentation:**
- Add extensive Unraid API research documentation
- API source code analysis and resolver mapping
- Competitive analysis and feature gap assessment
- Release notes analysis (7.0.0, 7.1.0, 7.2.0)
- Connect platform overview and remote access documentation
- Document known API patterns, limitations, and edge cases
**Testing & Code Quality:**
- Expand test coverage across all tool modules
- Add destructive action confirmation tests
- Improve test assertions and error case validation
- Refine type annotations for better static analysis
**Tool Improvements:**
- Enhance error handling consistency across all tools
- Improve type safety with explicit type annotations
- Refine GraphQL query construction patterns
- Better handling of optional parameters and edge cases
This commit prepares the project for v0.2.0 release with improved
metadata, comprehensive documentation, and enhanced code quality.
Co-authored-by: Claude <noreply@anthropic.com>
Refactor the entire tool layer to use the consolidated action pattern
(action: Literal[...] with QUERIES/MUTATIONS dicts). This reduces LLM
context from ~12k to ~5k tokens while adding ~60 new API capabilities.
New tools: unraid_info (19 actions), unraid_array (12), unraid_notifications (9),
unraid_users (8), unraid_keys (5). Rewritten: unraid_docker (15), unraid_vm (9),
unraid_storage (6), unraid_rclone (4), unraid_health (3).
Includes 129 tests across 10 test files, code review fixes for 16 issues
(severity ordering, PrefixedID regex, sensitive var redaction, etc.).
Removes tools/system.py (replaced by tools/info.py). Version bumped to 0.2.0.
- Update dev.sh to use /tmp for LOG_DIR instead of PROJECT_DIR/logs
- Update settings.py to use /tmp for LOGS_DIR instead of PROJECT_ROOT/logs
- This change moves both pid files and log files to the temporary directory
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove env_file directive from docker-compose.yml to eliminate .env file dependency
- Add explicit environment variable declarations with default values using ${VAR:-default} syntax
- Update port mapping to use UNRAID_MCP_PORT environment variable for both host and container
- Include all 11 environment variables used by the application with proper defaults
- Update README.md Docker deployment instructions to use export commands instead of .env files
- Update manual Docker run command to use -e flags instead of --env-file
This makes Docker deployment self-contained and follows container best practices.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>