generated from atu/saiop-infrastructure
6.5 KiB
6.5 KiB
name, description, version, author, license, platforms, prerequisites, metadata
| name | description | version | author | license | platforms | prerequisites | metadata | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| saiop-mcp-servers | MCP server setup for SAIOP (git, postgres read-only, docker, proxmox snapshot access) — least-privilege patterns, and what was operator-approved to widen beyond the initial conservative defaults. | 2.0.0 | Claude Code (SAIOP ops session) | MIT |
|
|
|
MCP servers on SAIOP (Phase 6.3)
When to use
Adding or auditing MCP tool access for Hermes (Deployment Guide §6.3).
What's wired up and why
- git-mcp (
uvx mcp-server-git -r /opt/ai-stack) — all 12 tools enabled (including commit/branch/checkout). Low risk: git is naturally revertible, no host-level escalation path. - postgres-mcp (
uvx postgres-mcp --access-mode restricted) — a dedicatedmcp_readonlyPostgres role exists (SELECT-only, granted onsaiop+netboxDBs specifically, not the other app DBs). Two independent enforcement layers: the role itself can't write (verified:DELETEreturnspermission denied), and--access-mode restrictedblocks non-SELECT statements at the app layer too. Connection string goes through theDATABASE_URIenv var (checked the package source —os.environ.get("DATABASE_URI", args.database_url)), not a CLI arg, since CLI args are visible to any local user viaps aux.postgresql's port is published to127.0.0.1:5432only (loopback) since Hermes runs on the host, not in Docker, and can't resolve container names. - docker-mcp (
uvx docker-mcp) — operator-approved widening: originally restricted toget-logs/list-containersonly (the package has no native read-only mode;create-container/deploy-composeare full write/deploy). After an explicit flag-and-confirm round, the operator chose to enable all 4 tools. This is a real, deliberate widening of what Hermes can do to the host via MCP — if revisiting this decision, remember the restriction mechanism is still available and field-proven (tools: {include: [...]}inconfig.yaml, same mechanism verified for the Telegram platform-toolset restriction) if you want to dial it back. - proxmox-mcp — a custom-written, single-tool MCP server
(
/opt/ai-stack/hermes/scripts/proxmox_mcp.py, run viauv run --with mcp python3 ...), not an off-the-shelf package. Exposes exactly one tool,proxmox_qm(command), which SSHes to the Proxmox host as the existing scopedclaudeaccount using a dedicated keypair generated specifically for Hermes (not reusing the operator's desktop key — separate blast radius per principal, same pattern as every other dedicated credential in this project).- All three off-the-shelf SSH MCP packages checked (
mcp-server-ssh,ssh-mcp-server,ssh-mcp) were rejected before writing a custom one:mcp-server-sshexposes a sprawling toolkit (execute_command,file_operations,process_manager,sftp_upload/download,system_monitor) — far more surface than needed.ssh-mcp(themcpsshpackage) was the narrowest (2 tools) but depends on an external "Passman" credential-management service to resolve connection details — an unwanted new dependency just to satisfy one fixed host/user/key.ssh-mcp-serverdidn't expose a usable interface for this case either. Writing ~60 lines directly against themcpPython SDK (FastMCP) was less work than adapting any of them, and means the only security-relevant logic in the whole path is the server-side sshd restriction (see below) — the MCP server itself is a deliberately thin, unopinionated pipe. - The real enforcement is server-side, not in the MCP tool. An sshd
ForceCommanddrop-in on the Proxmox host (Match User claudein/etc/ssh/sshd_config.d/claude-restricted.conf) routes every SSH session for that account through a wrapper script (/usr/local/bin/claude-ssh-wrapper.sh) that only permitsqm snapshot/listsnapshot/statuspatterns — regardless of what the SSH client (the MCP tool, or anything else) requests. Verified directly: allowed commands work, arbitrary commands (whoami,cat /etc/passwd) are rejected, and a command-injection attempt via the snapshot-name argument (qm snapshot 141 test; whoami) is rejected too (the wrapper splits on whitespace and validates each token, not just regex-matching the whole string).AllowAgentForwarding/AllowTcpForwarding/PermitTTYare all disabled in the sameMatchblock. - Wrapper-script gotcha hit while building this: a long
elif [[ ... =~ ... ]]line got an actual newline inserted into it during copy/paste intonano, breaking the regex syntax (manifested as a confusingunexpected argument 'newline' to conditional binary operatorerror — not obviously a paste problem from the error text alone). Rewritten to avoid any long lines at all (read -ra parts <<< "$cmd"+ per-token validation instead of one big regex) specifically so there's nothing left for a wrap/paste to corrupt. - I don't have write access to
/etc/sshor reloadsshdmyself — only the scopedclaudeaccount, which can't escalate to root. The operator applied the sshd-side changes directly; I verified them afterward over SSH from the VM side.
- All three off-the-shelf SSH MCP packages checked (
What was deliberately NOT wired up
- pfSense/MikroTik MCP — no real device credentials exist in this environment to configure them safely. Needs the operator to provide scoped, read-only-by-default credentials first.
Useful commands
hermes mcp list # see all servers + tool-count summary
hermes mcp test <name> # verify a server connects and discover its tools
hermes mcp add has an interactive curses-based tool picker with no clean
non-interactive path — for a scripted/repeatable setup, hand-edit
mcp_servers: in config.yaml directly instead (schema:
{command, args, env, enabled, tools: {include: [...]}}).
If revisiting the Docker or Proxmox scope later
- To re-restrict docker-mcp: add back
tools: {include: ["get-logs", "list-containers"]}under its entry. - To extend proxmox-mcp's reach: the only place that matters is the sshd wrapper on the Proxmox host, not this MCP server or Hermes's config — the MCP tool will happily send any command, the wrapper decides what actually runs.