{"id":"3f7c895c-847f-478d-b020-685335f4ed2e","slug":"unitrends-mcp","kind":"mcp","name":"unitrends-mcp","summary":"MCP server for Unitrends Backup — appliances, jobs, recovery points, replication, alerts","owner":"WYRE-AI","repoUrl":"https://github.com/WYRE-AI/unitrends-mcp","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-05-01T19:53:42.000Z","addedAt":"2026-10-14T05:20:30.769Z","logoUrl":"https://avatars.githubusercontent.com/u/288510302?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","memory-knowledge"],"tags":["backup","bcdr","claude","kaseya","msp","unitrends"],"runtime":"docker","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":true,"reviewed":false,"syncedAt":"2026-10-14T19:32:28.084Z","sample":false,"url":"https://conduits.dev/tools/unitrends-mcp","language":"TypeScript","transports":["stdio"],"install":{"docker":"ghcr.io/wyre-technology/unitrends-mcp:v1.1.3"},"requirements":{"env":[{"name":"UNITRENDS_BASE_URL","secret":false,"required":true,"description":"Base URL of the Unitrends appliance or MSP Console (e.g. https://unitrends.example.com)"},{"name":"UNITRENDS_USERNAME","secret":false,"required":true,"description":"Unitrends API username"},{"name":"UNITRENDS_PASSWORD","secret":true,"required":true,"description":"Unitrends API password"},{"name":"UNITRENDS_VERIFY_TLS","secret":false,"required":false,"description":"Verify TLS certificates. Set 'false' for self-signed appliances."},{"name":"MCP_TRANSPORT","secret":false,"required":false,"description":"Transport mode for the server. Set to 'stdio' for local CLI use; the image defaults to 'http' for gateway hosting."},{"name":"AUTH_MODE","secret":false,"required":false,"description":"Credential source: 'env' reads vars locally, 'gateway' expects header injection from the WYRE MCP Gateway."},{"name":"LOG_LEVEL","secret":false,"required":false,"description":"Log verbosity: debug, info, warn, error"}],"upstreamMayCharge":false},"tools":null,"readme":"# Unitrends Backup MCP Server\n\n[![CI](https://github.com/WYRE-AI/unitrends-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/WYRE-AI/unitrends-mcp/actions/workflows/ci.yml)\n[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nA [Model Context Protocol](https://modelcontextprotocol.io) server exposing the\n[Unitrends Backup](https://www.unitrends.com/) API to Claude and other MCP clients.\n\n## What it does\n\nSurface backup posture from your Unitrends appliances (or MSP Console) directly\nto AI assistants — list appliances and protected assets, monitor running and\nhistorical backup jobs, browse recovery points, queue restores and replication,\nand review open alarms and RPO compliance.\n\n- **Interactive Appliance Card (MCP Apps)**: `unitrends_get_appliance` renders as an interactive card in MCP Apps hosts (Claude Desktop/web); neutral by default, brandable via `window.__BRAND__` injection or `MCP_BRAND_*` env vars; plain-JSON behavior is unchanged in other hosts\n\n## Tools\n\n| Tool | Purpose |\n| --- | --- |\n| `unitrends_list_appliances` | List appliances under the MSP Console |\n| `unitrends_get_appliance` | Get a single appliance's details (renders as an interactive card in MCP Apps hosts) |\n| `unitrends_list_assets` | List protected assets (require applianceId — elicits if missing) |\n| `unitrends_get_asset` | Fetch a single asset detail |\n| `unitrends_list_running_jobs` | Currently running and queued backup jobs |\n| `unitrends_list_job_history` | Historical jobs (date-range elicitation) |\n| `unitrends_list_recovery_points` | Recovery points for an asset |\n| `unitrends_queue_restore` | Queue a restore (DESTRUCTIVE — requires confirmation) |\n| `unitrends_get_restore_status` | Check restore progress |\n| `unitrends_list_alerts` | Open alarms |\n| `unitrends_get_success_rate` | RPO compliance report (date-range elicitation) |\n\n## Credentials\n\nCopy [`.env.example`](.env.example). Leave `CONDUIT_S2S_SECRET` empty in that file.\n\n### Local (env mode and stdio)\n\nThe stdio transport reads these variables and does not use `CONDUIT_S2S_SECRET`.\n\n```sh\nexport UNITRENDS_BASE_URL=\"https://unitrends.example.com\"\nexport UNITRENDS_USERNAME=\"...\"\nexport UNITRENDS_PASSWORD=\"...\"\nexport UNITRENDS_VERIFY_TLS=\"true\"   # 'false' for self-signed appliances\n```\n\n### HTTP service-to-service auth\n\n`CONDUIT_S2S_SECRET` is required when `MCP_TRANSPORT=http`. If it is empty, or a known placeholder (`changeme`, `change-me`, `placeholder`, `secret`, `example`, `your-s2s-secret`, `your-secret`, `replace-me`, `replace-with-a-real-secret`, `s2s-secret`), the process logs an error and exits non-zero. It never prints the secret. Generate one with `openssl rand -hex 32`.\n\n`MCP_ALLOW_INSECURE_DEV=1` is the only way to start HTTP without that secret. The flag must be exactly `1`. The process logs a warning, does not check `X-Gateway-S2S`, and listens on `127.0.0.1` only. A non-loopback `MCP_HTTP_HOST` (including `0.0.0.0`) is ignored in that mode and the override is logged. A known placeholder still refuses to start; unset the secret to use the bypass.\n\n`MCP_HTTP_HOST` is honored when a real secret is set, including `0.0.0.0`. When `MCP_HTTP_HOST` is unset and a secret is set, the server binds `0.0.0.0`. When there is no secret, the bind address is `127.0.0.1`. The container image sets `MCP_HTTP_HOST=0.0.0.0`.\n\nWith a real secret, every `/mcp` request must include `X-Gateway-S2S`. The value is `t=<unix seconds>,v1=<hex hmac-sha256>`. The MAC is HMAC-SHA256 over the ASCII string `t=<unix seconds>` (the same prefix that appears in the header), keyed with `CONDUIT_S2S_SECRET`. The timestamp must be within 300 seconds of the server clock. A missing or invalid header is `401` and is rejected before any vendor credential is read.\n\n`/health` stays unauthenticated, does not read credentials, and is the container health check.\n\n### Hosted (gateway mode)\n\n`AUTH_MODE=gateway` (the Compose default) requires these headers on every `/mcp` request. A request missing any of the required headers is `401` and does not fall back to `UNITRENDS_*` in the process environment.\n\n- `X-Unitrends-Base-URL` (required)\n- `X-Unitrends-Username` (required)\n- `X-Unitrends-Password` (required, secret)\n- `X-Unitrends-Verify-TLS` (optional, default `true`)\n- `X-Gateway-S2S` (required when `CONDUIT_S2S_SECRET` is set)\n\n```typescript\n// Gateway injects vendor credentials and the X-Gateway-S2S proof\nconst response = await fetch(\"http://gateway:8080/mcp\", {\n  method: \"POST\",\n  headers: {\n    \"content-type\": \"application/json\",\n    accept: \"application/json, text/event-stream\",\n    \"x-gateway-s2s\": \"<t=unix,v1=hmac-sha256 of t=unix, keyed with CONDUIT_S2S_SECRET>\",\n    \"x-unitrends-base-url\": \"https://unitrends.example.com\",\n    \"x-unitrends-username\": \"your_username\",\n    \"x-unitrends-password\": \"your_password\",\n  },\n  body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"tools/list\" }),\n});\n```\n\nSingle-tenant HTTP (`AUTH_MODE=env`, the image default) still requires a valid `X-Gateway-S2S` header, then reads `UNITRENDS_*` from the environment when the request does not carry credential headers.\n\n## Run\n\n```sh\nnpm install\nnpm run build\nnpm start                       # stdio\n\n# HTTP refuses to start until CONDUIT_S2S_SECRET is a real secret\nCONDUIT_S2S_SECRET=\"$CONDUIT_S2S_SECRET\" MCP_TRANSPORT=http npm start\n\n# Local HTTP only, without a service-to-service secret (always binds 127.0.0.1)\nMCP_TRANSPORT=http MCP_ALLOW_INSECURE_DEV=1 npm start\n```\n\n### Docker\n\nThe image listens on `0.0.0.0` inside the container and defaults to `AUTH_MODE=env`. Publish the port on loopback and pass the S2S secret. HTTP will not start when `CONDUIT_S2S_SECRET` is empty or a known placeholder.\n\n```sh\ndocker build -t unitrends-mcp .\ndocker run \\\n  -e CONDUIT_S2S_SECRET=\"$CONDUIT_S2S_SECRET\" \\\n  -e UNITRENDS_BASE_URL=\"https://unitrends.example.com\" \\\n  -e UNITRENDS_USERNAME=\"...\" \\\n  -e UNITRENDS_PASSWORD=\"...\" \\\n  -p 127.0.0.1:8080:8080 \\\n  unitrends-mcp\n```\n\nGateway mode does not read those vendor variables. Credentials arrive on each request, and the request must also carry `X-Gateway-S2S` signed with `CONDUIT_S2S_SECRET`:\n\n```sh\ndocker run \\\n  -e CONDUIT_S2S_SECRET=\"$CONDUIT_S2S_SECRET\" \\\n  -e AUTH_MODE=gateway \\\n  -p 127.0.0.1:8080:8080 \\\n  unitrends-mcp\n```\n\nCompose refuses to start when `CONDUIT_S2S_SECRET` is unset or empty, sets `AUTH_MODE=gateway`, does not inject `UNITRENDS_BASE_URL`, `UNITRENDS_USERNAME`, or `UNITRENDS_PASSWORD`, and publishes `127.0.0.1:8080:8080`:\n\n```sh\nCONDUIT_S2S_SECRET=\"$CONDUIT_S2S_SECRET\" docker compose up\n```\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE).\n"}