# Infrastructure Mcp

> A self-hosted MCP 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.

- **Type:** MCP server
- **Install:** `agentstack add mcp-radumalica-infrastructure-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [radumalica](https://agentstack.voostack.com/s/radumalica)
- **Installs:** 0
- **Category:** [Cloud & Infrastructure](https://agentstack.voostack.com/c/cloud-infrastructure)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [radumalica](https://github.com/radumalica)
- **Source:** https://github.com/radumalica/infrastructure-mcp

## Install

```sh
agentstack add mcp-radumalica-infrastructure-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Infrastructure MCP Server

A self-hosted [Model Context Protocol](https://modelcontextprotocol.io) 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:

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.

### Technology Stack

| Concern | Choice |
|---|---|
| Language | Go 1.26+ |
| MCP SDK | Official [Model Context Protocol Go SDK](https://github.com/modelcontextprotocol/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 tag
- `run_command` — run an arbitrary command against an inventory target
- `uptime` — uptime and load averages
- `disk_usage` — per-filesystem usage with warning/critical severity
- `memory_usage` — memory utilization with warning/critical severity

**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`)

**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

**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

**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 `-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](#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

```bash
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)):

```bash
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):

```bash
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

```bash
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:

```bash
cp configs/inventory.example.yaml configs/inventory.yaml
```

```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

```bash
./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 (`/mcp` endpoint, 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)

```json
{
  "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`:

```bash
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`](https://www.npmjs.com/package/mcp-remote) or an equivalent local proxy:

```json
{
  "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

```json
{
  "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](https://github.com/radumalica)
- **Source:** [radumalica/infrastructure-mcp](https://github.com/radumalica/infrastructure-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-radumalica-infrastructure-mcp
- Seller: https://agentstack.voostack.com/s/radumalica
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
