dsm-mcp
by dogeared
README.md
# dsm-mcp
A [Model Context Protocol](https://modelcontextprotocol.io) server for Synology
**DSM** and **Container Manager**. It lets an MCP client (Claude) inspect the NAS
and manage its containers directly over the Synology Web API — no browser, and no
per-request DSM login/2FA. The server logs in once with a dedicated service
account and keeps the session alive itself, which is effectively the
"non-expiring session" you wanted: Claude never re-authenticates.
Companion to `arr-mcp`. Same shape (Python, streamable-HTTP, bearer auth), a
different port (8788), and a Synology-specific client.
## Tools
| Area | Tools |
|------|-------|
| Discovery/health | `dsm_health`, `dsm_api_discover`, `dsm_raw_call` |
| System | `dsm_system_info`, `dsm_storage`, `dsm_utilization` |
| Containers | `dsm_docker_containers`, `dsm_docker_container`, `dsm_docker_action`*, `dsm_docker_logs`, `dsm_docker_projects` |
`*` = write action (start/stop/restart a container). `dsm_raw_call` can invoke
any DSM method, including writes — it's the escape hatch for the parts of DSM's
API that vary by version.
### About DSM's API (read this)
Synology's Web API is **discovery-driven and version/model-dependent**. Each tool
resolves the correct CGI path and version at call time via `SYNO.API.Info`, so the
system/storage/utilization tools are robust. The **Docker/Container Manager**
methods are less consistently documented across DSM releases — if
`dsm_docker_logs`, `dsm_docker_projects`, or an action's params don't match your
DSM, run `dsm_api_discover` (query `SYNO.Docker` or `all`) to see exactly what
your NAS exposes, then drive it with `dsm_raw_call`. Nothing is hardcoded that
can't be worked around live.
## Service account (do this first)
Don't point this at your main admin. In DSM:
1. **Control Panel → User & Group → Create** a user, e.g. `claude-svc`.
2. Grant only what's needed: **Container Manager** app permission, and read of
system/storage. Keep it out of `administrators` if your DSM version allows the
Docker API without admin; if it requires admin, prefer a separate admin-group
account used *only* here.
* Note: As of this writing, the service account must be a member of the
administrators group to be able to access the container manager endpoints
3. **Disable 2FA** on this account (a daemon can't type a rotating code). If your
policy enforces 2FA, enroll a trusted device once and put its token in
`DSM_DEVICE_ID`.
4. Optionally restrict it to the LAN (Control Panel → Security → firewall / IP
rules), since the MCP server sits on the LAN and only the tunnel is public.
## Setup
```bash
cp config.example.env .env
# edit .env: DSM_URL, the service account + password, a strong MCP_AUTH_TOKEN.
# HTTPS with the self-signed cert? use :5001 and set DSM_VERIFY_SSL=false
docker compose up -d --build
docker logs -f dsm-mcp # confirm it starts; auth runs on the first tool call
```
Run without Docker (dev):
```bash
pip install -r requirements.txt
set -a; source .env; set +a
python server.py
```
## Exposing it to Claude
Endpoint: `https://<your-hostname>/mcp`. Same as arr-mcp:
1. Tunnel it (dedicated `cloudflared`, or a hostname on your existing Jellyfin
tunnel → `http://dsm-mcp:8788`), ideally behind **Cloudflare Access**.
2. Claude → Settings → Connectors → custom connector → the `/mcp` URL, with
`Authorization: Bearer <MCP_AUTH_TOKEN>`.
## Security notes
- This can **restart containers** and read system internals, and it authenticates
as a DSM user. Keep the account least-privileged, keep 2FA-off scoped to *only*
that LAN account, and always put the endpoint behind the bearer token (and
ideally Cloudflare Access).
- Credentials live only in `.env` (git/docker-ignored). The MCP client never sees
them — it only sees tool results.
- No account-deletion, no DSM settings-mutation, no volume/pool destructive tools
are exposed. `dsm_raw_call` is powerful by design; treat the endpoint's auth
accordingly.
## Files
- `server.py` — MCP server + tools (streamable-HTTP + bearer auth)
- `dsm_client.py` — Synology Web API client (discovery, login, session refresh)
- `config.py` — builds the client from env vars
- `Dockerfile` / `docker-compose.yml` — container + optional tunnel
- `config.example.env` — copy to `.env`
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues