Install
$ agentstack add mcp-radumalica-infrastructure-mcp ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README — it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Infrastructure MCP Server
A self-hosted Model Context Protocol server that lets AI agents (Claude, Cursor, Hermes, OpenAI Agents, ...) safely operate real infrastructure — Linux servers, Docker, Kubernetes, Proxmox, Grafana, network gear, and more — through high-level, auditable tools instead of raw shell access.
> Status: early, actively developed. See [Roadmap](#roadmap) for what's implemented and what's coming.
Table of Contents
- [Why](#why)
- [Goals](#goals)
- [Non-Goals](#non-goals)
- [Architecture](#architecture)
- [Current Features](#current-features)
- [Installation](#installation)
- [Configuration](#configuration)
- [Connecting an AI Agent](#connecting-an-ai-agent)
- [Tool Design Philosophy](#tool-design-philosophy)
- [Security](#security)
- [Development](#development)
- [CI/CD](#cicd)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [A Note on How This Project Is Built](#a-note-on-how-this-project-is-built)
Why
AI agents are increasingly asked to help operate infrastructure — check disk space, restart a container, look at recent errors. The naive way to enable this is to hand the agent raw SSH/shell access. That's fast to build and dangerous to run: no audit trail, no input validation, no blast-radius control, and every agent has to independently learn how to safely operate every kind of system.
Infrastructure MCP Server is the alternative: a single, self-hosted service that exposes meaningful, structured, inventory-driven tools (disk_usage(), docker_restart(), grafana_alerts()) instead of execute(command). Agents reason about infrastructure; this server is the only thing that actually touches it.
Goals
- Self-hosted
- Production-ready
- Secure by default
- Extensible
- Vendor agnostic
- Infrastructure as Code friendly
- Stateless where possible
- Structured outputs only
- Human-auditable
- Open Source
Non-Goals
The project is not intended to become:
- another SSH wrapper
- another Ansible replacement
- another monitoring system
- another Kubernetes operator
Instead, it provides AI-friendly infrastructure primitives built on top of those things.
Architecture
AI Agent
(Hermes / Claude / Cursor)
│
│ MCP
▼
Infrastructure MCP Server
│
┌───────────┴────────────┐
│ │
Infrastructure APIs SSH / Telnet Layer
│ │
▼ ▼
Proxmox Linux Servers
Grafana Cisco / MikroTik
Prometheus UniFi
Docker Home Assistant
Kubernetes Storage / Bare Metal
Three layers, wired top-down:
- MCP layer (
mcp/tools/,mcp/prompts/,mcp/resources/) — registers tools with the MCP SDK. Tools call adapters; they never talk to infrastructure directly. - Adapter layer (
internal/linux/,internal/docker/,internal/proxmox/,internal/grafana/, ...) — one isolated package per integration, all implementing a common interface. Authentication (SSH keys, API tokens, kubeconfig) lives inside adapters and is never exposed to AI agents. - Foundation —
internal/inventory/(YAML config, validated, env-var expansion for secrets) andinternal/ssh/internal/telnet(pooled connections to targets, including legacy devices with obsolete crypto or no SSH at all).
Everything is inventory-driven. No IP addresses, hostnames, or credentials appear in tool implementations — targets are resolved by name/tag from the inventory YAML.
Technology Stack
| Concern | Choice | |---|---| | Language | Go 1.26+ | | MCP SDK | Official Model Context Protocol Go SDK | | Configuration | YAML (go-playground/validator) | | Logging | log/slog (structured, JSON) | | SSH | golang.org/x/crypto/ssh | | HTTP | net/http | | Serialization | encoding/json | | Testing | Go testing, Testcontainers, Docker Compose | | CI | GitHub Actions |
Project Structure
infrastructure-mcp/
├── cmd/
│ └── server/ # entry point (stdio MCP transport)
├── internal/
│ ├── inventory/ # YAML config, validation, secret expansion
│ ├── ssh/ # pooled SSH connectivity
│ ├── telnet/ # legacy device transport
│ ├── remote/ # SSH/Telnet dispatch by target protocol
│ ├── linux/ # Linux diagnostics adapter
│ ├── docker/ # Docker CLI-over-SSH adapter
│ ├── toolerr/ # structured error contract
│ ├── kubernetes/
│ ├── grafana/
│ ├── proxmox/ # planned
│ ├── prometheus/, loki/ # planned
│ ├── cisco/, mikrotik/, unifi/ # planned
│ └── homeassistant/ # planned
├── mcp/
│ ├── tools/ # one file per MCP tool
│ ├── prompts/
│ └── resources/
├── configs/ # example inventory
├── docs/
├── examples/
├── scripts/
└── tests/
Current Features
Tools implemented and tested against the real MCP protocol so far:
Core (v0.1)
list_servers— list inventory targets, optionally filtered by tagrun_command— run an arbitrary command against an inventory targetuptime— uptime and load averagesdisk_usage— per-filesystem usage with warning/critical severitymemory_usage— memory utilization with warning/critical severity
Linux (v0.2)
failed_services— systemd units in the failed statecpu_usage— aggregate CPU utilization (Proxmox/KVM guest-time aware)reboot_required— pending-reboot detection (marker file + kernel comparison)running_processes— top processes by CPU usagejournal_errors— recent systemd journal entries at error priority+kernel_version— running kernel release
Docker (v0.3)
docker_ps— list containers (running or all)docker_images— list local imagesdocker_stats— point-in-time resource usage snapshotdocker_logs— recent combined stdout/stderr log linesdocker_restart— restart a container (destructive; requiresconfirm: true)
Kubernetes (v0.4)
kubectl_get_pods— list pods, optionally scoped to a namespacekubectl_logs— fetch a pod/container's log lineskubectl_events— recent cluster events, most recent firstkubectl_describe— structured pod summary (spec/status highlights + recent events)kubectl_nodes— node list with readiness, roles, and capacity
Grafana (v0.5)
grafana_alerts— currently firing/resolved alert instances from the alerting APIgrafana_dashboards— search dashboards by title text and/or taggrafana_annotations— annotations, optionally scoped to a time range and/or tagsgrafana_query— raw query against one datasource (PromQL/LogQL/SQL/...); response is a datasource-specific passthrough, not normalized
Cross-cutting, since v0.1
- Legacy network device support: Telnet transport, SSH legacy-crypto negotiation, transparent per-target protocol dispatch
- Structured error contract on every tool (
message,recommendation,retryable,category) - Structured per-execution logging (
tool,user,target,duration,result,error) - Host-key verification fail-closed by default, with explicit opt-outs for lab use
- Dual MCP transport: stdio (local subprocess clients) and Streamable HTTP (remote clients), from the same binary — HTTP is fail-closed by default, requiring a bearer token (
MCP_HTTP_TOKEN) unless explicitly opted out for lab use - Docker image (multi-arch, GHCR) + Docker Compose, with a self-check
-healthcheckmode for shell-less containers - CI/CD: PR-gated build/vet/lint/race-tested-CI, automatic semantic versioning + changelog + image publish + Trivy scan on merge to
main— see [CI/CD](#cicd)
See [Roadmap](#roadmap) for what's next.
Installation
Prerequisites
- Go 1.26 or newer (bumped from 1.24 in v0.4 — required by
k8s.io/client-go's current release train) - SSH access (key- or password-based) to the servers/devices you want to manage
- An MCP-capable client (Claude Desktop, Claude Code, Cursor, or any other MCP client)
Build from source
git clone https://github.com//infrastructure-mcp.git
cd infrastructure-mcp
go build -o bin/infrastructure-mcp ./cmd/server
Or run via Docker
Pre-built multi-arch images (linux/amd64, linux/arm64) are published to GHCR on every merge to main (see [CI/CD](#cicd)):
docker pull ghcr.io//infrastructure-mcp:latest
Or with Docker Compose, which wires up the inventory and SSH volume mounts for you — see [docker-compose.yaml](docker-compose.yaml):
cp configs/inventory.example.yaml inventory.yaml # fill in your real targets
cp .env.example .env # fill in real secrets, never commit
docker compose up -d
Run tests
go build ./... # build
go vet ./... # static checks
go test ./... -race -cover # all tests, race detector, coverage
go test ./internal/linux -run TestName # a single test
Configuration
The server is entirely inventory-driven — copy the example inventory and fill in your own targets:
cp configs/inventory.example.yaml configs/inventory.yaml
servers:
archive:
hostname: 10.0.0.5
user: hermes
key: ~/.ssh/archive
tags:
- linux
- ethereum
pve01:
hostname: 10.0.0.2
user: hermes
proxyjump: jumpbox # reached only via another inventory target
tags:
- proxmox
routers:
core:
hostname: 10.0.0.10
vendor: cisco
user: admin
password: ${CORE_ROUTER_PASSWORD} # resolved from the environment at load time
legacy-edge: # no SSH server at all — Telnet only
hostname: 10.0.0.12
vendor: cisco
user: admin
password: ${LEGACY_EDGE_PASSWORD}
protocol: telnet
switches:
ancient-sw: # obsolete SSH key exchange/cipher/MAC only
hostname: 10.0.0.21
vendor: cisco
user: admin
password: ${ANCIENT_SW_PASSWORD}
legacy_crypto: true
# Every category, including single-instance-feeling ones like Grafana and
# Proxmox, is a map keyed by name — so a second Grafana or a second Proxmox
# cluster is just another key, not a schema change.
grafana:
main:
url: https://grafana.lab.local
token: ${GRAFANA_TOKEN}
proxmox:
lab:
url: https://pve.lab.local:8006
token: ${PROXMOX_TOKEN}
# All auth (client cert, bearer token, exec plugin, ...) lives inside the
# kubeconfig file itself — nothing is inlined into the inventory.
kubernetes:
home:
kubeconfig: /etc/infrastructure-mcp/kubeconfig-home
context: home-admin
Secrets are never written in plaintext. Any ${VAR} in the YAML is resolved from the process environment at load time, and load fails closed if the variable is unset — real credentials are never committed to the repo.
Server flags
./bin/infrastructure-mcp \
-inventory configs/inventory.yaml \
-known-hosts ~/.ssh/known_hosts \
-insecure-ignore-host-key=false \
-transport stdio
| Flag | Default | Description | |---|---|---| | -inventory | configs/inventory.yaml | Path to the inventory YAML file | | -known-hosts | $HOME/.ssh/known_hosts | Path used to verify target SSH host keys | | -insecure-ignore-host-key | false | Skip host key verification entirely — lab/dev only, never production | | -transport | stdio | stdio (local subprocess clients) or http (remote Streamable HTTP clients) | | -http-addr | :8080 | Address to listen on when -transport=http | | -healthcheck | false | Instead of starting the server, GET -healthcheck-url and exit 0/1 — used as the container HEALTHCHECK | | -allow-anonymous-http | false | Allow -transport=http to serve without a bearer token — lab/dev only, every tool becomes reachable to anyone who can reach the port |
Host key verification is fail-closed by default: an unrecognized target's connection is refused unless it's in known_hosts or you explicitly opt into insecure mode. -transport=http is fail-closed the same way: it refuses to start unless the MCP_HTTP_TOKEN environment variable is set (see below) or -allow-anonymous-http is passed explicitly.
Connecting an AI Agent
Infrastructure MCP Server supports two transports from the same binary, so it works with both local-subprocess and remote-network MCP clients:
- stdio (default) — the client spawns the binary itself and talks over its stdin/stdout. This is what most desktop IDEs and agent CLIs (Claude Desktop, Claude Code, Cursor) expect.
- Streamable HTTP (
-transport http) — the server listens on the network (/mcpendpoint, plus/healthz) and any remote MCP client connects over HTTP. Use this when the server runs elsewhere (a container, a VM, behind a reverse proxy) rather than on the same machine as the client.
stdio (local)
{
"mcpServers": {
"infrastructure": {
"command": "/path/to/bin/infrastructure-mcp",
"args": ["-inventory", "/path/to/configs/inventory.yaml"]
}
}
}
Remote HTTP
-transport=http requires a bearer token — /mcp exposes every registered tool, including run_command, so the server refuses to start without one (fail-closed, like host key verification above). Generate one and set it as MCP_HTTP_TOKEN:
export MCP_HTTP_TOKEN=$(openssl rand -hex 32)
./bin/infrastructure-mcp -inventory configs/inventory.yaml -transport http -http-addr :8080
(or docker compose up -d, which does this by default once MCP_HTTP_TOKEN is set in .env — see [Installation](#installation)). Every request to /mcp must then send Authorization: Bearer $MCP_HTTP_TOKEN; /healthz stays open for container/load-balancer health checks. -allow-anonymous-http disables this requirement — lab/dev only, never expose an anonymous instance to anything but localhost.
Point any remote-MCP-capable client at http://:8080/mcp with that header set. For clients that only support stdio, bridge with mcp-remote or an equivalent local proxy:
{
"mcpServers": {
"infrastructure": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://:8080/mcp", "--header", "Authorization: Bearer ${MCP_HTTP_TOKEN}"]
}
}
}
Put the HTTP endpoint behind TLS and network-level access control (a reverse proxy, VPN, or private network) before exposing it beyond localhost — the server itself does not add its own transport-layer authentication.
Once connected, over either transport, the agent sees only the tools listed under [Current Features](#current-features) — never raw shell access, never credentials.
Tool Design Philosophy
Never expose raw infrastructure:
execute(command) # avoid
Prefer meaningful, structured actions:
check_disk()
check_memory()
docker_list()
grafana_alerts()
backup_switch()
Every tool:
- validates its inputs
- never panics
- returns structured JSON
- includes severity where relevant
- includes a recommendation on warning/critical states
- includes a timestamp
{
"status": "warning",
"server": "archive",
"disk_usage": 91,
"recommendation": "Free disk space or expand storage.",
"timestamp": "2026-07-23T19:10:00Z"
}
Logging
Every tool execution produces a structured log line with:
tool, user, duration, target, result, error
Error Handling
Tools never return a raw Go error. Every failure is shaped as:
message, recommendation, retryable, category
(category is one of not_found, auth, network, timeout, invalid_input, internal.)
Security
- No secrets
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: radumalica
- Source: radumalica/infrastructure-mcp
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.