Skip to main content
Glama

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.

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

✅ every parameter validated, 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

git clone https://github.com/<your-org>/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"]
    }
  }
}

Related MCP server: Shuttle

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

  • 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

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

Cross-cutting, since v0.1

  • 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. Foundationinternal/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/<your-org>/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/<your-org>/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}

# 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

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)

{
  "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). 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://<host>: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://<host>: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 — never raw shell access, never credentials.

Ready-to-adapt client config files (stdio and remote-HTTP-via-mcp-remote) live under examples/mcp-client-config/.


Examples

Full request/response walkthroughs for a few representative tools, run against the inventory shown in Configuration. Values are synthetic — copy the shape, not the data. Every tool's shape is documented in docs/TOOL_REFERENCE.md; runnable inventory fragments and MCP client configs live under examples/.

Check disk space on a server:

disk_usage(server: "archive")
{
  "server": "archive",
  "filesystems": [
    {
      "filesystem": "/dev/sda1",
      "mount_point": "/",
      "total_kb": 51475068,
      "used_kb": 46853200,
      "available_kb": 2003212,
      "used_percent": 91,
      "status": "critical",
      "recommendation": "Free disk space immediately or expand capacity."
    }
  ],
  "timestamp": "2026-07-30T09:00:00Z"
}

List containers on a Docker host:

docker_ps(server: "archive")
{
  "server": "archive",
  "containers": [
    {
      "id": "a1b2c3d4e5f6",
      "image": "grafana/grafana:11.2.0",
      "status": "Up 3 weeks",
      "state": "running",
      "ports": "0.0.0.0:3000->3000/tcp",
      "names": "grafana"
    }
  ],
  "timestamp": "2026-07-30T09:00:00Z"
}

Restart a container — the confirm-gate pattern every mutating tool uses. The first call, without confirm, is a no-op that reports what would happen:

docker_restart(server: "archive", container: "grafana")
{
  "server": "archive",
  "container": "grafana",
  "status": "confirmation_required",
  "message": "Set confirm: true to restart this container. No action was taken.",
  "timestamp": "2026-07-30T09:00:00Z"
}

Only the confirmed call actually restarts it:

docker_restart(server: "archive", container: "grafana", confirm: true)
{
  "server": "archive",
  "container": "grafana",
  "status": "restarted",
  "message": "Container restarted.",
  "timestamp": "2026-07-30T09:00:05Z"
}

Interfaces on a Cisco switch reached over legacy-crypto SSH (legacy_crypto: true in inventory — see ADR 0005):

cisco_interfaces(device: "old-core-sw")
{
  "device": "old-core-sw",
  "interfaces": [
    { "interface": "Vlan100", "ip_address": "10.0.0.23", "ok": "YES", "method": "NVRAM", "status": "up", "protocol": "up" },
    { "interface": "GigabitEthernet1/0/1", "ip_address": "unassigned", "ok": "YES", "method": "unset", "status": "up", "protocol": "up" }
  ],
  "timestamp": "2026-07-30T09:00:00Z"
}

A firing alert from Grafana:

grafana_alerts(instance: "main")
{
  "instance": "main",
  "alerts": [
    {
      "status": "firing",
      "labels": { "alertname": "HighDiskUsage", "instance": "archive", "severity": "critical" },
      "starts_at": "2026-07-30T08:00:00Z",
      "fingerprint": "9f2a1c7e0b3d4e5f"
    }
  ],
  "timestamp": "2026-07-30T09:00:00Z"
}

A tool call against an unreachable target — the structured error envelope, never a raw Go error:

uptime(server: "archive")
{
  "message": "dial tcp 10.0.0.5:22: connect: connection refused",
  "recommendation": "Retry the request; if it persists, check network connectivity to the target.",
  "retryable": true,
  "category": "network"
}

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 in logs

  • No secrets in prompts

  • No shell interpolation — any user-supplied value that reaches a remote command is validated against a strict whitelist pattern first

  • Validate all inputs

  • Whitelist inventory — no target is reachable unless it's named in the inventory

  • Least privilege

  • Read-only by default

  • Dangerous actions require explicit confirmation (e.g. docker_restart requires confirm: true and is a no-op otherwise)

  • -transport=http is fail-closed: it refuses to start without a bearer token (MCP_HTTP_TOKEN) unless -allow-anonymous-http is explicitly passed

Found a security issue? Please report it privately rather than opening a public issue — see Contributing.


Development

Testing standards

  • Unit tests target ~100% coverage for inventory, adapters, parsing, and validation

  • Integration tests use Testcontainers / Docker Compose (Ubuntu, Alpine, Grafana, Prometheus, Loki)

  • Every tool is exercised through a real MCP protocol round-trip (mcp.NewInMemoryTransports()), not just unit-tested in isolation

Performance goals

  • SSH/Telnet connection pooling

  • Parallel execution

  • Context cancellation everywhere

  • Streaming responses

  • Timeout and retry support

Code conventions

  • Small packages, dependency injection, no globals, prefer interfaces

  • context.Context everywhere

  • log/slog for logging

  • Avoid reflection when possible

  • Functions under ~50 lines when practical

  • Structured errors, never raw ones

  • Document exported functions

  • Never expose credentials to the MCP layer

  • Never hardcode infrastructure — always resolve through inventory

Adding a new integration? See docs/ADDING_AN_ADAPTER.md for the full walkthrough.


CI/CD

Two GitHub Actions workflows, both required for anyone contributing:

ci.yml — required for every PR into main

Runs on every pull request and on push to main:

  • testgo build ./..., go vet ./..., a gofmt -l formatting check, and go test ./... -race -cover

  • lintgolangci-lint against .golangci.yml (errcheck, staticcheck, govet, gosec, and more)

Branch protection on main must be configured by a repo admin (this is a repository setting, not something a workflow file can enforce) to require both the test and lint jobs to pass, and to require a pull request before merging — that gate only takes effect once the setting is turned on.

The release pipeline is designed to work with protected main: it creates and pushes only the release tag, creates the GitHub release from that tag, and opens a separate PR for CHANGELOG.md updates.

release.yml — runs on every merge to main

  1. Version + tagCocogitto reads Conventional Commits since the last tag (config: cog.toml) and computes the next semver. The workflow then creates and pushes the vX.Y.Z tag (no direct commit push to main). A merge with no releasable commits (e.g. docs/chore-only) skips the rest of the pipeline.

  2. Build & scan — builds the Docker image and scans it with Trivy before publishing; a CRITICAL/HIGH finding blocks the release. Results are uploaded to the repo's Security tab either way.

  3. Publish — once the scan is clean, builds and pushes the multi-arch (linux/amd64, linux/arm64) image to ghcr.io/<org>/infrastructure-mcp, tagged latest and vX.Y.Z.

  4. Changelog PR — the workflow regenerates CHANGELOG.md and opens an automated PR so changelog updates still go through normal branch protection and review.

Commit messages therefore need to follow Conventional Commits (feat:, fix:, docs:, chore:, ...) — see Contributing.


Roadmap

Shipped: Linux · Docker · Kubernetes · Grafana · Proxmox · Cisco Not started: MikroTik · UniFi · Prometheus/Loki/Tempo/Alertmanager · Home Assistant · AI-composite tools · AWS/Azure/GCP/VMware/...

Implemented versions are listed under Current Features and tracked feature-by-feature in PROGRESS.md. Everything below is not started.

v0.7 — Networking (in progress: Cisco shipped, MikroTik/UniFi remain)

MikroTik: backup, interfaces, routes, firewall, system resources UniFi: devices, clients, AP status, firmware, statistics

Note: Telnet transport and SSH legacy-crypto support for these vendors already shipped ahead of schedule in v0.1 — only the vendor-specific tools above remain.

v0.8 — Monitoring

Prometheus, Loki, Tempo, Alertmanager

v0.9 — Home Assistant

  • entities

  • devices

  • automations

  • sensors

  • cameras

v1.0 — AI-oriented composite tools

Instead of calling 15 low-level tools, the agent calls one expert tool that composes them:

  • diagnose_linux()

  • diagnose_docker()

  • diagnose_kubernetes()

  • diagnose_network()

  • diagnose_storage()

  • morning_brief()

  • incident_summary()

  • root_cause_analysis()

  • backup_everything()

  • check_infrastructure()

Further out

AWS, Azure, GCP, VMware, TrueNAS, Ceph, Synology, IPMI, Redfish, OpenStack, Nomad, Consul.


Contributing

Contributions are welcome — this is an early-stage project and there's a lot of surface area left on the roadmap above.

  1. Fork the repo and create a feature branch.

  2. Follow the existing package/adapter pattern: one isolated package per integration under internal/, a matching set of tools under mcp/tools/, wired into cmd/server/main.go — see docs/ADDING_AN_ADAPTER.md for the full walkthrough.

  3. Match the existing conventions: structured errors via internal/toolerr, structured logging via the withLogging wrapper, inventory-driven targets, no shell interpolation of user input.

  4. Add tests — unit tests for adapters/parsing, and at least one test that exercises new tools through the real MCP protocol.

  5. Run go build ./... && go vet ./... && gofmt -l . && go test ./... -race -cover and, if you have it installed, golangci-lint run ./... before opening a PR — this is exactly what ci.yml runs, and it's a required, non-optional check on every PR into main (see CI/CD).

  6. Use Conventional Commits for your commit messages (feat:, fix:, docs:, chore:, refactor:, test:, ci:, ...) — the version bump, CHANGELOG.md, and release on merge are generated automatically from them.

  7. Open a pull request describing what changed and why. For an architectural decision (not just "add tool #N following the existing pattern"), add an ADR under docs/adr/.

For larger changes (a new adapter, a new version's worth of tools), consider opening an issue first to discuss the approach.


License

Licensed under the MIT License — free to use, modify, and distribute, including commercially.


A Note on How This Project Is Built

This project is vibe-coded: the vast majority of the implementation — code, tests, commit-by-commit progress — is written by AI (Claude Code), iterating directly against this README as the specification.

The design and architecture are human-authored by the repository owner: the layering (MCP → adapters → foundation), the inventory-driven philosophy, the tool-design rules, the security posture, and the version roadmap were all decided by a person before any code was written. AI implements against that spec; it doesn't set the direction.

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

Maintenance

Maintainers
Response time
1dRelease cycle
5Releases (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

View all related MCP servers

Related MCP Connectors

  • Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.

  • Issue, rotate and revoke scoped API-key passes for 25+ providers — the agent never sees a real key

  • Pay-per-call cybersecurity for AI agents: vuln scans, threat intel, compliance, code security.

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/radumalica/infrastructure-mcp'

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