mirror of
https://github.com/jmagar/unraid-mcp
synced 2026-04-21 13:37:53 +00:00
Generate 42 documentation files across 6 subdirectories (mcp/, plugin/, repo/, stack/, upstream/, and root) based on plugin-lab templates. All content is real — no template placeholders — covering tools (15 action domains, 107 subactions), resources (10 live subscriptions), elicitation flows, GraphQL integration, auth, deployment, CI/CD, testing, and security guardrails.
5.2 KiB
5.2 KiB
Architecture Overview -- unraid-mcp
Component diagram
MCP Client (Claude Code / Codex / Gemini / HTTP)
|
| MCP Protocol (stdio or streamable-http)
|
v
+----------------------------------------------+
| ASGI Middleware Stack |
| 1. HealthMiddleware (GET /health) |
| 2. WellKnownMiddleware (OAuth discovery) |
| 3. BearerAuthMiddleware (RFC 6750) |
+----------------------------------------------+
|
v
+----------------------------------------------+
| FastMCP Server |
| MCP Middleware Chain: |
| 1. LoggingMiddleware (request logging) |
| 2. ErrorHandlingMiddleware (exception wrap) |
| 3. RateLimitingMiddleware (540 req/min) |
| 4. ResponseLimitingMiddleware (512 KB cap) |
+----------------------------------------------+
|
+----> Tools (4 registered)
| +-- unraid (action+subaction router)
| | +-- _system.py (18 subactions)
| | +-- _health.py (4 subactions)
| | +-- _array.py (13 subactions)
| | +-- _disk.py (6 subactions)
| | +-- _docker.py (7 subactions)
| | +-- _vm.py (9 subactions)
| | +-- _notification (12 subactions)
| | +-- _key.py (7 subactions)
| | +-- _plugin.py (3 subactions)
| | +-- _rclone.py (4 subactions)
| | +-- _setting.py (2 subactions)
| | +-- _customization (5 subactions)
| | +-- _oidc.py (5 subactions)
| | +-- _user.py (1 subaction)
| | +-- _live.py (11 subactions)
| +-- unraid_help
| +-- diagnose_subscriptions
| +-- test_subscription_query
|
+----> Resources (10 registered)
+-- unraid://logs/stream
+-- unraid://live/{action} (9 subscriptions)
|
v
+---------------------------+
| SubscriptionManager |
| (WebSocket connections) |
+---------------------------+
|
| GraphQL-over-WebSocket
| (graphql-transport-ws)
|
v
+---------------------------+
| Unraid GraphQL API |
| (upstream server) |
+---------------------------+
Data flow
Tool call (query)
- Client sends
tools/callwithaction+subaction - ASGI middleware validates bearer token (HTTP) or passes through (stdio)
- MCP middleware logs, handles errors, checks rate limit, caps response size
unraid()routes to domain handler (e.g.,_handle_docker)- Handler looks up pre-built GraphQL query from domain
_*_QUERIESdict core/client.pysends async HTTP request to Unraid API withx-api-key- Response parsed, formatted, returned to client
Tool call (mutation with destructive gate)
- Client sends
tools/callwith destructive subaction gate_destructive_action()checks if subaction is in_*_DESTRUCTIVEset- If
confirm=True: proceed immediately - If interactive client: send elicitation request with
_ConfirmActionform - If user confirms: proceed; otherwise raise
ToolError - Handler sends GraphQL mutation
- Response returned to client
Resource read (live data)
- Client reads
unraid://live/<action> ensure_subscriptions_started()initializes WebSocket connections (once)- Resource function checks
SubscriptionManager.resource_datacache - If data available: return with
_fetched_attimestamp - If connecting: return "connecting" placeholder
- If failed (terminal state): return error message
- If auto-start disabled: fall back to
subscribe_onceone-shot query
Credential setup (elicitation)
- Client calls
unraid(action="health", subaction="setup") - If credentials exist: probe connection, ask to reset via elicitation
- If resetting or first run: send
_UnraidCredentialselicitation form - User fills in API URL and key
- Write to
~/.unraid-mcp/.env(atomic: tmp +os.replace) - Apply to running process via
apply_runtime_config() - Return success message
Key design decisions
Consolidated tool pattern
One unraid tool with 15 action domains instead of 15+ separate tools. This:
- Reduces MCP context window usage (one tool description covers all operations)
- Simplifies client tool selection
- Enables shared parameters across domains
Tradeoff: Caching is disabled because the single tool mixes reads and mutations.
Pre-built query dicts
GraphQL queries are pre-defined in Python dicts, not constructed dynamically. This:
- Prevents GraphQL injection
- Makes queries discoverable and testable
- Enables schema validation tests (119 tests)
Persistent subscription manager
WebSocket connections are maintained as long-running async tasks. This:
- Provides instant access to live data via MCP resources
- Reduces latency compared to on-demand connections
- Supports automatic reconnection with exponential backoff
See Also
- TECH.md -- Technology choices
- ../mcp/PATTERNS.md -- Code patterns
- ../mcp/TOOLS.md -- Tool reference