trmm-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@trmm-mcpwhich machines are offline and why"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
TacticalRMM MCP server
Lets an AI assistant read everything in your TacticalRMM install, and — when you explicitly switch it on — run commands on your managed machines.
Ask "which machines are offline and why", "what's eating memory on the front
desk PC", "show me every failed check this week", and get answers from live
fleet data. Turn on elevate mode and the assistant can also fix things, but
only after you approve each action yourself.
Why I built it
I manage machines for a number of clients, and TacticalRMM already collects far more about each one than anyone has time to sit and read. I wanted to actually use that data: run deeper diagnostics than the dashboard puts in front of you, produce per-client reports covering every device, and find the things worth fixing before someone rings up to report them.
It does grow billable work, and I think that is fine as long as the work is real. A disk throwing early SMART errors, a server quietly filling up, patches that stopped applying three months ago and nobody noticed — catching those is worth paying for. Billing for busywork is not. The test I hold this to is whether every item on a report is something the client can see the sense in fixing once it has been explained to them.
Related MCP server: kaseya-vsa-mcp
What you get
28 tools. 20 read-only ones covering agents, checks, alerts, services, processes, event logs, software inventory, patches, scripts and audit history; 8 execution tools for running commands and scripts, rebooting, controlling services, killing processes, and waking machines.
A full audit trail of everything. Every inbound request, every tool call with its arguments, every result, every TRMM API call, every approval decision and every refusal is written to a structured JSONL log with credential redaction and rotation. For a bridge that can run commands as SYSTEM across a fleet, knowing exactly who asked for what and when is not optional — so it is on by default and cannot be silently skipped. Details in Audit logging.
An approval gate the model cannot reach. In elevate mode an execution is
refused until you approve that exact call in a browser (password + TOTP) or
from a shell on the server. Approvals are bound to a fingerprint of the
arguments, are single-use, and expire. No tool exists that grants elevation.
Read-only that actually holds. Three independent layers: the execution tools are never registered, the HTTP client refuses non-read requests, and the API key itself is bound to a TRMM role with no execution permissions.
Built for real fleets. TRMM has no pagination and returns whole tables — one agent detail call measured 192 KB here. Responses are projected, capped and truncated with an explicit notice, so a single call cannot swallow the model's context.
HTTPS with bearer auth, a self-signed certificate carrying an IP SAN, and a hardened systemd unit. Clients trust that one certificate rather than skipping verification.
Works with Claude Code, Claude Desktop and LM Studio on macOS, Windows and Linux. Built and verified against TRMM 1.5.1. The TRMM API itself is documented independently in TRMM-API.md.
Three modes
|
|
| |
Tools exposed | 20 read tools | 28 | 28 |
Reads | yes | yes | yes |
Executions | never | only what you approve, one call at a time | always |
TRMM key for reads |
|
|
|
TRMM key for executions | — |
|
|
Good for | pure Q&A | day-to-day chat | unattended automation |
elevate is the one to use for interactive work: Claude can look at anything,
but the moment it wants to do something it stops and waits for you.
How elevate works
Claude calls an execution tool. It does not run. The call is refused with an explanation and a link.
You open
http://192.0.2.10:8770/approve/, see the exact command and target, and click Approve once (or runapprove.py okon the server).Claude retries the identical call. It runs, and the approval is consumed.
Anything further needs approving again — it is back to read-only by itself.
The approval is bound to a fingerprint of the exact arguments, so an approval
for hostname cannot be spent on a different command. Verified in the tests.
For a burst of work, open a window (e.g. 10 minutes / 5 uses) from the same page. Windows expire on their own; "Revoke every grant now" kills everything immediately.
Protecting the approval page
The approval page is the only thing between a model asking to run something and it running, so give it credentials of its own:
cd /opt/trmm-mcp && ./venv/bin/python setup_approval_auth.pyIt asks for a password, generates a TOTP secret, prints a QR code to scan with
any authenticator app, and writes the result to .env. Then restart the
server. Sign-in afterwards needs both the password and a 6-digit code.
Why bother, when the page already asked for a token: that token is the bearer token, and every MCP client config holds a copy. Anything with the token could approve its own requests, which defeats the point of out-of-band approval. Password + TOTP is a credential that exists only in your head and your phone.
What the implementation does:
The password is hashed with PBKDF2-HMAC-SHA256, 600k rounds, and a per-install salt. Only the hash is stored.
TOTP codes can't be replayed. A code is spent on use, and only once both factors pass, so a mistyped password doesn't burn a good code.
One 30-second step of drift either side is tolerated.
After five failed attempts it locks out with exponential backoff, per client address, and refuses correct credentials while the lockout lasts.
The session cookie holds no secret, just a signed expiry and an epoch (
HttpOnly,SameSite=Strict, 12 hours). Re-running setup bumps the epoch and signs everyone out.Failed logins are recorded as
approval_loginevents noting which factor failed. The page never tells the visitor which one was wrong.
TLS
The listener serves HTTPS using a self-signed certificate at
certs/cert.pem (key certs/key.pem, 10 years). It carries an IP SAN for
192.0.2.10 as well as 127.0.0.1, rmm-server, localhost and
mcp.example.com — without an IP SAN, a client dialling the address by
IP cannot verify the certificate at all and you are pushed into disabling
verification, which defeats the point.
Because traffic is encrypted, the session cookie is issued with Secure set.
Clients are told to trust this one certificate rather than to skip checking:
mcp-remote / Node —
NODE_EXTRA_CA_CERTSpointing at a copy ofcert.pem, notNODE_TLS_REJECT_UNAUTHORIZED=0.Browser — you will get a warning the first time. Either accept it, or import
cert.peminto Trusted Root Certification Authorities on the workstation to make it go away properly.
To regenerate (after an IP change, say), recreate the pair with the same SAN
list and restart. Set TRMM_MCP_TLS=false to fall back to plain HTTP.
Why not MCP elicitation
The protocol has an elicitation/create feature that would put the prompt right
in the chat, and it is the wrong tool here:
Claude Desktop does not implement it. It returns a synthetic
cancelin ~47 ms with no UI ever shown, so the server cannot tell refusal from silence.It is not a security boundary even where it works. The spec says clients SHOULD get user approval, not MUST, and explicitly declines to mandate any interaction model. Claude Code ships an
Elicitationhook whose documented purpose is auto-answering these prompts without showing a dialog, and anElicitationResulthook that can rewrite the user's answer.
So approval here happens on a channel the model has no access to. There is no tool that grants elevation — the only ways in are the browser page and a shell on this box. That is the property worth having, and the tests assert it.
Why read-only is actually read-only
Three independent layers, each sufficient on its own:
The execution tools are never registered in read-only mode, and Claude can't call a tool it can't see. Verified over the wire, not just in theory.
The HTTP client refuses non-read requests. GET is allowed; PATCH only for
/alerts/and/logs/audit/, which is how TRMM queries those. Everything else raises before a packet leaves the process.The API key itself can't execute.
mcp-readonlyis bound to a TRMM role with no execution permissions, so TRMM returns 403 even if the first two layers were bypassed. That key gets 403 onPOST /agents/<id>/cmd/,/runscript/,/reboot/and/wol/.
The MCP client does not retain or use the command key in read-only mode.
One honest caveat about layer 3
TRMM has no view-only permission for Windows services, processes, or
Windows updates — viewing and acting are gated by the same can_manage_procs /
can_manage_winsvcs / can_manage_winupdates flag (verified in
services/permissions.py, agents/permissions.py, winupdate/permissions.py).
Without them the read-only role cannot list running services or processes at all, which removes most of the value for diagnosing a sick machine. So the read-only role does hold those three flags. The consequence, stated plainly:
The read-only key, used outside this server, could stop a service, kill a process, or trigger a Windows update install.
It still cannot run commands, run scripts, reboot, or send WoL — those are 403 at the key level.
Through this MCP server none of it is reachable, because layer 2 refuses every non-GET request before it leaves the process, and layer 1 never exposes a tool that would try.
If you would rather have a strictly minimal key and lose service/process
visibility, drop VIEW_REQUIRES_MANAGE_PERMS in
provision_trmm_accounts.py and re-run it.
Setup
Already done on this box:
venv at
venv/withmcp2.0 andhttpxTRMM roles
MCP Read OnlyandMCP Command, usersmcp-readonlyandmcp-command(both blocked from UI login, unusable passwords)API keys written to
.env(mode 600)
To re-provision or rotate keys:
/rmm/api/env/bin/python /opt/trmm-mcp/provision_trmm_accounts.py --rotateRunning the server
To reach it from other machines, first tell it which address to serve on. These
go in .env, not in the unit file — the unit is in git, and an address baked in
there would follow every copy of it onto the wrong machine:
cat >> /opt/trmm-mcp/.env <<'EOF'
TRMM_MCP_HTTP_HOST=10.0.0.5
TRMM_MCP_PUBLIC_URL=https://10.0.0.5:8770
EOFSubstitute your own address. TRMM_MCP_PUBLIC_URL is what the assistant tells
you to open when something needs approving, so it has to be a URL your browser
can actually reach. Skip this step and the server binds 127.0.0.1, which is
fine if the client runs on the same box.
Then install the unit and start it:
sudo cp /opt/trmm-mcp/trmm-mcp.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now trmm-mcpCheck what it came up on — the startup line names the address, the mode and whether TLS is on:
journalctl -u trmm-mcp -n 5 --no-pager | grep listeningIt serves HTTPS on port 8770 by default and requires the bearer token from
.env. To run it in the foreground instead, for a quick test:
/opt/trmm-mcp/run.sh readonly httpThe listener runs stateless (TRMM_MCP_STATELESS_HTTP=true, the default). This
matters: with stateful streamable HTTP the server hands out a session id that
dies with the process, so restarting the service leaves any connected client
replaying a session the server has forgotten. Through mcp-remote that shows up
as tool calls hanging for minutes and then failing, while tools/list still
answers, with Rejected request with unknown or expired session ID in
logs/server.log. Stateless removes the failure mode; we use no
server-initiated requests, so it costs nothing. restart_test.py proves both
behaviours.
The bearer token is the only authentication on that port, so anything that can reach it inherits this server's TRMM permissions. Keep it on the LAN or behind a tunnel rather than binding a public interface.
Connecting a client
Claude Code, Claude Desktop and LM Studio are covered below, each on macOS,
Windows and Linux. Whichever you use, you need two things from the server: the
bearer token from .env, and a copy of certs/cert.pem so the client
can verify the TLS certificate.
Copy the certificate over first.
macOS and Linux:
scp rmmuser@192.0.2.10:/opt/trmm-mcp/certs/cert.pem ~/trmm-mcp-cert.pemWindows (PowerShell):
scp rmmuser@192.0.2.10:/opt/trmm-mcp/certs/cert.pem $env:USERPROFILE\trmm-mcp-cert.pemClaude Code — macOS, Windows, Linux
The simplest of the three, because Claude Code speaks HTTP directly and needs no bridge:
claude mcp add --transport http trmm https://192.0.2.10:8770/mcp \
--header "Authorization: Bearer <TRMM_MCP_AUTH_TOKEN>" --scope user--scope user makes it available across all your projects; leave it off for the
current project only. Claude Code reads the operating system's certificate
store, so once you have installed the certificate (see
Trusting the certificate) nothing further is
needed.
On the server itself you can skip the network entirely and run it over stdio:
claude mcp add trmm -- /opt/trmm-mcp/run.sh readonlySwap readonly for elevate to get the approval gate, or command for
unattended execution. Keeping two entries (trmm and trmm-command) makes the
active capability obvious at a glance.
Claude Desktop — macOS, Windows, Linux (beta)
Desktop's built-in "custom connector" UI cannot reach this server. Those
connectors are dialled from Anthropic's own infrastructure, so a private address
is unroutable from there and a self-signed certificate is rejected.
claude_desktop_config.json is stdio-only as well — it has no url field. The
working route is mcp-remote, a small Node bridge that Desktop launches locally
and which speaks HTTP to this server. Node 18+ is required.
Open the config with Settings → Developer → Edit Config (the Claude menu in the menu bar or app menu, not the in-window account settings). That button opens whichever file the install actually reads, which saves you guessing. The paths, if you want them directly:
OS | Path |
macOS |
|
Windows |
|
Linux |
|
macOS and Linux — npx is invoked directly:
{
"mcpServers": {
"tacticalrmm": {
"command": "npx",
"args": [
"-y", "mcp-remote@latest",
"https://192.0.2.10:8770/mcp",
"--transport", "http-only",
"--header", "Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer <TRMM_MCP_AUTH_TOKEN from .env>",
"NODE_EXTRA_CA_CERTS": "/Users/you/trmm-mcp-cert.pem"
}
}
}
}On Linux the certificate path is typically /home/you/trmm-mcp-cert.pem.
Windows — the same thing wrapped in cmd /c:
{
"mcpServers": {
"tacticalrmm": {
"command": "cmd",
"args": [
"/c", "npx", "-y", "mcp-remote@latest",
"https://192.0.2.10:8770/mcp",
"--transport", "http-only",
"--header", "Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer <TRMM_MCP_AUTH_TOKEN from .env>",
"NODE_EXTRA_CA_CERTS": "C:\\Users\\YOU\\trmm-mcp-cert.pem"
}
}
}
}Three details worth understanding:
cmd /cis a Windows-only workaround. There,npxis the batch shimnpx.cmd, which Node only resolves throughPATHEXTwhen spawned with a shell; Electron hosts that spawn without one fail withspawn npx ENOENT. No such shim exists on macOS or Linux, so the wrapper is unnecessary there. Some Windows builds do spawn with a shell and work without it — treatcmd /cas the safe default rather than a hard requirement.Authorization:${AUTH_HEADER}with the value inenv, and no space after the colon. Claude Desktop on Windows does not escape spaces insideargs, which splits a literalBearer xyzinto stray arguments. mcp-remote expands${VAR}itself, so the space stays safely inside the variable. Harmless on every platform, so use it everywhere.NODE_EXTRA_CA_CERTSis the same variable name on all three systems. It appends to Node's trust store rather than disabling verification, so the certificate still has to match the address you dial. Prefer it overNODE_TLS_REJECT_UNAUTHORIZED=0, which switches verification off for everything that process touches. Escape the backslashes on Windows.
Then quit Claude Desktop completely and reopen it. On Windows and macOS, closing the window leaves it running — use the tray or menu-bar icon and pick Quit. Start a new chat afterwards, since the tool list is fixed when a conversation begins.
If Desktop appears to ignore your config on Windows, you are probably on the
MSIX build from the Store or WinGet, which virtualizes the filesystem. It reads
%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude\, while the "Edit
Config" button may open the un-virtualized %APPDATA% copy. Find the real one:
$p = Get-ChildItem "$env:LOCALAPPDATA\Packages\Claude_*" -Directory | Select-Object -First 1
"$($p.FullName)\LocalCache\Roaming\Claude\claude_desktop_config.json"To test the bridge outside Desktop when something misbehaves:
npx -p mcp-remote@latest mcp-remote-client https://192.0.2.10:8770/mcp --transport http-only --header "Authorization: Bearer <token>"Connection failures are logged to mcp.log, and each server's stderr to
mcp-server-tacticalrmm.log — under ~/Library/Logs/Claude on macOS and
%APPDATA%\Claude\logs on Windows (with the same MSIX redirect).
LM Studio — macOS, Windows, Linux
LM Studio speaks remote HTTP natively, so no bridge is needed. Edit mcp.json
through the app: right sidebar → Program → Install → Edit mcp.json.
The documented path (~/.lmstudio/mcp.json) and the path several versions
actually use (~/.cache/lm-studio/mcp.json) disagree, so the in-app editor is
the reliable route.
{
"mcpServers": {
"tacticalrmm": {
"url": "https://192.0.2.10:8770/mcp",
"headers": { "Authorization": "Bearer <TRMM_MCP_AUTH_TOKEN from .env>" },
"timeout": 60000
}
}
}Two caveats. LM Studio rejects self-signed certificates and offers no setting to trust one, so start it with the CA in the environment:
NODE_EXTRA_CA_CERTS=~/trmm-mcp-cert.pem "/Applications/LM Studio.app/Contents/MacOS/LM Studio" # macOS
NODE_EXTRA_CA_CERTS=~/trmm-mcp-cert.pem lm-studio # Linux$env:NODE_EXTRA_CA_CERTS="$env:USERPROFILE\trmm-mcp-cert.pem"; & "$env:LOCALAPPDATA\Programs\LM Studio\LM Studio.exe"And recent versions block private IP addresses for servers added dynamically
through the API. Declaring the server in mcp.json, as above, is the supported
way around that.
LM Studio prompts before every tool call and shows the arguments, which is a useful second pair of eyes — but it is the client asking, and it can be set to "always allow". The approval gate on this server is the real boundary.
Over SSH instead — no open port
Rather than exposing the HTTP listener, a client can run the stdio server across SSH. Nothing listens on the network and SSH keys do the authentication. This suits Claude Desktop, which has no HTTP transport of its own.
Confirm passwordless SSH from the workstation first.
macOS and Linux:
ssh -o BatchMode=yes rmmuser@192.0.2.10 "echo OK"
ssh-keygen -t ed25519 -C "mcp-client" # if it prompted
ssh-copy-id rmmuser@192.0.2.10Windows (PowerShell):
ssh -o BatchMode=yes rmmuser@192.0.2.10 "echo OK"
ssh-keygen -t ed25519 -C "mcp-client" # if it prompted
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh rmmuser@192.0.2.10 "cat >> ~/.ssh/authorized_keys"Then, in claude_desktop_config.json — ssh on macOS and Linux,
C:\\Windows\\System32\\OpenSSH\\ssh.exe on Windows:
{
"mcpServers": {
"tacticalrmm": {
"command": "ssh",
"args": [
"-T",
"-o", "BatchMode=yes",
"-o", "StrictHostKeyChecking=accept-new",
"rmmuser@192.0.2.10",
"/opt/trmm-mcp/run.sh readonly"
]
}
}
}-T is required: a pseudo-TTY would corrupt the JSON-RPC stream. BatchMode
makes a missing key fail immediately instead of hanging on a password prompt the
client cannot answer.
Verified on this server: launching under a minimal environment from a different
working directory still handshakes cleanly, and ~/.bashrc has the standard
non-interactive guard, so nothing pollutes the protocol stream.
Trusting the certificate
Node-based clients (Claude Desktop via mcp-remote, LM Studio) read
NODE_EXTRA_CA_CERTS and need nothing else. Claude Code and your browser read
the operating system store, so install the certificate there to use the approval
page without warnings.
macOS:
sudo security add-trusted-cert -d -r trustAsRoot -k /Library/Keychains/System.keychain ~/trmm-mcp-cert.pemWindows (elevated prompt):
certutil -addstore -f root "%USERPROFILE%\trmm-mcp-cert.pem"Linux (Debian/Ubuntu — note the .crt extension is required):
sudo cp ~/trmm-mcp-cert.pem /usr/local/share/ca-certificates/trmm-mcp.crt && sudo update-ca-certificatesLinux (RHEL/Fedora):
sudo cp ~/trmm-mcp-cert.pem /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust extractTwo browser quirks. Firefox keeps its own certificate store on every platform
and ignores the system one, so import there separately or set
security.enterprise_roots.enabled in about:config. Chrome on Linux also uses
its own NSS database rather than the system bundle:
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "trmm-mcp" -i ~/trmm-mcp-cert.pemAlternatively just accept the browser warning once — the approval page still protects itself with a password and TOTP.
Tools
Read (both modes)
Tool | What it gives you |
| Counts plus the offline / needs-reboot / failing-check / pending-patch lists. Start here. |
| Agents with status; filter by client, site, type, online state, hostname |
| Full detail for one machine (heavy sections omitted by default) |
| Windows services, filterable by name and state |
| Live process list sorted by memory or CPU |
| Windows event log, filterable by level and text |
| Configured checks and their status |
| Past command/script runs with their output |
| Installed software inventory |
| Patch status |
| Automated tasks on an agent |
| Clients and sites with ids |
| Current alerts |
| Script library; full source code |
| Queued agent work |
| Who changed what, when |
| Server debug log |
| Versions and the active mode |
| Escape hatch — raw GET against any API path |
Execution (command mode only)
Tool | Notes |
| Shell command, returns output |
| Saved script by id, waits for output |
| Immediate reboot |
| start / stop / restart a Windows service |
| Kill by PID |
| WoL packet |
| Force checks to run now |
| Run an automated task now |
Anywhere a tool takes an agent you can pass the hostname or the agent_id — hostnames are resolved automatically, and an ambiguous one is rejected rather than guessed.
Deliberately not exposed
Bulk / fleet-wide execution (
/agents/actions/bulk/): highest blast radius, and TRMM returns no output for it anyway.Server-side scripts (
run_on_server), which run on the TRMM server itself.Config mutation. No editing agents, policies, users, or settings.
API key enumeration and the global keystore, which both leak credentials.
Adding any of these is a small edit to server.py. They were left out on
purpose, not overlooked.
Safety features in command mode
Every mutating call is audit-logged to
command-audit.logas JSON lines (timestamp, mode, method, path, body, outcome), refusals included.Destructive-command patterns are refused client-side:
rm -rf /,mkfs,dd of=/dev/*,format c:,diskpart,cipher /w, recursiveRemove-ItemonC:\, and forced immediateshutdown. Override withTRMM_MCP_BLOCK_PATTERNS(set empty to disable).Optional agent allowlist: set
TRMM_MCP_AGENT_ALLOWLISTto a comma-separated list of hostnames/agent_ids to hard-scope what can be touched.
Audit logging
Everything the server is asked to do, everything it does, and everything that
fails is recorded under logs/. Both files rotate at 10 MB × 5 backups and are
mode 600.
File | What's in it |
| The audit trail: one JSON object per line |
| Human-readable diagnostics — warnings, errors, tracebacks, and anything uvicorn/httpx/the SDK emit |
| Mutating TRMM calls only, kept as a short separate record |
Event kinds in events.jsonl:
kind | Meaning |
| Process start, with mode and transport |
| An inbound MCP call, with tool name and full arguments |
| Its outcome: ok/error, duration, result payload |
| Every TRMM API request — method, path, status, bytes, duration, whether the command key was used |
| A state-changing TRMM call |
| Approval refused or spent |
| Approve/deny/window/revoke decisions |
| A refusal — read-only guard, destructive pattern, agent allowlist |
| An exception, with type, message and traceback |
Reading it:
cd /opt/trmm-mcp && ./venv/bin/python logs.py -n 40-f follows live, -k error or -k blocked filters by kind, -t run_command
by tool, --full prints whole records. It's plain JSONL, so jq works too:
jq -c 'select(.kind=="response" and .ok==false)' /opt/trmm-mcp/logs/events.jsonlCredentials are redacted on the way to disk — the TRMM API keys and the
bearer token are replaced with <redacted:abcd...> wherever they appear,
including inside error text. Verified by a test that greps the log for each
live secret.
Two things to know. Tool results are recorded, and for an RMM that means
command output can land in the log — that is usually what you want for an audit
trail, but treat logs/ as sensitive. Payloads are clipped to
TRMM_MCP_LOG_PAYLOAD_CHARS (default 4000, -1 for everything, 0 to record
sizes only). And nothing is ever written to stdout, because under stdio that
channel carries the protocol.
Backups
Two things need backing up on this box, and TacticalRMM itself is the important one — it holds agent enrolment, history and MeshCentral state, none of which can be recreated.
What | Script | Output |
TacticalRMM (Postgres ×2, MeshCentral, certs, configs) |
|
|
This MCP server (.env, TLS keypair, approval state, code) |
|
|
Both use the same retention so there is one scheme to remember: daily kept 14 days, weekly 60, monthly 380. Weekly is Friday, monthly is the 10th.
Install the schedule:
sudo mkdir -p /rmmbackups/{daily,weekly,monthly} /rmmbackups/mcp && sudo chmod 700 /rmmbackups && sudo cp /opt/trmm-mcp/backup-units/*.service /opt/trmm-mcp/backup-units/*.timer /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now mcp-backup.timer trmm-backup.timerThe MCP backup deliberately excludes venv/ (rebuildable from
requirements.txt) so an archive is ~200 KB rather than hundreds of megabytes.
Every archive carries a RESTORE.txt with step-by-step instructions and a
MANIFEST.sha256 of every file, plus a .sha256 alongside it.
Encrypt before it leaves the box. An archive contains the TRMM API keys, the bearer token, the TLS private key and the approval password hash + TOTP secret — it is a credential store. Create a passphrase file and the script switches to GPG AES-256 automatically:
openssl rand -base64 48 > /opt/trmm-mcp/.backup-passphrase && chmod 600 /opt/trmm-mcp/.backup-passphraseStore that passphrase somewhere other than this machine, or the backups are unrecoverable when you most need them.
Check an archive is actually restorable:
/opt/trmm-mcp/backup-mcp.sh --verify /rmmbackups/mcp/daily/<file>The script verifies each new archive before pruning old ones, so a broken
backup never causes a good one to be deleted. Both services write
/rmmbackups/LAST_SUCCESS_* on success and append to
/rmmbackups/BACKUP-FAILURES.log on failure — point a TRMM check at the age of
those files and your RMM will tell you when its own backups stop running.
Still on you: these all live on the same disk as the data they protect. Copy
/rmmbackups somewhere else — that is the difference between a backup and a
convenience.
Installing as a package
The server is a proper Python package (pyproject.toml), pinned to the SDK 2.0
API it targets:
cd /opt/trmm-mcp && ./venv/bin/pip install -e .That registers a trmm-mcp console entry point equivalent to
python -m trmm_mcp.server. The existing systemd unit and run.sh keep working
either way — installing is only needed if you want the entry point or to build a
distributable wheel.
Tool annotations
Every tool carries MCP hints (readOnlyHint / destructiveHint /
idempotentHint / openWorldHint) so a client can reason about safety on its
own — colour a destructive tool, refuse to auto-run one — independent of the
approval gate. They're advisory; the real enforcement is still the out-of-band
gate. The classification lives in two small sets in server.py
(_DESTRUCTIVE_TOOLS / _SAFE_WRITE_TOOLS), applied after registration, so
adding a tool means adding one line there, not editing a decorator.
License
AGPL-3.0-or-later (LICENSE), with per-file SPDX headers. The copyleft terms
mean anyone who runs a modified copy as a network service must publish their
changes — deliberate, to keep execution-capable forks open.
Rebuilding the venv
Dependencies are pinned in requirements.txt. This matters: the server is
written against MCP SDK 2.0, whose API differs from 1.x (mcp.server.fastmcp
no longer exists, results arrive as dicts, is_error not isError). An
unpinned rebuild can silently pull an incompatible SDK.
cd /opt/trmm-mcp && ./venv/bin/pip install -r requirements.txtConfiguration
All settings are environment variables, read from .env in this directory
(real environment variables take precedence).
Variable | Default | Meaning |
| — | Backend base URL |
|
|
|
| unset | PBKDF2 hash; set by |
| unset | Base32 TOTP secret; set by the same script |
|
| Approval-page session lifetime, seconds |
|
| Failed sign-ins before lockout |
|
| Seconds an unapproved request stays open |
|
| Ceiling on any approval window |
| derived | Approval URL shown to the user |
|
| Where grants are persisted |
| — | Key used in read-only mode |
| — | Key used for executions in |
|
|
|
|
| HTTP listener |
|
| Truncation cap per tool result |
| empty (all) | Restrict execution targets |
| built-in list | Refused command regexes |
|
| Mutation log path |
|
| Where events.jsonl and server.log live |
|
| Diagnostic verbosity |
|
| Per-payload cap; |
|
| Rotation |
|
| CA for the self-signed origin cert |
|
| Set false only to disable verification outright |
|
| Connect/write timeout, seconds |
|
| Read timeout — must exceed the longest tool timeout, since synchronous runs hold the connection open |
TLS note: rather than disabling verification for the self-signed origin cert, the server uses that cert as its own CA bundle — so the connection is still verified.
Context-size handling
TRMM has no pagination and returns whole tables. On this install a single
GET /agents/<id>/ was 192 KB (services 118 KB, wmi_detail 60 KB).
Dumped raw, one call would consume a large share of the model's context.
So: trmm_get_agent omits heavy sections unless you name them in include
(the same agent comes back as 3.3 KB, a 58× reduction); list tools project
to useful fields and take limit; and every result is capped at
TRMM_MCP_MAX_RESPONSE_CHARS with an explicit truncation notice rather than
silent cutoff.
Output shape
Every tool returns a JSON object, never a bare list. This matters: the MCP
layer emits one content block per item for a list return, and an empty list
produces no content at all — so "no checks are configured" would be
indistinguishable from a failed call. Listing tools therefore return
{"count": N, "<thing>": [...]}, and an empty result reads as
{"count": 0, "checks": [], "agent": "workstation-01"}.
Testing
cd /opt/trmm-mcp && ./venv/bin/python e2e_test.pyThe main one: drives the server over the real MCP protocol exactly as Claude does — a full read-only diagnostic workflow, then command mode with real execution and the guards, then the HTTP transport. 25 checks.
cd /opt/trmm-mcp && TRMM_MCP_MODE=readonly ./venv/bin/python selftest.pyChecks live API reads, hostname resolution, payload trimming, and that the
read-only guard blocks writes. Run with TRMM_MCP_MODE=command to additionally
verify the destructive-pattern guard and a real command execution.
cd /opt/trmm-mcp && ./venv/bin/python protocol_test.py readonlyLaunches the server as a real MCP client would and asserts no execution tools are exposed over the wire.
Troubleshooting
Symptom | Cause |
| The role lacks that flag — add it in |
| You are in read-only mode. Expected. |
| Missing trailing slash, or the endpoint wants PATCH |
| A required body field was missing — see TRMM-API.md §4 |
| Agent offline; there is no queueing |
| Ambiguous hostname — pass the agent_id |
Files
provision_trmm_accounts.py creates TRMM roles/users/keys, writes .env
trmm_mcp/config.py env config, mode selection, guard patterns
trmm_mcp/client.py HTTP client, read-only enforcement, audit log
trmm_mcp/server.py tool definitions
run.sh launcher
trmm_mcp/observability.py logging: event stream, redaction, MCP middleware
trmm_mcp/approval_auth.py password hashing, TOTP, sessions, lockout
setup_approval_auth.py enrol the approval-page password + 2FA
approval_auth_test.py auth suite (hashing, 2FA, replay, lockout)
approve.py CLI to approve/deny/window/revoke
logs.py read/follow/filter the audit trail
logging_test.py asserts what gets logged, and that secrets don't
elevation_test.py elevate-mode suite (refuse, approve, consume, revert)
e2e_test.py full protocol-level suite (both modes + HTTP)
selftest.py live API checks
smoketest_remaining.py exercises every read tool against a live agent
protocol_test.py minimal MCP handshake check
trmm-mcp.service optional systemd unit for HTTP mode
.env credentials (mode 600)
command-audit.log append-only record of mutating callsThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseAqualityCmaintenanceAn MCP server that connects AI assistants to the NinjaOne remote monitoring and management platform via the REST API v2. It provides tools for device inventory, organization management, alert handling, maintenance scheduling, and automated job execution.221
- Alicense-qualityAmaintenanceMCP server for Kaseya VSA — endpoints, patches, procedures, alarms, and tickets. Enables AI assistants to manage and monitor devices via the Kaseya VSA RMM platform.Apache 2.0
- AlicenseAqualityDmaintenanceMCP server that exposes OSSEC HIDS security monitoring capabilities as tools, resources, and prompts for AI assistants.262MIT
- Alicense-qualityAmaintenanceMCP server for interacting with the ThreatLocker Portal API, enabling querying of computers, applications, policies, audit logs, and more through AI assistants.1GPL 3.0
Related MCP Connectors
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
MCP server for Gainium — manage trading bots, deals, and balances via AI assistants
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/shin2344234/trmm-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server