Commit Graph

125 Commits

Author SHA1 Message Date
Jacob Magar
6eafc16af7 feat(oidc): add unraid_oidc tool with providers, provider, configuration, public_providers, validate_session 2026-03-15 19:30:22 -04:00
Jacob Magar
ebba60c095 fix(types): suppress ty union-attr false positives on elicitation result.data access 2026-03-15 19:27:43 -04:00
Jacob Magar
2b4b1f0395 feat(plugins): add unraid_plugins tool with list, add, remove actions
Implements the unraid_plugins MCP tool (3 actions, 1 destructive) and adds
elicit_destructive_confirmation() to core/setup to support all tools that
gate dangerous mutations behind confirm=True with optional MCP elicitation.
2026-03-15 19:26:42 -04:00
Jacob Magar
d26467a4d0 feat(customization): add unraid_customization tool with theme, public_theme, is_initial_setup, sso_enabled, set_theme 2026-03-15 19:19:06 -04:00
Jacob Magar
76391b4d2b feat(keys): add add_role and remove_role actions for API key role management
Adds two new mutation actions to unraid_keys:
- add_role: calls apiKey.addRole with apiKeyId + role, requires key_id and roles
- remove_role: calls apiKey.removeRole with apiKeyId + role, requires key_id and roles

Updates safety audit to explicitly exempt remove_role from the delete/remove
heuristic (reversible action — role can be re-added). Updates schema coverage
test and adds schema validation tests for both new mutations.
2026-03-15 19:13:03 -04:00
Jacob Magar
0d4a3fa4e2 fix(live): validate log_tail path against allowlist, move guards before error handler
Add _ALLOWED_LOG_PREFIXES allowlist check to log_tail (mirrors storage.py pattern)
to prevent path traversal attacks. Move path/required guards before tool_error_handler
context so validation errors raise cleanly. Add two tests: ToolError propagation and
invalid path rejection.
2026-03-15 19:08:43 -04:00
Jacob Magar
3a72f6c6b9 feat(array): add parity_history, start/stop array, disk add/remove/mount/unmount/clear_stats
Expands unraid_array from 5 to 13 actions: adds parity_history query,
start_array/stop_array state mutations, and disk operations (add_disk,
remove_disk, mount_disk, unmount_disk, clear_disk_stats). Destructive
actions remove_disk and clear_disk_stats require confirm=True. Safety
audit tests updated to cover the new DESTRUCTIVE_ACTIONS registry entry.
2026-03-15 19:03:01 -04:00
Jacob Magar
675a466d02 feat(live): add unraid_live tool with 11 subscription snapshot actions
Creates unraid_mcp/tools/live.py with SNAPSHOT_ACTIONS (9 one-shot reads)
and COLLECT_ACTIONS (2 streaming collectors), plus tests/test_live.py
with 6 passing tests. Registers register_live_tool in server.py, bringing
the total to 12 tools.
2026-03-15 18:56:14 -04:00
Jacob Magar
5a3e8e285b fix(subscriptions): bound snapshot loops with asyncio.timeout, raise on collect errors
- Wrap async-for loops in asyncio.timeout() so both subscribe_once and subscribe_collect
  cannot hang indefinitely when no messages arrive after the handshake
- subscribe_once: TimeoutError → ToolError("Subscription timed out after Xs")
- subscribe_collect: TimeoutError → pass (return events collected so far)
- Remove manual deadline checks inside the loops (now redundant)
- subscribe_collect now raises ToolError on GraphQL payload errors instead of silently dropping them
- subscribe_collect handshake now distinguishes connection_error (auth) from unexpected type
2026-03-15 18:52:27 -04:00
Jacob Magar
181ad53414 feat(subscriptions): add subscribe_once and subscribe_collect snapshot helpers 2026-03-15 18:48:42 -04:00
Jacob Magar
72ccf9b074 chore: add settings slash command and bump to 0.4.8
- Add commands/settings.md slash command for unraid_settings tool
- Sync uv.lock after version bump

Co-authored-by: Claude <claude@anthropic.com>
2026-03-14 16:40:04 -04:00
Jacob Magar
5740f4848a chore: bump version to 0.4.7 2026-03-14 14:49:13 -04:00
Jacob Magar
4ce3edd423 docs: update credential setup to reflect ~/.unraid-mcp/.env flow 2026-03-14 14:48:06 -04:00
Jacob Magar
2c0f4a1730 feat(creds): add optional env_file from ~/.unraid-mcp/.env to docker-compose 2026-03-14 14:46:10 -04:00
Jacob Magar
a3754e37c3 feat(creds): setup declined message includes manual path and variable names 2026-03-14 14:45:35 -04:00
Jacob Magar
c4f1b2eb00 test(creds): add replacement test for credentials propagation through tool_error_handler 2026-03-14 14:21:21 -04:00
Jacob Magar
c80ab0ca6b refactor(creds): remove per-tool elicitation from unraid_info 2026-03-14 14:20:20 -04:00
Jacob Magar
08afdcc50e refactor(creds): remove per-tool elicitation from unraid_settings 2026-03-14 14:19:42 -04:00
Jacob Magar
ba7b8dfaa6 refactor(creds): remove per-tool elicitation from unraid_keys 2026-03-14 14:18:20 -04:00
Jacob Magar
23e70e46d0 refactor(creds): remove per-tool elicitation from unraid_users 2026-03-14 14:17:14 -04:00
Jacob Magar
fe66e8742c refactor(creds): remove per-tool elicitation from unraid_rclone 2026-03-14 14:16:34 -04:00
Jacob Magar
77f3d897a3 refactor(creds): remove per-tool elicitation from unraid_notifications 2026-03-14 14:16:07 -04:00
Jacob Magar
8c67145bcc refactor(creds): remove per-tool elicitation from unraid_vm 2026-03-14 14:14:22 -04:00
Jacob Magar
9fc85ea48c refactor(creds): remove per-tool elicitation from unraid_storage 2026-03-14 14:13:52 -04:00
Jacob Magar
d99855973a refactor(creds): remove per-tool elicitation from unraid_docker 2026-03-14 14:13:14 -04:00
Jacob Magar
9435a8c534 refactor(creds): remove per-tool elicitation from unraid_array 2026-03-14 14:09:14 -04:00
Jacob Magar
81f1fe174d feat(creds): tool_error_handler converts CredentialsNotConfiguredError to ToolError with path
Converts the CredentialsNotConfiguredError sentinel to a user-facing ToolError
in tool_error_handler, including the exact CREDENTIALS_ENV_PATH so users know
where to create the .env file. Removes the now-invalid per-tool elicitation
test (replaced by 2 new tests for the handler conversion behavior).
2026-03-14 14:05:59 -04:00
Jacob Magar
e930b868e4 feat(creds): write to ~/.unraid-mcp/.env with 700/600 permissions, seed from .env.example
- _write_env now creates CREDENTIALS_DIR (mode 700) and writes credentials
  to CREDENTIALS_ENV_PATH (mode 600) instead of PROJECT_ROOT/.env
- On first run (no .env yet), seeds file content from .env.example to
  preserve comments and structure
- elicit_and_configure catches NotImplementedError from ctx.elicit() so
  clients that don't support elicitation return False gracefully instead
  of propagating the exception
- Updated test_elicit_and_configure_writes_env_file to patch CREDENTIALS_DIR
  and CREDENTIALS_ENV_PATH instead of PROJECT_ROOT
- Added 5 new tests covering dir/file permissions, .env.example seeding,
  in-place credential update, and NotImplementedError guard
2026-03-14 14:00:57 -04:00
Jacob Magar
d8ce45c0fc feat(creds): add CREDENTIALS_DIR and CREDENTIALS_ENV_PATH to settings
Introduce a version-agnostic credential directory (~/.unraid-mcp, overridable
via UNRAID_CREDENTIALS_DIR env var) and surface it as CREDENTIALS_DIR and
CREDENTIALS_ENV_PATH module-level constants. Prepend the canonical .env path to
dotenv_paths so all runtimes (plugin, uv, Docker) resolve credentials from the
same stable location without relying on versioned plugin cache paths.
2026-03-14 13:54:14 -04:00
Jacob Magar
14e9dca8bc chore: bump version to 0.4.6 2026-03-14 04:30:01 -04:00
Jacob Magar
f0a97edbf7 docs: update credential setup docs to reflect elicitation flow 2026-03-14 04:29:35 -04:00
Jacob Magar
85cd173449 fix(elicitation): guard ctx=None in elicit_and_configure, cover all settings/docker/notifications actions
- setup.py: elicit_and_configure now accepts Context | None; returns False
  immediately when ctx is None instead of crashing with AttributeError
- settings.py: added CredentialsNotConfiguredError try/except guard around
  make_graphql_request calls in all 8 previously-unguarded actions
  (update_temperature, update_time, configure_ups, update_api, connect_sign_in,
  connect_sign_out, setup_remote_access, enable_dynamic_remote_access)
- docker.py: added guards to all 20 previously-unguarded make_graphql_request
  calls (details, logs, networks, network_details, port_conflicts, check_updates,
  restart, update_all, all 11 organizer mutations, and single-container fallback)
- notifications.py: added guards to all 11 previously-unguarded calls
  (list, warnings, create, archive/unread, delete, delete_archived, archive_all,
  archive_many, create_unique, unarchive_many, unarchive_all, recalculate)
2026-03-14 04:28:34 -04:00
Jacob Magar
e1c80cf1da feat(elicitation): add ctx + credential elicitation to unraid_settings 2026-03-14 04:19:08 -04:00
Jacob Magar
ba14a8d341 feat(elicitation): add ctx + credential elicitation to unraid_keys 2026-03-14 04:18:06 -04:00
Jacob Magar
cec254b432 feat(elicitation): add ctx + credential elicitation to unraid_users 2026-03-14 04:17:17 -04:00
Jacob Magar
dec80832ea feat(elicitation): add ctx + credential elicitation to unraid_rclone 2026-03-14 04:16:54 -04:00
Jacob Magar
4b4c8ddf63 feat(elicitation): add ctx + credential elicitation to unraid_notifications 2026-03-14 04:16:08 -04:00
Jacob Magar
dfcaa37614 feat(elicitation): add ctx + credential elicitation to unraid_vm 2026-03-14 04:14:43 -04:00
Jacob Magar
060acab239 feat(elicitation): add ctx + credential elicitation to unraid_storage 2026-03-14 04:14:15 -04:00
Jacob Magar
be186dc2d7 feat(elicitation): add ctx + credential elicitation to unraid_docker 2026-03-14 04:13:34 -04:00
Jacob Magar
13f85bd499 feat(elicitation): add ctx + credential elicitation to unraid_array 2026-03-14 04:11:38 -04:00
Jacob Magar
49264550b1 feat(elicitation): auto-elicit credentials on CredentialsNotConfiguredError in unraid_info 2026-03-14 04:07:51 -04:00
Jacob Magar
9be46750b8 feat(elicitation): add setup action to unraid_health 2026-03-14 04:02:15 -04:00
Jacob Magar
61604b313f fix(elicitation): pass CredentialsNotConfiguredError through tool_error_handler 2026-03-14 03:58:44 -04:00
Jacob Magar
8a986a84c2 feat(elicitation): raise CredentialsNotConfiguredError in client when creds absent
make_graphql_request now reads credentials from the settings module at call
time (via a local import) instead of relying on module-level names captured at
import time. When either credential is missing it raises CredentialsNotConfiguredError
(not ToolError), allowing callers to trigger elicitation rather than surfacing a
generic error to the MCP client.

Updated tests/test_client.py and tests/http_layer/test_request_construction.py
to patch unraid_mcp.config.settings.* instead of the now-removed client-module
attrs, and to expect CredentialsNotConfiguredError on missing credentials.
2026-03-14 03:55:57 -04:00
Jacob Magar
02e61b4290 refactor(elicitation): use PROJECT_ROOT directly (already a Path) 2026-03-14 03:50:45 -04:00
Jacob Magar
e73f791fd3 feat(elicitation): add elicit_and_configure() with .env persistence 2026-03-14 03:49:11 -04:00
Jacob Magar
7458409147 test(elicitation): assert warning is logged when creds missing at startup 2026-03-14 03:47:29 -04:00
Jacob Magar
42bfcc1998 feat(elicitation): degrade gracefully when credentials are missing at startup 2026-03-14 03:45:42 -04:00
Jacob Magar
a7988e1eae test(elicitation): fix os.environ leak in apply_runtime_config test 2026-03-14 03:44:34 -04:00