Skip to main content
Glama
0mandrock1

mcp-vpsobs

by 0mandrock1

mcp-vpsobs

An AI agent that is troubleshooting a production incident wants to look at logs, check whether a service is up, and see how full a disk is. The usual way to give it that is to hand it a shell: SSH credentials, or a "run this command" tool with a string it can pass to subprocess. That solves the immediate problem and creates a much bigger one -- the agent (and anything that can make it say the wrong thing, from a poisoned log line to a compromised MCP client) now has full read/write access to a host that keeps other people's systems running.

mcp-vpsobs is a Model Context Protocol server that gives an agent eyes on a single Linux host without giving it hands. It does not accept a command string. It does not have a write, restart, kill or exec path anywhere in the code. Every unit, container, log and directory it can see is named ahead of time in a YAML allowlist; anything not named is denied before a subprocess is even started. Every byte it returns has already passed through a redaction pass that strips bearer tokens, password= assignments, provider-shaped API keys and e-mail addresses.

Why not just give the agent SSH

The honest argument for a narrower surface:

  • The blast radius of a mistake is bounded. If the agent (or the model behind it, or a prompt-injected log line it read) decides to do something destructive, there is no rm, no systemctl restart, no docker exec for it to reach for. The collectors in this repo only ever call backend.run with a fixed, read-only argv or backend.read_file; there is no code path that writes to the host.

  • The allowlist is explicit and inspectable. A YAML file is the entire answer to "what can this agent see on my host", instead of "whatever the SSH key's permissions and the model's judgement allow at any given moment."

  • Denial happens before any host access. Config.check_unit, check_container, site_log_path and resolve_directory raise DeniedError before a collector builds an argv or opens a file -- tests/test_attack.py proves this with a backend fixture that fails the test if it is ever called.

  • Secrets get a chance to not leak. Every line handed back to the agent passes through redact().

What this design does not protect against, stated plainly rather than hand-waved:

  • Resource exhaustion by a well-behaved-looking client. Each call has a timeout and a byte cap, but nothing here rate-limits repeated calls. An agent that calls journal_tail in a tight loop can still generate load proportional to how fast it can issue MCP requests. There is no token-bucket or per-minute quota in this codebase.

  • Novel secret shapes. The redaction patterns are shaped (bearer tokens, key=value assignments, known provider key formats, e-mails, optionally private IPv4) rather than a generic entropy detector, on purpose -- a blanket "redact anything that looks random" rule would also eat git SHAs, UUIDs and request IDs and make the logs useless. A secret in a format none of the patterns anticipate will pass through unredacted.

  • An operator who allowlists too much. If you put / in directories or a unit that happens to log credentials in units, this server will faithfully show an agent everything inside it. The allowlist moves the trust decision to the config file; it does not make the decision for you.

  • Vulnerabilities in the tools it shells out to. This server is only as read-only as systemctl, journalctl, docker, nginx and openssl are. A bug in one of those binaries, or in the mcp SDK itself, is outside what an allowlist can fix.

  • Audit logging. There is no record kept of which tool was called with which arguments. Everything here is visibility of the host, not visibility into how the agent used this server. If you need "who asked what and when," it is not implemented -- add logging in front of the transport before relying on this in a setting where that matters.

Related MCP server: Linux MCP Server

Architecture

config.yaml --> Config (allowlist + limits)
                    |
                    v
              collectors/*  --uses-->  Backend (Protocol)
                    |                       |
                    v                       +-- SubprocessBackend (real: argv-only, shell=False)
              redact() / redact_lines()     +-- FakeBackend (tests: canned argv -> CommandResult)
                    |
                    v
                server.py (FastMCP resources + tools, stdio)
  • Allowlist first. Config (in src/vpsobs/config.py, not modified by this change) is the only place that knows what is visible. A collector cannot "forget" a check because there is nothing for it to check except by asking Config.

  • Backend injection. Every collector takes a Backend -- run(argv, timeout, max_bytes) or read_file(path, max_bytes, tail) -- so tests run against a FakeBackend fed canned command output. No test in this repo touches a real systemctl, docker, journalctl, nginx or openssl.

  • Redaction is a pipeline, not a suggestion. Anything that reaches an agent goes through redact()/redact_lines() on the way out.

Resources

URI

Contents

host://summary

uptime, load average, memory, disk usage of /

host://services

allowlisted systemd units with active/sub/load state

host://containers

allowlisted docker containers (name, image, status, ports)

host://sites

nginx server_name -> root/proxy_pass

Tools

Tool

Signature

journal_tail

(unit, lines=100, since=None, grep=None)

docker_logs

(container, lines=100, since=None)

nginx_access_stats

(site, window="1h") -- request count, status histogram, top paths, top user agents, error sample

cert_expiry

() -- days remaining per configured certificate

disk_hogs

(path, depth=1, top=10)

port_map

() -- listening sockets with owning process, where known

Every tool result is a JSON string. Failures (denied, not configured, timeout, backend error) come back as {"error": ..., "message": ..., "details": {...}} in the same shape, rather than as a different kind of MCP protocol error -- see "Design notes" below.

Configuration

Copy config.example.yaml to config.yaml and edit it:

  • units -- systemd unit names visible to host://services and journal_tail.

  • containers -- docker container names visible to host://containers and docker_logs.

  • sites -- site label -> access log path, used by nginx_access_stats.

  • directories -- absolute directory roots disk_hogs may inspect.

  • certs -- certificate file paths cert_expiry reports on.

  • redaction.redact_private_ipv4 -- also scrub RFC1918/loopback addresses (off by default).

  • limits.timeout_seconds / limits.max_output_bytes / limits.max_lines -- the hard ceiling every collector runs under.

  • nginx_config_command -- override if nginx -T is not the right command for your setup (default ["nginx", "-T"]).

Design notes

Places where the spec left a decision open and the simpler option was taken:

  • MCP SDK surface: built on mcp.server.fastmcp.FastMCP (decorator-based resources/tools, stdio transport) rather than the low-level mcp.server.Server API, for less boilerplate per resource/tool.

  • Error reporting: VpsobsError subclasses are caught in server.py and serialized as {"error": ...} JSON text in the normal tool/resource result, rather than raised as MCP protocol-level errors. One response shape for both success and failure is simpler for a caller to parse.

  • host://containers filtering: shows only containers already on the containers allowlist, rather than showing every running container annotated with an allowlisted/not flag. Fewer things for an agent to see that it can't act on, and no leaking of the existence of containers the operator never chose to expose.

  • port_map and the unit allowlist: port_map does not cross-reference listening sockets against config.units. It reports whatever ss reports; nothing is denied either way, so filtering would only be cosmetic. Note ss -tulpn needs to run as root (or with equivalent capabilities) to populate the process/pid column at all -- without that, process/pid come back null.

  • host://sites directive scoping: the nginx config parser tracks server { ... } blocks (including nested location blocks) for server_name, root and proxy_pass, but does not model nginx's directive-inheritance rules (e.g. a root set on the surrounding http block applying to a server that declares none itself).

  • Config file location: --config defaults to ./config.yaml in the current working directory; there is no /etc/vpsobs/config.yaml fallback. Simpler to reason about, and the caller (Claude Desktop config, claude mcp add, systemd unit, etc.) already has to specify a working directory or an absolute --config path either way.

  • No audit logging: confirmed absent by reading server.py and every collector -- no tool call is logged anywhere beyond whatever the mcp library itself does. Stated explicitly in "Why not just give the agent SSH" above rather than left implicit.

  • journal_tail/docker_logs grep: implemented as a plain Python substring filter applied to lines already returned by journalctl/docker logs, never passed to a subprocess or treated as a regex -- there is no way for a grep value to become a shell metacharacter problem because it never reaches an argv.

Running it

pip install -e .
cp config.example.yaml config.yaml   # then edit the allowlist for your host
mcp-vpsobs --config config.yaml

The server speaks MCP over stdio. To point Claude Desktop or claude mcp add at it:

claude mcp add vpsobs -- mcp-vpsobs --config /path/to/config.yaml

or, in Claude Desktop's claude_desktop_config.json:

{
  "mcpServers": {
    "vpsobs": {
      "command": "mcp-vpsobs",
      "args": ["--config", "/path/to/config.yaml"]
    }
  }
}

Testing

pip install -e ".[dev]"
pytest -q

Every test runs against a fixture backend (tests/conftest.py's FakeBackend) or a pure string-parsing helper -- none of them shell out to a real systemctl, journalctl, docker, nginx or openssl, so the suite passes in CI (see .github/workflows/ci.yml) with none of those installed.

As of this commit the suite defines 72 test functions across 13 files (grep -roh "def test_" tests/ | wc -l); parametrized cases push the number pytest actually collects higher than that -- run pytest -q for the exact, current count.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    A
    quality
    F
    maintenance
    Enables read-only Linux system diagnostics and troubleshooting on RHEL-based systems, including system info, services, processes, logs, network, and storage analysis. Supports both local and remote SSH execution across multiple hosts.
    Last updated
    20
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    An MCP server for read-only Linux system administration and diagnostics on RHEL-based systems via SSH. It enables users to troubleshoot remote hosts by accessing system information, services, logs, and network configurations through natural language.
    Last updated
    19
    272
    Apache 2.0
  • A
    license
    -
    quality
    D
    maintenance
    This MCP server provides read-only Linux system diagnostics tools for inspecting system information, processes, and log snapshots. It enables AI models to analyze Linux system health, troubleshoot issues, and review security through workflow prompts and HTTP transport with API key authentication.
    Last updated
    MIT
  • A
    license
    -
    quality
    D
    maintenance
    Enables interaction with Linux system operations via MCP, including CPU, memory, processes, storage, filesystem, hardware, network, monitoring, and logs.
    Last updated
    9
    MIT

View all related MCP servers

Related MCP Connectors

  • Operate your own Linux servers from your LLM. Requires the SentinelX agent installed per host.

  • Remote MCP for Android CLI agent build gate, structured receipts, audit logs, and reviewer-ready evi

  • Remote MCP for A2A dependency inspector MCP, structured receipts, audit logs, and reviewer-ready evi

View all MCP Connectors

Latest Blog Posts

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/0mandrock1/mcp-vpsobs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server