Commit graph

291 commits

Author SHA1 Message Date
Jacob Magar
d20a095901 test(unraid-mcp-a07): add missing tests for sso_enabled, system/array error path, vm/reboot happy path 2026-03-28 21:00:34 -04:00
Jacob Magar
54c66ed9a2 fix(unraid-mcp-vvh): add COLLECT_ACTIONS extensibility guard with constant and assertion test 2026-03-28 20:57:12 -04:00
Jacob Magar
5fd786b1c9 fix(unraid-mcp-i7o): system simple_dict subactions raise ToolError or log warning on null response 2026-03-28 20:57:12 -04:00
Jacob Magar
0250eb3706 fix(unraid-mcp-brw): add logger.info audit trace to flash_backup destructive path 2026-03-28 20:57:12 -04:00
Jacob Magar
7b834f6fd3 fix(unraid-mcp-8vo): add logging to bare except Exception in health/setup connection probe 2026-03-28 20:52:59 -04:00
Jacob Magar
5f52ade550 fix(unraid-mcp-dai): fix 32 broken integration test patch targets for settings values 2026-03-28 20:52:54 -04:00
Jacob Magar
b1c96c273e fix(unraid-mcp-1nr): narrow except Exception in _comprehensive_health_check to connection errors only 2026-03-28 20:52:53 -04:00
Jacob Magar
447c7e5da6 Merge feat/domain-split: split tools/unraid.py into per-domain modules 2026-03-28 01:45:57 -04:00
Jacob Magar
db6f19e157 refactor: split tools/unraid.py into per-domain modules
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.
2026-03-28 01:44:46 -04:00
Jacob Magar
fb1118f75c refactor: unify format_kb with format_bytes; remove duplicate notification constants
- 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
2026-03-28 01:31:15 -04:00
Jacob Magar
8573a0f6c7 fix: patch make_graphql_request at core.client to prevent tests hitting live server
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.
2026-03-28 01:31:05 -04:00
Jacob Magar
b60bbc0a17 chore: add .worktrees to .gitignore 2026-03-27 21:57:10 -04:00
Jacob Magar
207b68cd8c chore: initialize beads + lavra project config (v1.1.5)
- bd init: Dolt-backed issue tracker, prefix unraid-mcp-<hash>
- .lavra/config/project-setup.md: python stack, 4 review agents
- .lavra/config/codebase-profile.md: stack/arch/conventions profile
- .gitignore: add lavra session-state and beads entries
- CLAUDE.md: beads workflow integration block

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-27 21:30:12 -04:00
Jacob Magar
1c8a81786c bd init: initialize beads issue tracking 2026-03-27 20:49:51 -04:00
Jacob Magar
abb71f17ff chore: rebrand plugin to unRAID, expand description with full action reference (v1.1.4)
- 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>
2026-03-25 00:18:16 -04:00
Jacob Magar
87cd5e5193 docs: expand plugin README with full tool/action/subaction reference 2026-03-24 23:35:54 -04:00
Jacob Magar
2f9decdac9 chore: sync plugin description and marketplace version to 1.1.3 2026-03-24 23:33:48 -04:00
Jacob Magar
8a43b2535a feat: Google OAuth, API key auth, consolidated tool, docs overhaul (v1.1.3)
Merges feat/google-oauth into main.

Highlights:
- Consolidated 15 individual tools into single `unraid` tool with action/subaction routing
- Added API key bearer token authentication for HTTP transport
- Added Google OAuth via FastMCP GoogleProvider (subsequently removed in favor of external gateway pattern)
- Comprehensive security hardening: path traversal fixes, timing-safe auth, traceback leak prevention
- CI pipeline: lint, typecheck, test, version-sync, audit
- CHANGELOG.md, AUTHENTICATION.md, full PR review comment resolution
- Version: 1.0.0 → 1.1.3 across this branch

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-24 23:26:52 -04:00
Jacob Magar
3c6b59b763 chore: bump version to 1.1.3, update CHANGELOG
Version: 1.1.2 → 1.1.3 (patch)

Changelog entry documents 11 documentation accuracy fixes and 1 test
improvement addressed in 183db70 (PR review comments).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-24 23:26:37 -04:00
Jacob Magar
183db70d97 fix: address 17 remaining PR review comments
Resolves review threads:
- PRRT_kwDOO6Hdxs50mcYz: oidc/validate_session now documents required `token`
- PRRT_kwDOO6Hdxs50mcY8: setting/update corrected to require `settings_input`
- PRRT_kwDOO6Hdxs50mcZE: rclone/create_remote corrected to `provider_type`+`config_data`
- PRRT_kwDOO6Hdxs50mcZL: disk/logs corrected to `log_path`+`tail_lines`
- PRRT_kwDOO6Hdxs50mcZe: parity_progress added to event-driven subscriptions list
- PRRT_kwDOO6Hdxs50mcZh: log_tail README example now includes required `path`
- PRRT_kwDOO6Hdxs50mcaR: parity_start quick-reference now includes required `correct=False`
- PRRT_kwDOO6Hdxs50mcaq: array_state documented as "may show" not "will always show"
- PRRT_kwDOO6Hdxs50mnR8: key/create roles is optional; add_role/remove_role use `roles` (plural)
- PRRT_kwDOO6Hdxs50mnRd: endpoints.md heading moved before blockquote (MD041)
- PRRT_kwDOO6Hdxs50mnTB: test_resources.py uses _get_resource() helper instead of raw internals
- PRRT_kwDOO6Hdxs50mYkZ: N/A — _build_google_auth removed in prior refactor commit
- PRRT_kwDOO6Hdxs50mnQf: N/A — plugin.json already at 1.1.2, matches pyproject.toml
- PRRT_kwDOO6Hdxs50mnQ7: N/A — blank line already present in CLAUDE.md
- PRRT_kwDOO6Hdxs50mnRD: N/A — fastmcp.http.json removed in prior refactor commit
- PRRT_kwDOO6Hdxs50mnRH: N/A — blank line already present in README.md
- PRRT_kwDOO6Hdxs50mnSW: N/A — test_auth_builder.py removed in prior refactor commit
2026-03-24 22:50:40 -04:00
Jacob Magar
e548f6e6c9 refactor: remove Docker and HTTP transport support, fix hypothesis cache directory 2026-03-24 19:22:27 -04:00
Jacob Magar
e68d4a80e4 refactor: simplify path validation and connection_init via shared helpers
- 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>
2026-03-23 11:57:00 -04:00
Jacob Magar
dc1e5f18d8 docs: add CHANGELOG.md covering v0.1.0 through v1.1.2
Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-23 11:38:27 -04:00
Jacob Magar
2b777be927 fix(security): path traversal, timing-safe auth, stale credential bindings
Security:
- Remove /mnt/ from _ALLOWED_LOG_PREFIXES to prevent Unraid share exposure
- Add early .. detection for disk/logs and live/log_tail path validation
- Add /boot/ prefix restriction for flash_backup source_path
- Use hmac.compare_digest for timing-safe API key verification in server.py
- Gate include_traceback on DEBUG log level (no tracebacks in production)

Correctness:
- Re-raise CredentialsNotConfiguredError in health check instead of swallowing
- Fix ups_device query (remove non-existent nominalPower/currentPower fields)

Best practices (BP-01, BP-05, BP-06):
- Add # noqa: ASYNC109 to timeout params in _handle_live and unraid()
- Fix start_array* → start_array in docstring (not in ARRAY_DESTRUCTIVE)
- Remove from __future__ import annotations from snapshot.py
- Replace import-time UNRAID_API_KEY/URL bindings with _settings.ATTR pattern
  in manager.py, snapshot.py, utils.py, diagnostics.py — fixes stale binding
  after apply_runtime_config() post-elicitation (BP-05)

CI/CD:
- Add .github/workflows/ci.yml (5-job pipeline: lint, typecheck, test, version-sync, audit)
- Add fail_under = 80 to [tool.coverage.report]
- Add version sync check to scripts/validate-marketplace.sh

Documentation:
- Sync plugin.json version 1.1.1 → 1.1.2 with pyproject.toml
- Update CLAUDE.md: 3 tools, system domain count 18, scripts comment fix
- Update README.md: 3 tools, security notes
- Update docs/AUTHENTICATION.md: H1 title fix
- Add UNRAID_CREDENTIALS_DIR to .env.example

Bump: 1.1.1 → 1.1.2

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-23 11:37:05 -04:00
Jacob Magar
d59f8c22a8 docs: rename GOOGLE_OAUTH.md → AUTHENTICATION.md, update references 2026-03-16 11:12:25 -04:00
Jacob Magar
cc24f1ec62 feat: add API key bearer token authentication
- ApiKeyVerifier(TokenVerifier) — validates Authorization: Bearer <key>
  against UNRAID_MCP_API_KEY; guards against empty-key bypass
- _build_auth() replaces module-level _build_google_auth() call:
  returns MultiAuth(server=google, verifiers=[api_key]) when both set,
  GoogleProvider alone, ApiKeyVerifier alone, or None
- settings.py: add UNRAID_MCP_API_KEY + is_api_key_auth_configured()
  + api_key_auth_enabled in get_config_summary()
- run_server(): improved auth status logging for all three states
- tests/test_api_key_auth.py: 9 tests covering verifier + _build_auth
- .env.example: add UNRAID_MCP_API_KEY section
- docs/GOOGLE_OAUTH.md: add API Key section
- README.md / CLAUDE.md: rename section, document both auth methods
- Fix pre-existing: test_health.py patched cache_middleware/error_middleware
  now match renamed _cache_middleware/_error_middleware in server.py
2026-03-16 11:11:38 -04:00
Jacob Magar
6f7a58a0f9 docs: add Google OAuth setup guide and update README/CLAUDE.md
- Create docs/GOOGLE_OAUTH.md: complete OAuth setup walkthrough
  (Google Cloud Console, env vars, JWT key generation, troubleshooting)
- README.md: add Google OAuth section with quick-setup steps + link
- CLAUDE.md: add JWT key generation tip + link to full guide
2026-03-16 10:59:30 -04:00
Jacob Magar
440245108a fix(tests): clear subscription_configs before auto-start tests to account for default SNAPSHOT_ACTIONS 2026-03-16 10:54:43 -04:00
Jacob Magar
9754261402 fix(auth): use setenv('') instead of delenv to prevent dotenv re-injection in tests 2026-03-16 10:51:14 -04:00
Jacob Magar
9e9915b2fa docs(auth): document Google OAuth setup in CLAUDE.md 2026-03-16 10:48:38 -04:00
Jacob Magar
2ab61be2df feat(auth): wire GoogleProvider into FastMCP, log auth status on startup
- 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)
2026-03-16 10:42:51 -04:00
Jacob Magar
b319cf4932 fix(auth): use dict[str, Any] for kwargs, add typing.Any import 2026-03-16 10:40:29 -04:00
Jacob Magar
0f46cb9713 test(auth): add _build_google_auth() unit tests and bump to v1.1.1
- 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>
2026-03-16 10:37:52 -04:00
Jacob Magar
1248ccd53e fix(auth): hoist _build_google_auth import to module level for ty compatibility 2026-03-16 10:37:36 -04:00
Jacob Magar
4a1ffcfd51 feat(auth): add _build_google_auth() builder with stdio warning
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.
2026-03-16 10:36:41 -04:00
Jacob Magar
f69aa94826 feat(dx): add fastmcp.json configs, module-level tool registration, tool timeout
- 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>
2026-03-16 10:32:16 -04:00
Jacob Magar
5187cf730f fix(auth): use Any return type in _reload_settings for ty compatibility 2026-03-16 10:29:56 -04:00
Jacob Magar
896fc8db1b feat(auth): add Google OAuth settings with is_google_auth_configured()
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.
2026-03-16 10:28:53 -04:00
Jacob Magar
7db878b80b docs: fix stale references in CLAUDE.md post-consolidation refactor
- Fix test patching example: tools.info → tools.unraid (old module deleted)
- Fix destructive actions: storage → disk (correct action domain)
- Update mutation handler ordering: QUERIES → _*_QUERIES domain dicts
- Add test-destructive.sh to scripts section
- Bump version 1.0.0 → 1.0.1

Co-authored-by: Claude <claude@anthropic.com>
2026-03-16 10:21:58 -04:00
Jacob Magar
3888b9cb4a fix: address markdown lint and doc accuracy issues (threads 6-13)
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
2026-03-16 10:01:25 -04:00
Jacob Magar
cf9449a15d fix: address 18 PR review comments (threads 1-18)
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)
2026-03-16 10:01:12 -04:00
Jacob Magar
884319ab11 fix: address 14 PR review comments from coderabbitai/chatgpt-codex
- 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
2026-03-16 03:10:01 -04:00
Jacob Magar
efaab031ae fix: address all 17 PR review comments
Resolves review threads:
- PRRT_kwDOO6Hdxs50fewG (setup.py): non-eliciting clients now return True
  from elicit_reset_confirmation so they can reconfigure without being blocked
- PRRT_kwDOO6Hdxs50fewM (test-tools.sh): add notification/recalculate smoke test
- PRRT_kwDOO6Hdxs50fewP (test-tools.sh): add system/array smoke test
- PRRT_kwDOO6Hdxs50fewT (resources.py): surface manager error state instead of
  reporting 'connecting' for permanently failed subscriptions
- PRRT_kwDOO6Hdxs50feAj (resources.py): use is not None check for empty cached dicts
- PRRT_kwDOO6Hdxs50fewY (integration tests): remove duplicate snapshot-registration
  tests already covered in test_resources.py
- PRRT_kwDOO6Hdxs50fewe (test_resources.py): replace brittle import-detail test
  with behavior tests for connecting/error states
- PRRT_kwDOO6Hdxs50fewh (test_customization.py): strengthen public_theme assertion
- PRRT_kwDOO6Hdxs50fewk (test_customization.py): strengthen theme assertion
- PRRT_kwDOO6Hdxs50fewo (__init__.py): correct subaction count ~88 -> ~107
- PRRT_kwDOO6Hdxs50fewx (test_oidc.py): assert providers list value directly
- PRRT_kwDOO6Hdxs50fewz (unraid.py): remove unreachable raise after vm handler
- PRRT_kwDOO6Hdxs50few2 (unraid.py): remove unreachable raise after docker handler
- PRRT_kwDOO6Hdxs50fev8 (CLAUDE.md): replace legacy 15-tool table with unified
  unraid action/subaction table
- PRRT_kwDOO6Hdxs50fev_ (test_oidc.py): assert providers + defaultAllowedOrigins
- PRRT_kwDOO6Hdxs50feAz (CLAUDE.md): update tool categories to unified API shape
- PRRT_kwDOO6Hdxs50feBE (CLAUDE.md/setup.py): update unraid_health refs to
  unraid(action=health, subaction=setup)
2026-03-16 02:58:54 -04:00
Jacob Magar
dab1cd6995 refactor(tools)!: consolidate 15 individual tools into single unified unraid tool
BREAKING CHANGE: Replaces 15 separate MCP tools (unraid_info, unraid_array,
unraid_storage, unraid_docker, unraid_vm, unraid_notifications, unraid_rclone,
unraid_users, unraid_keys, unraid_health, unraid_settings, unraid_customization,
unraid_plugins, unraid_oidc, unraid_live) with a single `unraid` tool using
action (domain) + subaction (operation) routing.

New interface: unraid(action="system", subaction="overview") replaces
unraid_info(action="overview"). All 15 domains and ~108 subactions preserved.

- Add unraid_mcp/tools/unraid.py (1891 lines, all domains consolidated)
- Remove 15 individual tool files
- Update tools/__init__.py to register single unified tool
- Update server.py for new tool registration pattern
- Update subscriptions/manager.py and resources.py for new tool names
- Update all 25 test files + integration/contract/safety/schema/property tests
- Update mcporter smoke-test script for new tool interface
- Bump version 0.6.0 → 1.0.0

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-16 02:29:57 -04:00
Jacob Magar
faf9fb9ad7 fix(guards): use Pydantic model for elicitation to get labeled checkbox instead of 'Value: []' 2026-03-15 23:48:53 -04:00
Jacob Magar
fe7b6485fd chore: ruff format types.py (pre-existing unformatted file) 2026-03-15 23:40:27 -04:00
Jacob Magar
d7545869e2 feat(guards): wire elicitation into notifications/vm/rclone/settings/storage
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.
2026-03-15 23:38:20 -04:00
Jacob Magar
cdab970c12 refactor(guards): migrate array/keys/plugins to gate_destructive_action
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.
2026-03-15 23:33:07 -04:00
Jacob Magar
80d2dd39ee refactor(guards): remove elicit_destructive_confirmation from setup.py (moved to guards.py)
Update array, keys, and plugins tool imports to source elicit_destructive_confirmation from core.guards instead of core.setup.
2026-03-15 23:29:22 -04:00
Jacob Magar
aa5fa3e177 feat(guards): add core/guards.py with gate_destructive_action helper 2026-03-15 23:25:39 -04:00