New
Apps 821Makers 820This week 821
Promote your app−30%
Infrastructure MCP Server screenshot 1
3views in
the last 30 days

Infrastructure MCP Server

Give Claude Code, Cursor, and other AI agents safe access to your real infrastructure — without...

Built with Claude Code

by radumalica · from RORomania · added Oct 29, 2026
no ratings0 comments

Infrastructure MCP

Infrastructure MCP Server

Give Claude Code, Cursor, and other AI agents safe access to your real infrastructure — without giving them raw SSH access.

A self-hosted Model Context Protocol server that exposes high-level, auditable tools (disk_usage(), docker_restart(), grafana_alerts(), ...) instead of execute(command). Every call is inventory-driven, structured, and logged — the agent reasons about infrastructure; this server is the only thing that actually touches it.

Terminal recording: an agent lists the server's tools, calls uptime and memory_usage on demo targets, and is refused an unknown target

Try it in two minutes, with no real infrastructure: cd examples/demo && ./run-demo.sh — see the demo guide.

Status: early, actively developed. See Roadmap for what's implemented and what's coming.

Manage: ✓ Linux   ✓ Docker   ✓ Kubernetes   ✓ Proxmox   ✓ Grafana   ✓ Cisco (MikroTik, UniFi, Prometheus/Loki, Home Assistant, and more are on the Roadmap — not shipped yet.)

Works with: ✓ Claude Code   ✓ Cursor   ✓ OpenAI Codex   ✓ Gemini CLI   ✓ Windsurf   ✓ Hermes (MCP is an open, client-agnostic protocol — any spec-compliant client works; these are just the ones people actually use it with.)

Raw SSH access Infrastructure MCP
Audit trail ❌ ✅ structured log per call (tool, user, target, duration, result)
Input validation ❌ ✅ named tools validate every parameter; no shell interpolation
Structured output ❌ free text ✅ JSON, every response
Inventory-aware ❌ agent picks the host ✅ targets resolved by name/tag only
Blast-radius control ❌ ✅ read-only by default, mutations require confirm: true
Credential exposure ❌ agent sees them ✅ credentials never leave the adapter layer

Quick Start

Prebuilt binary (Linux and macOS, amd64 and arm64): download the archive for your platform from the latest release, verify it against SHA256SUMS, and unpack it. Or use Docker: docker pull ghcr.io/radumalica/infrastructure-mcp:latest.

From source:

git clone https://github.com/radumalica/infrastructure-mcp.git
cd infrastructure-mcp
go build -o bin/infrastructure-mcp ./cmd/server

cp configs/inventory.example.yaml configs/inventory.yaml   # fill in your own targets

Point your MCP client at the binary — see Connecting an AI Agent for Docker and remote-HTTP setups too:

{
  "mcpServers": {
    "infrastructure": {
      "command": "/path/to/bin/infrastructure-mcp",
      "args": ["-inventory", "/path/to/configs/inventory.yaml"]
    }
  }
}

Example Conversation

You: My archive server feels slow, and I'm seeing errors in Grafana. Can you find out why?

Claude, using Infrastructure MCP:

  1. cpu_usage(server: "archive")                          → 92% used, status: "warning"
  2. docker_ps(server: "archive")                           → prometheus: "Restarting (1) 4 seconds ago"
  3. docker_logs(server: "archive", container: "prometheus") → "panic: too many open files"
  4. grafana_alerts(instance: "main")                        → firing: HighDiskUsage on archive:/

Claude: Root cause found — archive is at 91% disk usage, which is causing Prometheus
to crash-loop on startup ("too many open files" from a corrupted WAL it can't
compact into the remaining space). Free up space on `/` (or expand it), then run:

  docker_restart(server: "archive", container: "prometheus", confirm: true)

Four tool calls, one root cause, zero raw shell access. Every call above is inventory-driven, logged, and returns structured JSON — see Examples for the actual response shapes.


Current Features

Tools implemented and tested against the real MCP protocol so far:

Core (v0.1)

  • list_servers — list inventory targets, optionally filtered by tag
  • check_inventory_health — validate the inventory file: parses, every ${VAR} is set, every SSH key/kubeconfig file it references is readable
  • uptime — uptime and load averages
  • disk_usage — per-filesystem usage with warning/critical severity
  • memory_usage — memory utilization with warning/critical severity

Optional compatibility escape hatches: run_command, docker_exec and kubectl_exec execute arbitrary commands on an inventory target, in a container, or in a pod. They are disabled by default and registered only when the server starts with -enable-run-command. Prefer the named tools; enabling them grants connected agents unrestricted command execution.

Linux (v0.2)

  • failed_services — systemd units in the failed state
  • cpu_usage — aggregate CPU utilization (Proxmox/KVM guest-time aware)
  • reboot_required — pending-reboot detection (marker file + kernel comparison)
  • running_processes — top processes by CPU usage
  • journal_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 images
  • docker_stats — point-in-time resource usage snapshot
  • docker_logs — recent combined stdout/stderr log lines
  • docker_restart — restart a container (destructive; requires confirm: true)
  • docker_exec (opt-in, -enable-run-command) — run a command inside a running container, the container-scoped equivalent of run_command

Kubernetes (v0.4)

  • kubectl_get_pods — list pods, optionally scoped to a namespace
  • kubectl_logs — fetch a pod/container's log lines
  • kubectl_events — recent cluster events, most recent first
  • kubectl_describe — structured pod summary (spec/status highlights + recent events)
  • kubectl_nodes — node list with readiness, roles, and capacity
  • kubectl_exec (opt-in, -enable-run-command) — run a command inside a pod's container, the pod-scoped equivalent of run_command/docker_exec

Grafana (v0.5)

  • grafana_alerts — currently firing/resolved alert instances from the alerting API
  • grafana_dashboards — search dashboards by title text and/or tag
  • grafana_annotations — annotations, optionally scoped to a time range and/or tags
  • grafana_query — raw query against one datasource (PromQL/LogQL/SQL/...); response is a datasource-specific passthrough, not normalized

Proxmox (v0.6)

  • proxmox_nodes — list cluster nodes with status and resource usage
  • proxmox_vms — list QEMU VMs and LXC containers on a node
  • proxmox_tasks — recent tasks on a node, most recent first
  • proxmox_start_vm — start a VM/container (state-changing; requires confirm: true)
  • proxmox_stop_vm — force-stop a VM/container (destructive; requires confirm: true)
  • proxmox_snapshot — take a VM/container snapshot (state-changing; requires confirm: true)

Networking — Cisco (v0.7, partial: MikroTik/UniFi remain on the Roadmap)

  • cisco_backup — fetch the running configuration, as-is
  • cisco_version — software version, hostname, and uptime
  • cisco_interfaces — interface list with IP address, admin/link status, and line protocol
  • cisco_inventory — hardware components (chassis, modules, PSUs) with PID/VID/serial number
  • cisco_logs — buffered syslog messages, optionally capped to the most recent N lines
  • cisco_backup_diff — fetch the running config and diff it against the last snapshot taken by this tool, flagging config drift

Cross-cutting, since v0.1

  • audit://recent MCP resource — the most recent mutating tool invocations (docker_restart, proxmox_start_vm/proxmox_stop_vm/proxmox_snapshot), newest first, in-memory only (-audit-history-size, default 200)
  • dry_run: true on every confirm-gated mutating tool — reports the exact command/API call that would run without acting, taking priority over confirm even if both are set
  • Legacy network device support: Telnet transport, SSH legacy-crypto negotiation, transparent per-target protocol dispatch, SSH public-key auth for routers/switches
  • 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 -healthcheck mode 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

See Roadmap for what's next.


Table of Contents


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:

  1. MCP layer (mcp/tools/, mcp/prompts/, mcp/resources/) — registers tools with the MCP SDK. Tools call adapters; they never talk to infrastructure directly.
  2. 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.
  3. Foundation — internal/inventory/ (YAML config, validated, env-var expansion for secrets) and internal/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.

Architectural decisions and the reasoning behind them are recorded as ADRs — see docs/adr/.

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/
│   ├── prometheus/, loki/            # planned
│   ├── cisco/
│   ├── mikrotik/, unifi/              # planned
│   └── homeassistant/                  # planned
├── mcp/
│   ├── tools/               # one file per MCP tool
│   ├── prompts/
│   └── resources/
├── configs/                # example inventory
├── docs/
│   ├── adr/                 # architecture decision records
│   ├── TOOL_REFERENCE.md      # every tool's params + example output
│   └── ADDING_AN_ADAPTER.md    # how to add a new integration
├── examples/
│   ├── mcp-client-config/       # ready-to-adapt client JSON snippets
│   ├── inventory-snippets/       # focused single-concept YAML fragments
│   └── sample-tool-outputs/       # example JSON responses
├── scripts/
└── tests/

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/radumalica/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):

docker pull ghcr.io/radumalica/infrastructure-mcp:latest

Or with Docker Compose, which wires up the inventory and SSH volume mounts for you — see 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:
  # token is a Proxmox API token in "user@realm!tokenid=uuid" form
  # (Datacenter > Permissions > API Tokens in the PVE UI). Ticket/cookie
  # login is not supported.
  lab:
    url: https://pve.lab.local:8006
    token: ${PROXMOX_TOKEN}
    # insecure_skip_verify: true   # only for a stock Proxmox's self-signed
                                    # cert — lab/dev only, never production

# 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.

More focused, single-concept inventory fragments (key-authenticated network devices, multi-instance services, ...) live under examples/inventory-snippets/.

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
-enable-run-command false Register the unrestricted run_command, docker_exec and kubectl_exec escape hatches — high risk, prefer named tools
-backup-dir configs/backups Directory cisco_backup_diff persists its per-device config snapshots in (created on first use)
-audit-history-size 200 Number of recent mutating tool invocations retained in-memory and exposed via the audit://recent resource

Host key verification is fail-closed by default: an unrecognized target's c

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#infrastructure · #mcp · #security