Move each domain's constants and handler into its own module (_array.py,
_disk.py, _docker.py, _vm.py, _notification.py, _key.py, _plugin.py,
_rclone.py, _setting.py, _customization.py, _oidc.py, _user.py,
_live.py, _system.py). Each module uses 'from ..core import client as _client'
+ '_client.make_graphql_request()' for module-attribute access, ensuring
test patches at unraid_mcp.core.client intercept all calls regardless of
which domain module is executing.
_health.py holds constants + _comprehensive_health_check helper only;
_handle_health stays in unraid.py because tests patch elicit_and_configure
at unraid_mcp.tools.unraid — keeping it there avoids circular imports
while preserving correct patch interception.
unraid.py becomes a thin dispatcher: imports handlers from domain files,
re-exports all query/mutation dicts and destructive-action sets for tests,
and owns only _handle_health inline.
835 tests passing.
- format_kb now delegates to format_bytes(kb * 1024) — eliminates the
duplicate size-formatting loop and fixes a silent truncation at TB
(the old explicit if-chain could not reach PB or EB)
- Remove _VALID_NOTIF_TYPES (identical duplicate of _VALID_LIST_TYPES);
update the one reference in _handle_notification to use _VALID_LIST_TYPES
- Remove redundant importance validation in the notification/create branch
(already validated earlier in _handle_notification before subaction dispatch)
Closes: unraid-mcp-ilg.13, unraid-mcp-ilg.14
All test _mock_graphql fixtures previously patched unraid_mcp.tools.unraid.make_graphql_request
(a local name binding). After a domain split, new module files bind their own local names,
making the tool-level patch ineffective — test mutations reached the real Unraid server and
stopped the array twice.
Fix: switch unraid.py to use module-attribute access (_client.make_graphql_request) via
`from ..core import client as _client`. Update all test patch targets to
unraid_mcp.core.client.make_graphql_request, which intercepts calls regardless of how
many modules import from that namespace.
835 tests passing. Integration test failures are pre-existing and unrelated.
- Add displayName "unRAID" to plugin.json and marketplace.json
- Expand description to list all 15 action domains and 107 subactions
- Mark destructive subactions with * in description
- Bump version 1.1.3 → 1.1.4
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Extract _validate_path() in unraid.py — consolidates traversal check + normpath
+ prefix validation used by disk/logs and live/log_tail into one place
- Extract build_connection_init() in subscriptions/utils.py — removes 4 duplicate
connection_init payload blocks from snapshot.py (×2), manager.py, diagnostics.py;
also fixes diagnostics.py bug where x-api-key: None was sent when no key configured
- Remove _LIVE_ALLOWED_LOG_PREFIXES alias — direct reference to _ALLOWED_LOG_PREFIXES
- Move import hmac to module level in server.py (was inside verify_token hot path)
Co-Authored-By: Claude <noreply@anthropic.com>
- Call _build_google_auth() at module level before mcp = FastMCP(...)
- Pass auth=_google_auth to FastMCP() constructor
- Add startup log in run_server(): INFO when OAuth enabled (with redirect URI), WARNING when open/unauthenticated
- Add test verifying mcp has no auth provider when Google vars are absent (baseline + post-wire)
- 4 tests covering unconfigured/configured/no-jwt-key/stdio-warning paths
- Validates GoogleProvider is called with correct kwargs
- Verifies jwt_signing_key is omitted (not passed empty) when unset
Co-authored-by: Claude <noreply@anthropic.com>
Adds _build_google_auth() to server.py that reads Google OAuth settings
and returns a configured GoogleProvider instance or None when unconfigured.
Includes warning for stdio transport incompatibility and conditional
jwt_signing_key passthrough. 4 new TDD tests in tests/test_auth_builder.py.
- Add fastmcp.http.json and fastmcp.stdio.json declarative server configs
for streamable-http (:6970) and stdio transports respectively
- Move register_all_modules() to module level in server.py so
`fastmcp run server.py --reload` discovers the fully-wired mcp object
without going through run_server() — tools registered exactly once
- Add timeout=120 to @mcp.tool() decorator as a global safety net;
any hung subaction returns a clean MCP error instead of hanging forever
- Document fastmcp run --reload, fastmcp list, fastmcp call in README
- Bump version 1.0.1 → 1.1.0
Co-authored-by: Claude <claude@anthropic.com>
Add GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, UNRAID_MCP_BASE_URL, and
UNRAID_MCP_JWT_SIGNING_KEY env vars to settings.py, along with the
is_google_auth_configured() predicate and three new keys in
get_config_summary(). TDD: 4 tests written red-first, all passing green.
Thread 6: Add python language specifier to .claude-plugin/README.md code fence
Thread 7: Fix MD022/MD031 spacing in docs/MARKETPLACE.md
Thread 8: Blank line before Destructive Actions heading in README.md
Thread 9: Move H1 to first line in skills/unraid/references/api-reference.md
Thread 10+11+12: Rewrite quick-reference.md:
- Fix MD022/MD031 (blank lines around headings/fences)
- Add python language specifiers to all code fences
- Fix disk/logs params: path/lines → log_path/tail_lines
- Add path param to live/log_tail example
- Remove invalid notification/unread subaction
- Fix notification/list filter= → list_type=
Thread 13: Add python/text language specifiers to troubleshooting.md fences
Thread 14: Update test-tools.sh header comment (in separate commit)
Also: Remove 'connect' from README system subactions table (it was removed from
the tool in previous commit), fix notification 'unread' → 'mark_unread' in table
Threads 1, 2, 3 — test hygiene:
- Move elicit_and_configure/elicit_reset_confirmation to module-level imports
in unraid.py so tests can patch at unraid_mcp.tools.unraid.* (thread 2)
- Add return type annotations to _make_tool() in test_customization.py (thread 1)
- Replace unused _mock_ensure_started fixture params with @usefixtures (thread 3)
Thread 4 — remove dead 'connect' subaction from _SYSTEM_QUERIES; the subaction
was always rejected with a ToolError, creating an inconsistent contract.
Thread 5 — centralize two inline "query { online }" strings by reusing
_SYSTEM_QUERIES["online"]; add _DOCKER_QUERIES["_resolve"] for container-name
resolution instead of an inline query literal.
Threads 14, 15, 16, 17, 18 — test improvements:
- test-tools.sh: reword header to "broad non-destructive smoke coverage" (t14)
- test-tools.sh: add _json_payload() helper using jq --arg for safe JSON
construction; replace all printf-based payloads (thread 15)
- test_input_validation.py: add return type annotations to _make_tool and all
nested _run_test coroutines (thread 16)
- test_query_validation.py: extract _all_domain_dicts() shared helper to
eliminate the duplicate 22-item registry (thread 17)
- test_query_validation.py: tighten regression threshold from 50 → 90 (thread 18)
- guards.py: split confirm bypass into explicit check; use .get() for
dict description to prevent KeyError on missing action keys
- resources.py: use `is not None` for logs stream cache check; add
on-demand subscribe_once fallback when auto_start is disabled so
resources return real data instead of a perpetual "connecting" placeholder
- setup.py: always prompt before overwriting credentials even on failed
probe (transient outage ≠ bad credentials); update elicitation message
- unraid.py: always elicit_reset_confirmation before overwriting creds;
use asyncio.to_thread() for os.path.realpath() to avoid blocking async
- test_health.py: update test for new always-prompt-on-overwrite behavior;
add test for declined-reset on failed probe
- test_resources.py: add tests for logs-stream None check, auto_start
disabled fallback (success and failure), and fallback error recovery
- test-tools.sh: add suite_live() covering cpu/memory/cpu_telemetry/
notifications_overview/log_tail; include in sequential and parallel runners
- CLAUDE.md: correct unraid_live → live action reference; document that
setup always prompts before overwriting; note subscribe_once fallback
Replace hard ToolError guard with gate_destructive_action() in 5 tools so
destructive actions prompt for interactive confirmation via MCP elicitation
when ctx is available, and still accept confirm=True as a bypass. Update
all test match strings from "destructive" to "not confirmed" accordingly.
Replace 7-11 line inline guard blocks in array.py, keys.py, and plugins.py
with single await gate_destructive_action(...) calls. Also fix guards.py to
raise unraid_mcp.core.exceptions.ToolError (project subclass) instead of
fastmcp.exceptions.ToolError so pytest.raises catches it correctly in tests.