datto-rmm-mcp
by MSPbotsAI
README.md
# datto-rmm-mcp
MCP server for **Datto RMM** — device inventory, the component (script) library,
and Quick Job execution (run a component on a device right now), over the
[Model Context Protocol](https://modelcontextprotocol.io/) (Streamable HTTP/SSE
transport).
Built for [PRD-16562](https://app.clickup.com/t/2280862/PRD-16562) (on-prem
operations via Datto RMM, alongside self-host/NinjaOne) after a feasibility
investigation confirmed the API surface — see Known Gaps for exactly what was
and wasn't verified before this was written.
## When would you use this
- "What devices do we have in Datto?" / find a device before running
something on it → `dattormm_get_devices`
- "What scripts/components can we run?" → `dattormm_get_components`
- "Run [component] on [device] now" → `dattormm_run_component`
(⚠️ destructive, irreversible — see Known Gaps for what you can and can't
confirm afterward)
- "Did that job finish?" → `dattormm_get_job_status`
## Tools
授权需要 `X-Datto-Api-Key` / `X-Datto-Api-Secret` / `X-Datto-Api-Url` 三个请求头(见下方授权说明)。
| Tool | 功能 | 参数 |
|---|---|---|
| `dattormm_get_devices` | 列出该租户Datto RMM账号下的全部设备 | `page`(可选)、`max_results`(可选) |
| `dattormm_get_components` | 列出可运行的组件/脚本库 | 无 |
| `dattormm_run_component` | 立即在指定设备上运行指定组件——Datto的"Quick Job",**破坏性、不可撤销** | `device_uid`(必填)、`component_uid`(必填)、`variables`(可选)、`confirm`(必填,必须为true) |
| `dattormm_get_job_status` | 查询某次job是否已到达终态 | `job_uid`(必填) |
> **`dattormm_get_job_status`只能确认job是否"completed",不能确认成败**——见Known Gaps。
## Quick Start
### Docker (recommended)
```bash
docker compose up --build
```
The server starts on `http://localhost:8080`.
### Local (uv)
```bash
uv sync
python -m datto_rmm_mcp
```
## Health Check
```bash
curl http://localhost:8080/health
# {"status": "ok"}
```
No credentials are required for the health endpoint.
## 授权参数说明 (Authentication)
Every request to `/mcp` must include the following HTTP headers (provided by the
MCP caller/gateway):
| Header | 类型 | 是否必填 | 字段描述 |
|---|---|---|---|
| `X-Datto-Api-Key` | string | 必填 | 租户在Datto RMM里生成的API Key。用作OAuth2 password grant的username。 |
| `X-Datto-Api-Secret` | string | 必填 | 对应的API Secret Key。用作password。 |
| `X-Datto-Api-Url` | string | 必填 | 该租户账号所在的Datto RMM区域API host(Datto是多区域架构,每个租户账号固定绑在一个区域上,如`zinfandel-api.centrastage.net`)。裸host或带`https://`前缀均可,服务端会自动归一化。 |
Missing any of the three headers returns `401 Unauthorized`。
**认证机制**:本服务收到这三个凭据后,自己去调Datto的OAuth2 password grant(`POST /auth/oauth/token`,client_id/secret是Datto官方公开的固定值`public-client`/`public`,租户的API Key/Secret作为username/password),换出真正的bearer token再去调实际接口——不是网关代发token,是这个服务自己做的(因为Datto这套OAuth的client凭据本身就是公开常量,不存在需要网关代管的机密)。Token只在一次请求的生命周期内持有,从不缓存。
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `MCP_HTTP_PORT` | `8080` | Listening port |
| `MCP_HTTP_HOST` | `0.0.0.0` | Listening host |
## MCP Endpoint
```
POST http://localhost:8080/mcp
```
Connect your MCP client with:
- Transport: `http` (Streamable HTTP / SSE)
- Headers: `X-Datto-Api-Key`, `X-Datto-Api-Secret`, `X-Datto-Api-Url` (all required)
## 测试示例 (Test Example)
```bash
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "X-Datto-Api-Key: <api-key>" \
-H "X-Datto-Api-Secret: <api-secret>" \
-H "X-Datto-Api-Url: zinfandel-api.centrastage.net" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "dattormm_get_devices", "arguments": { "max_results": 10 } }
}'
```
> ⚠️ 本仓库为公开仓库,请勿在任何提交的文件中写入真实的API Key/Secret,
> 上面的占位符仅为示意。
## Known Gaps
- **What was actually verified live, and how.** This server was written after
a feasibility investigation on real customer Datto RMM accounts (read-only
credentials, OAuth2 password grant against real `tenant_integration`
records), not from documentation alone:
- `GET /api/v2/account/devices` — called for real, returned real device
records (1711 devices in one tested account).
- `GET /api/v2/account/components` — called for real, returned 250 real
components.
- `GET /api/v2/job/{jobUid}` — called for real, twice, against two
different real job UIDs on two different tenant accounts. Both returned
only `{id, dateCreated, name, uid, status}` — **no exitCode, stdout,
stderr, or success/failure field of any kind.**
- `PUT /api/v2/device/{deviceUid}/quickjob` (the tool that actually runs a
script) has **never been executed** — by design, the investigation was
read-only to stay invisible to customers. Its request/response contract
in `api_client.py`/`tools/jobs.py` is taken from Datto's own current
official documentation (rmm.datto.com/help/en/Content/2SETUP/APIv2.htm),
not from a real call. **Test this for real (a Datto trial/sandbox
account, or an explicit customer-consented dry run) before treating this
tool as production-ready.**
- **No success/failure or stdout/stderr reporting, on purpose — and this is
not a version artifact.** Datto's own current API documentation states
outright: "It is not possible from within the API to examine the success
or failure result of a completed job schedule and access the
StdOut/StdErr." Datto's **v14.0.0 release notes separately claim** "New API
endpoints under v2/job now allow retrieval of job results, including
StdOut and StdErr" — these two official Datto documents contradict each
other, and this investigation could not resolve it: the "new endpoint" the
release notes describe was never found (WebSearch results describing it
could not be traced to a real, citable page), and there's no way to read a
tested account's platform version from the API to know whether it should
even have this capability. What's certain: **the two real accounts tested,
right now, do not return this data via `GET /v2/job/{jobUid}`.** Given
this, `dattormm_get_job_status`'s docstring is deliberately conservative —
it reports only "reached a terminal state," never success. Do not add a
success/failure field to this tool without a fresh, real test proving the
new endpoint (whatever it turns out to be) actually exists and works.
- **`dattormm_run_component`'s request shape is desk-verified, not
field-verified.** `{"componentUid": ..., "<variableName>": "<value>", ...}`
is what Datto's docs say to send; the docs also say a successful response
contains a `uid` usable as `dattormm_get_job_status`'s `job_uid` — this
server is built to that contract, but no real call has confirmed it works
as documented, whether variable names are validated, or what a validation
error actually looks like from this specific endpoint (blind probing
during this investigation got a generic `{"detail":"Failed to read
request"}` for every guessed body shape, including ones matching the
eventual documented shape — that response never got more specific no
matter what was sent, so it's untested whether the documented shape
actually parses).
- **componentUid discovery**: `dattormm_get_components` is the intended way
to find one (confirmed working), even though Datto's own docs say to copy
it from the UI manually — the API path works in practice, use it.
- **No write/admin endpoints beyond quickjob are wrapped**: device
claim/unclaim, site management, alert management, and anything else in
Datto's API are out of scope for this MCP; PRD-16562 only asked for
deployment/execution + inventory.
- **Pagination parameters (`page`, `max`) are passed through, not
independently verified beyond a `max=2` smoke test** — the exact upper
bound Datto enforces, and whether `page` is 0- or 1-indexed, weren't
confirmed field-by-field.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues