MCP server for monitoring a Linux server over JSON-RPC.
GET /healthpublic health endpoint.GET /.well-known/mcppublic MCP discovery endpoint.POST /mcpMCP JSON-RPC endpoint (bearer-token protected).initializeaccepts modern protocol versions (including2025-03-26) and negotiates gracefully.- MCP tools:
list_services,list_timers,list_logs. - MCP resources:
resource://services/snapshot,resource://services/failed,resource://logs/recent. - Bearer-token authentication using
MCP_API_TOKEN.
list_services: lists*.serviceunits with optionalscope,state,name_contains,limit, andsummary.list_timers: lists*.timerunits with optionalscope,name_contains,state,limit,sort,order,overdue_only,include_persistent, andsummary.list_logs: lists journald logs with requiredstart_utc/end_utcand optionalscope,priority,unit,exclude_units,grep,order,limit,allow_large_window, andsummary.
scope supports system|user|both and defaults to system for all three list tools.
Note: It is strongly recommended to run this service behind a reverse proxy (e.g., Nginx, HAProxy, Envoy) that takes care of TLS termination and restricts network access.
DO NOT EXPOSE THIS TO THE INTERNET!
See Security and Threat Model for the authenticated-client and runaway-agent threat boundary, attack scenarios, controls, and residual risks. The server permits read-only, non-secret monitoring disclosure and accepts denial-of-service risk only from token-holding clients, but it must not permit persistent host/workload modification or intentional secret disclosure. Because it uses plain HTTP and a static bearer token, use TLS and network access controls whenever traffic crosses an untrusted network.
| Variable | Required | Default | Description |
|---|---|---|---|
MCP_API_TOKEN |
yes | — | Static API token (minimum 16 characters). |
BIND_ADDR |
no | 127.0.0.1 |
Bind address. |
BIND_PORT |
no | 8080 |
Bind port. |
export MCP_API_TOKEN="a-secure-token-at-least-16-chars"
# optional:
# export BIND_ADDR="127.0.0.1"
# export BIND_PORT="8080"
cargo runcurl -s http://127.0.0.1:8080/healthcurl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","clientInfo":{"name":"example-client","version":"1.0.0"},"capabilities":{}}}' \
http://127.0.0.1:8080/mcpcurl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
http://127.0.0.1:8080/mcpcurl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_services","arguments":{"state":"failed","name_contains":"sshd","limit":200}}}' \
http://127.0.0.1:8080/mcpcurl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"list_timers","arguments":{"scope":"both","sort":"next","order":"asc","limit":200}}}' \
http://127.0.0.1:8080/mcplist_logs is strict about optional filters:
- Omit
prioritywhen you do not want a priority threshold. Valid values are0through7, or aliases such aserr,warning,info, anddebug.priorityis not a regex field, so values such as.*return JSON-RPC-32602withinvalid_priority. - Omit
unitwhen you do not want a unit filter. Do not sendunit: ""; empty unit names return JSON-RPC-32602withinvalid_unit. - Use
grepfor message text filtering. Plain strings are substring filters, and regex-lite patterns go there, for example/timeout|failed/. - Keep
start_utcandend_utcas RFC3339 UTC strings ending inZ. Windows over 7 days requireallow_large_window: true. exclude_unitscan be omitted when empty. If present, each entry must be a valid unit name.
curl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"list_logs","arguments":{"scope":"both","priority":"err","unit":"sshd_service","exclude_units":["cron.service"],"grep":"/timeout|failed/","order":"desc","start_utc":"2026-02-27T00:00:00Z","end_utc":"2026-02-27T01:00:00Z","limit":200}}}' \
http://127.0.0.1:8080/mcpFor the input shape in the original error report, the equivalent valid request is:
{
"allow_large_window": true,
"end_utc": "2026-05-17T23:59:59Z",
"grep": "error",
"limit": 20,
"order": "desc",
"scope": "system",
"start_utc": "2026-05-14T00:00:00Z",
"summary": true
}The omitted fields are intentional: priority: ".*" is invalid because priority accepts only journald severity thresholds, and unit: "" is invalid because a provided unit filter must be a non-empty unit identifier.
curl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"list_services","arguments":{"summary":true}}}' \
http://127.0.0.1:8080/mcp
curl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"list_timers","arguments":{"scope":"both","summary":true}}}' \
http://127.0.0.1:8080/mcp
curl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"list_logs","arguments":{"scope":"both","start_utc":"2026-02-27T00:00:00Z","end_utc":"2026-02-27T01:00:00Z","summary":true}}}' \
http://127.0.0.1:8080/mcpcurl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"list_services","arguments":{"scope":"global"}}}' \
http://127.0.0.1:8080/mcpExpected: JSON-RPC error -32602 with stable data code invalid_scope.
curl -s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MCP_API_TOKEN" \
-d '{"jsonrpc":"2.0","id":5,"method":"resources/read","params":{"uri":"resource://services/failed"}}' \
http://127.0.0.1:8080/mcpUse this sequence before handoff or release:
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo testlist_logsrequires UTC RFC3339 timestamps withZsuffix forstart_utcandend_utc.- Time windows over 7 days require
allow_large_window=true. - Timer and service tooling are read-only and do not mutate systemd state.