unraid-mcp/CHANGELOG.md
2026-04-15 19:13:31 -04:00

20 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[1.3.8] - 2026-04-15

Changed

  • Repository maintenance updates committed from the current working tree.
  • Version-bearing manifests synchronized to 1.3.8.

[1.3.7] - 2026-04-05

Fixed

  • bin/bump-version.sh: Fixed gemini path — always resolved the wrong .gemini-extension.json default before the conditional override; now prefers gemini-extension.json with dot-prefix as fallback. Added empty-version guard. Merged two sed -i calls per file into one with -e.
  • bin/ensure-ignore-files.sh: Fixed "scripts""bin" in REQUIRED_DOCKER array (was appending wrong dockerignore entry); fixed stale scripts/ and hooks/scripts/ path references in comments.
  • bin/validate-marketplace.sh: Replaced eval with bash -c; replaced python3 subprocess for version read with jq.
  • bin/check-docker-security.sh, bin/check-no-baked-env.sh, bin/check-outdated-deps.sh: Fixed stale scripts/ path references in header comments.
  • tests/test_bump_version.bats: Renamed TMPDIRTEST_DIR to avoid shadowing POSIX system variable; last test now uses an isolated symlinked copy instead of mutating real repo files.
  • Stale scripts/ references: Updated all remaining references from scripts/ to bin/ in .github/workflows/ci.yml, .pre-commit-config.yaml, Justfile, docs/CHECKLIST.md, docs/INVENTORY.md, docs/MARKETPLACE.md, docs/repo/SCRIPTS.md, and docs/repo/REPO.md. The hooks/scripts/ and skills/unraid/scripts/ paths are intentional and unchanged.

[1.3.6] - 2026-04-05

Added

  • tests/test_bump_version.bats: 9 bats tests for bin/bump-version.sh covering explicit version, patch/minor/major keywords, all-files-in-sync, output format, and dirname fallback.

Changed

  • bin/bump-version.sh: Uses CLAUDE_PLUGIN_ROOT as repo root override (set automatically by plugin runtime in hook contexts); falls back to dirname detection for direct dev use.
  • bin/: Renamed from scripts/ — moved all scripts to bin/.

[1.3.5] - 2026-04-05

Added

  • scripts/bump-version.sh: One-command version bump across all four version-bearing files. Supports explicit version or major/minor/patch keywords.

[1.3.4] - 2026-04-05

Changed

  • SessionStart hook: Removed install-deps.sh script; hook command is inline again per Claude Code plugin docs pattern.

[1.3.3] - 2026-04-05

Changed

  • install-deps.sh: Added Rust/Cargo support (Cargo.lockcargo build --release into ${CLAUDE_PLUGIN_DATA}/target).

[1.3.2] - 2026-04-05

Changed

  • SessionStart hook: Extracted inline uv command into .claude-plugin/install-deps.sh — a language-agnostic script that detects the package manager from lock files (uv, npm, yarn, pnpm) and installs deps into ${CLAUDE_PLUGIN_DATA}.

[1.3.1] - 2026-04-05

Changed

  • .mcp.json: Added all server env vars with defaults; removed UNRAID_CREDENTIALS_DIR (container-only override, not user-facing).

[1.3.0] - 2026-04-05

Added

  • SessionStart hook: plugin.json now installs Python deps via uv sync into ${CLAUDE_PLUGIN_DATA}/.venv on first run and on any uv.lock change — fixes "server failed to start" for users installing via the plugin system.
  • Persistent venv: MCP server command uses UV_PROJECT_ENVIRONMENT=${CLAUDE_PLUGIN_DATA}/.venv so the installed venv survives plugin updates without reinstalling on every session.

Changed

  • .mcp.json: Added UV_PROJECT_ENVIRONMENT=${CLAUDE_PLUGIN_DATA}/.venv and --project ${CLAUDE_PLUGIN_ROOT} so the MCP server uses the persisted venv installed by the hook.

[1.2.5] - 2026-04-05

Changed

  • CI gating: mcp-integration now runs only when both UNRAID_API_URL and UNRAID_API_KEY secrets are present, preventing fork and unconfigured-repo runs from failing on live integration setup.
  • Plugin manifests: Claude plugin manifest now points to ./.mcp.json instead of embedding an inline MCP server definition; version-bearing files synchronized to 1.2.5.

[1.2.4] - 2026-04-04

Added

  • Comprehensive test suite: Added tests for core modules, configuration, validation, subscriptions, and edge cases
  • Test coverage documentation: tests/TEST_COVERAGE.md with coverage map and gap analysis

Changed

  • Documentation: Comprehensive updates across CLAUDE.md, README, and reference docs
  • Version sync: Fixed pyproject.toml version mismatch (was 1.2.2, now aligned with all manifests at 1.2.4)

[1.2.3] - 2026-04-03

Changed

  • hooks/hooks.json: Removed SessionStart hook — sync-env.sh was exiting 1 on every session start when UNRAID_MCP_BEARER_TOKEN was not set via plugin userConfig, causing startup hook errors.

[1.2.2] - 2026-04-03

Changed

  • README: Restructured to a compact reference format — removed marketing prose, replaced with minimal tables and direct headings.
  • CHANGELOG: Adopted Keep a Changelog format.
  • Version sync: .codex-plugin/plugin.json and gemini-extension.json brought to 1.2.2 (were stale at 1.2.0).

[1.2.1] - 2026-04-03

Fixed

  • OAuth discovery 401 cascade (fixes #17): BearerAuthMiddleware was blocking GET /.well-known/oauth-protected-resource, causing MCP clients (e.g. Claude Code) to surface a generic "unknown error" after receiving a 401. Added WellKnownMiddleware (RFC 9728) placed before BearerAuthMiddleware that returns resource metadata with bearer_methods_supported: ["header"] and no authorization_servers — telling clients to use a pre-configured Bearer token rather than attempting an OAuth flow.

Added

  • docs/AUTHENTICATION.md: New setup guide covering token generation, server config, Claude Code client config ("Authorization": "Bearer <token>" header format), and troubleshooting for common 401 errors.
  • README Authentication section: Added the missing ## 🔐 Authentication section (was linked in TOC but not present) with quick-start examples and a link to the full guide.

[1.2.0] - 2026-03-30

Added

  • HTTP bearer token auth: ASGI-level BearerAuthMiddleware (pure __call__ pattern, no BaseHTTPMiddleware overhead) enforces Authorization: Bearer <token> on all HTTP requests. RFC 6750 compliant — missing header returns WWW-Authenticate: Bearer realm="unraid-mcp", invalid token adds error="invalid_token".
  • Auto token generation: On first HTTP startup with no token configured, a secrets.token_urlsafe(32) token is generated, written to ~/.unraid-mcp/.env (mode 600), announced once on STDERR without printing the secret, and removed from os.environ so subprocesses cannot inherit it.
  • Per-IP rate limiting: 60 failed auth attempts per 60 seconds → 429 with Retry-After: 60 header.
  • Gateway escape hatch: UNRAID_MCP_DISABLE_HTTP_AUTH=true bypasses bearer auth for users who handle authentication at a reverse proxy / gateway layer.
  • Startup guard: Server refuses to start in HTTP mode (streamable-http/sse) if no token is set and UNRAID_MCP_DISABLE_HTTP_AUTH is not explicitly enabled.
  • Tests: 23 new tests in tests/test_auth.py covering pass-through scopes, 401/429 responses, RFC 6750 header differentiation, per-IP rate limiting, window expiry, token generation, and startup guard.

Changed

  • Default transport: stdiostreamable-http. Users running directly (not via Claude Desktop plugin) will now get an HTTP server by default. To keep stdio behaviour, set UNRAID_MCP_TRANSPORT=stdio. The Claude Desktop plugin (plugin.json) is unaffected — it hardcodes stdio.
  • .env.example: Updated to document new auth variables (UNRAID_MCP_BEARER_TOKEN, UNRAID_MCP_DISABLE_HTTP_AUTH) and updated default transport comment.

Breaking Changes

  • Default transport is now streamable-http. Any script or service that relied on the default being stdio must explicitly set UNRAID_MCP_TRANSPORT=stdio.

[1.1.6] - 2026-03-30

Security

  • Path traversal: flash_backup source path now validated after posixpath.normpath (not before) — raw-string .. check was bypassable via encoded sequences like foo/bar/../..; null byte guard added
  • Key validation: DANGEROUS_KEY_PATTERN now blocks space (0x20) and DEL (0x7f) in addition to existing shell metacharacters; applies to both rclone and settings key validation

Fixed

  • Settings validation: configure_ups input now validated via _validate_settings_input before mutation — was previously passing unvalidated dict directly to GraphQL
  • Subscription locks: _start_one last_error write and stop_all() keys snapshot both now take _task_lock to prevent concurrent write/read races
  • Keepalive handling: Removed "ping" from keepalive elif — ping messages require a pong response, not silent discard; only "ka" and "pong" are silently dropped
  • Middleware import: middleware_refs.py ErrorHandlingMiddleware import changed from TYPE_CHECKING-only to unconditional — isinstance() calls at runtime were silently broken
  • Health reverse map: _STATUS_FROM_SEVERITY dict hoisted to module level — was being rebuilt on every _comprehensive_health_check call

Changed

  • Log content cap: _cap_log_content now skipped for non-log subscriptions (only log_tail/logFileSubscription have content fields) — reduces unnecessary dict key lookups on every WebSocket message
  • Live assertion: _handle_live now raises RuntimeError at import time if COLLECT_ACTIONS contains keys not in _HANDLED_COLLECT_SUBACTIONS — catches handler omissions before runtime
  • Subscription name guard: start_subscription validates name matches ^[a-zA-Z0-9_]+$ before use as WebSocket message ID

Added

  • Tests: 27 parametrized tests for DANGEROUS_KEY_PATTERN covering all documented dangerous characters and safe key examples (tests/test_validation.py)
  • Tests: test_check_api_error_wrapped_tool_error — verifies health check returns {status: unhealthy} when make_graphql_request raises ToolError wrapping httpx.ConnectError

[1.1.5] - 2026-03-27

Added

  • Beads issue tracking: bd init — Dolt-backed issue tracker with prefix unraid-mcp-<hash>, hooks, and AGENTS.md integration
  • Lavra project config: .lavra/config/project-setup.md — stack python, review agents (kieran-python-reviewer, code-simplicity-reviewer, security-sentinel, performance-oracle)
  • Codebase profile: .lavra/config/codebase-profile.md — auto-generated stack/architecture/conventions reference for planning and review commands

Changed

  • .gitignore: Added lavra session-state exclusion (.lavra/memory/session-state.md) and beads-related entries
  • CLAUDE.md: Added beads workflow integration block with mandatory bd usage rules and session completion protocol

[1.1.4] - 2026-03-25

Changed

  • Plugin branding: displayName set to unRAID in plugin.json and marketplace.json
  • Plugin description: Expanded to list all 3 tools and all 15 action domains with full subaction inventory (107 subactions, destructive actions marked with *)

[1.1.3] - 2026-03-24

Fixed

  • Docs accuracy: disk/logs docs corrected to use log_path/tail_lines parameters (were path/lines)
  • Docs accuracy: rclone/create_remote docs corrected to provider_type/config_data (were type/fields)
  • Docs accuracy: setting/update docs corrected to settings_input parameter (was settings)
  • Docs accuracy: key/create now documents roles as optional; add_role/remove_role corrected to roles (plural)
  • Docs accuracy: oidc/validate_session now documents required token parameter
  • Docs accuracy: parity_start quick-reference example now includes required correct=False
  • Docs accuracy: log_tail README example now includes required path="/var/log/syslog"
  • Docs accuracy: live/parity_progress added to event-driven subscriptions list in troubleshooting guide
  • Docs accuracy: live/array_state wording softened — "may show connecting indefinitely" vs "will always show"
  • Markdown: endpoints.md top-level heading moved before blockquote disclaimer (MD041)
  • Tests: test_resources.py now uses _get_resource() helper instead of raw mcp.providers[0]._components[...] access; isolates FastMCP internals to one location

[1.1.2] - 2026-03-23

Security

  • Path traversal: Removed /mnt/ from _ALLOWED_LOG_PREFIXES — was exposing all Unraid user shares to path-based reads
  • Path traversal: Added early .. detection for disk/logs and live/log_tail before any filesystem access; added /boot/ prefix restriction for flash_backup source paths
  • Timing-safe auth: verify_token now uses hmac.compare_digest instead of == to prevent timing oracle attacks on API key comparison
  • Traceback leak: include_traceback in ErrorHandlingMiddleware is now gated on DEBUG log level; production deployments no longer expose stack traces

Fixed

  • Health check: _comprehensive_health_check now re-raises CredentialsNotConfiguredError instead of swallowing it into a generic unhealthy status
  • UPS device query: Removed non-existent nominalPower and currentPower fields from ups_device query — every call was failing against the live API
  • Stale credential bindings: Subscription modules (manager.py, snapshot.py, utils.py, diagnostics.py) previously captured UNRAID_API_KEY/UNRAID_API_URL at import time; replaced with _settings.ATTR call-time access so apply_runtime_config() updates propagate correctly after credential elicitation

Added

  • CI pipeline: .github/workflows/ci.yml with 5 jobs — lint (ruff), typecheck (ty), test (pytest -m "not integration"), version-sync check, and uv audit dependency scan
  • Coverage threshold: fail_under = 80 added to [tool.coverage.report]
  • Version sync check: scripts/validate-marketplace.sh now verifies pyproject.toml and plugin.json versions match

Changed

  • Docs: Updated CLAUDE.md, README.md to reflect 3 tools (1 primary + 2 diagnostic); corrected system domain count (19→18); fixed scripts comment
  • Docs: docs/AUTHENTICATION.md H1 retitled to "Authentication Setup Guide"
  • Docs: Added UNRAID_CREDENTIALS_DIR commented entry to .env.example
  • Removed from __future__ import annotations from snapshot.py (caused TC002 false positives with FastMCP)
  • Added # noqa: ASYNC109 to timeout parameters in _handle_live and unraid() (valid suppressions)
  • Fixed start_array*start_array in tool docstring table (start_array is not in _ARRAY_DESTRUCTIVE)

Refactored

  • Path validation: Extracted _validate_path() in unraid.py — consolidates traversal check, normpath, and prefix validation used by both disk/logs and live/log_tail into one place; eliminates duplication
  • WebSocket auth payload: Extracted build_connection_init() in subscriptions/utils.py — removes 4 duplicate connection_init blocks from snapshot.py (×2), manager.py, and diagnostics.py; also fixes a bug in diagnostics.py where x-api-key: None was sent when no API key was configured
  • Removed _LIVE_ALLOWED_LOG_PREFIXES alias — direct reference to _ALLOWED_LOG_PREFIXES
  • Moved import hmac to module level in server.py (was inside verify_token hot path)

[1.1.1] - 2026-03-16

Added

  • API key auth: Authorization: Bearer <UNRAID_MCP_API_KEY> bearer token authentication via ApiKeyVerifier — machine-to-machine access without OAuth browser flow
  • MultiAuth: When both Google OAuth and API key are configured, MultiAuth accepts either method
  • Google OAuth: Full GoogleProvider integration — browser-based OAuth 2.0 flow with JWT session tokens; UNRAID_MCP_JWT_SIGNING_KEY for stable tokens across restarts
  • fastmcp.json: Dev tooling configs for FastMCP

Fixed

  • Auth test isolation: use os.environ[k] = "" instead of delenv to prevent dotenv re-injection between test reloads

[1.1.0] - 2026-03-16

Breaking Changes

  • Tool consolidation: 15 individual domain tools (unraid_docker, unraid_vm, etc.) merged into single unraid tool with action + subaction routing
    • Old: unraid_docker(action="list")
    • New: unraid(action="docker", subaction="list")

Added

  • live tool (11 subactions): Real-time WebSocket subscription snapshots — cpu, memory, cpu_telemetry, array_state, parity_progress, ups_status, notifications_overview, notification_feed, log_tail, owner, server_status
  • customization tool (5 subactions): theme, public_theme, is_initial_setup, sso_enabled, set_theme
  • plugin tool (3 subactions): list, add, remove
  • oidc tool (5 subactions): providers, provider, configuration, public_providers, validate_session
  • Persistent SubscriptionManager: unraid://live/* MCP resources backed by long-lived WebSocket connections with auto-start and reconnection
  • diagnose_subscriptions and test_subscription_query diagnostic tools
  • array: Added parity_history, start_array, stop_array, add_disk, remove_disk, mount_disk, unmount_disk, clear_disk_stats
  • keys: Added add_role, remove_role
  • settings: Added update_ssh (confirm required)
  • stop_array added to _ARRAY_DESTRUCTIVE
  • gate_destructive_action helper in core/guards.py — centralized elicitation + confirm guard
  • Full safety test suite: TestNoGraphQLCallsWhenUnconfirmed (zero-I/O guarantee for all 13 destructive actions)

Fixed

  • Removed 29 actions confirmed absent from live API v4.29.2 via GraphQL introspection (Docker organizer mutations, unassignedDevices, warningsAndAlerts, etc.)
  • log_tail path validated against allowlist before subscription start
  • WebSocket auth uses x-api-key connectionParams format

[1.0.0] - 2026-03-14 through 2026-03-15

Breaking Changes

  • Credential storage moved to ~/.unraid-mcp/.env (dir 700, file 600); all runtimes load from this path
  • unraid_health(action="setup") is the only tool that triggers credential elicitation; all others propagate CredentialsNotConfiguredError

Added

  • CredentialsNotConfiguredError sentinel — propagates cleanly through tool_error_handler with exact credential path in the error message
  • is_configured() and apply_runtime_config() in settings.py for runtime credential injection
  • elicit_and_configure() with .env persistence and confirmation before overwrite
  • 28 GraphQL mutations across storage, docker, notifications, and new settings tool
  • Comprehensive test suite expansion: schema validation (99 tests), HTTP layer (respx), property tests, safety audit, contract tests

Fixed

  • Numerous PR review fixes across 50+ commits (CodeRabbit, ChatGPT-Codex review rounds)
  • Shell scripts hardened against injection and null guards
  • Notification enum validation, subscription lock split, safe_get semantics

[0.6.0] - 2026-03-15

Added

  • Subscription byte/line cap to prevent unbounded memory growth
  • asyncio.timeout bounds on subscribe_once / subscribe_collect
  • Partial auto-start for subscriptions (best-effort on startup)

Fixed

  • WebSocket URL scheme handling (ws:///wss://)
  • flash_backup path validation and smoke test assertions

[0.5.0] - 2026-03-15

Tool expansion and live subscription foundation.


[0.4.x] - 2026-03-13 through 2026-03-14

Credential elicitation system, per-tool refactors, and mutation additions.


[0.2.x] - 2026-02-15 through 2026-03-13

Initial public release hardening: PR review cycles, test suite expansion, security fixes, plugin manifest.


[0.1.0] - 2026-02-08

Added

  • Consolidated 26 tools into 10 tools with 90 actions
  • FastMCP architecture migration with uv toolchain
  • Docker Compose support with health checks
  • WebSocket subscription infrastructure

Format: Keep a Changelog. Versioning: Semantic Versioning.