AI-Assisted Infrastructure Engineering›05 · MCP servers for infrastructure

Lesson 05 of 14 · Modules

MCP servers for infrastructure

Expose your platform to AI safely with the Model Context Protocol: what MCP servers are (tools, resources, prompts), building a small read-only Kubernetes diagnostics server in Python, connecting it to a client, and the permission design that keeps it safe.

Advanced
Key wordsModel Context ProtocolMCP servertoolsresourcespromptsstdioStreamable HTTPFastMCPread-onlyRBACview ClusterRoleclaude mcp add
AI client Claude Code, other MCP clients MCP server narrow tools: list_problem_pods… ServiceAccount bound to 'view' audit log every tool call Kubernetes API read-only MCP (stdio / HTTP) safety comes from RBAC and narrow tools, not from the prompt
An AI client calls narrow MCP tools; RBAC keeps them read-only.

What MCP is

The Model Context Protocol is an open protocol (introduced by Anthropic in 2024 and now supported by many clients and vendors) that lets AI applications connect to external systems in a standard way:

  • Tools: functions the model can call (e.g. list_pods(namespace)).
  • Resources: data the client can read (e.g. a runbook, a config file).
  • Prompts: reusable prompt templates.

Transports: stdio (the client starts the server as a local process) and Streamable HTTP (remote servers, with authentication).

MCP is like a standard plug socket. Before, every appliance needed its own special adaptor for every house. With a standard socket, any appliance (AI client) can plug into any house (your systems). But you still decide which sockets exist and how much power each one gives: a socket for the reading lamp, not one wired straight to the fuse box.

A read-only Kubernetes diagnostics server

# server.py: read-only Kubernetes diagnostics over MCP (Python SDK, FastMCP)
from kubernetes import client, config
from mcp.server.fastmcp import FastMCP

config.load_kube_config()          # uses a read-only context (see RBAC below)
core = client.CoreV1Api()
mcp = FastMCP("k8s-readonly")

MAX_ITEMS = 200

@mcp.tool()
def list_problem_pods(namespace: str = "") -> list[dict]:
    """List pods that are not Running/Succeeded or have restarts, optionally in one namespace."""
    pods = (core.list_namespaced_pod(namespace) if namespace
            else core.list_pod_for_all_namespaces()).items
    out = []
    for p in pods:
        restarts = sum(cs.restart_count for cs in (p.status.container_statuses or []))
        if p.status.phase not in ("Running", "Succeeded") or restarts > 0:
            out.append({"namespace": p.metadata.namespace, "pod": p.metadata.name,
                        "phase": p.status.phase, "restarts": restarts})
    return out[:MAX_ITEMS]

@mcp.tool()
def recent_events(namespace: str, limit: int = 50) -> list[dict]:
    """Return the most recent events in a namespace (warnings first)."""
    events = core.list_namespaced_event(namespace).items
    events.sort(key=lambda e: (e.type != "Warning",
                               -(e.last_timestamp or e.event_time or e.metadata.creation_timestamp).timestamp()))
    return [{"type": e.type, "reason": e.reason, "object": f"{e.involved_object.kind}/{e.involved_object.name}",
             "message": (e.message or "")[:300]} for e in events[:min(limit, MAX_ITEMS)]]

if __name__ == "__main__":
    mcp.run()                        # stdio transport by default
$ pip install "mcp[cli]" kubernetes
$ claude mcp add k8s-ro -- python server.py

(Check the MCP Python SDK docs for your version; the FastMCP API is the SDK's recommended high-level interface.)

RBAC: make it safe by construction

apiVersion: v1
kind: ServiceAccount
metadata:
  name: mcp-readonly
  namespace: ops-tools
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: mcp-readonly-view
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view                  # built-in: read most objects, no Secrets, no writes
subjects:
  - kind: ServiceAccount
    name: mcp-readonly
    namespace: ops-tools

Create a kubeconfig for this ServiceAccount (short-lived token) and point the server at it. Even if the model is confused or manipulated by text in logs (prompt injection), it cannot write or read Secrets.

Design rules for infra MCP servers

  • Narrow tools with typed, validated parameters; no generic shell or kubectl passthrough.
  • Cap output (items, message length): big dumps waste context and can hide injected instructions.
  • Redact sensitive fields (env vars, annotations with tokens) before returning data.
  • Log every call (tool, arguments, caller, time).
  • Add write tools only later, behind explicit approval flows (lesson 12), and with separate credentials.
  • Prefer existing, well-reviewed servers where they fit, but review their permissions like any third-party code.

Try it: your first MCP server

  1. Create the mcp-readonly ServiceAccount and binding in a kind cluster, and a kubeconfig using a token from kubectl create token mcp-readonly -n ops-tools.
  2. Run the server above with that kubeconfig and register it with your MCP client.
  3. Break a deployment (bad image) and ask the assistant "what's wrong in namespace X?"; watch it call your tools.
  4. Try to make it delete something: confirm there's no tool for it, and that the kubeconfig would be denied anyway (kubectl auth can-i delete pods --as=system:serviceaccount:ops-tools:mcp-readonly).
  5. Add a third tool (e.g. deployment_rollout_status) with input validation and output caps.

Going deeper: MCP in production

  • Remote servers over Streamable HTTP need proper authentication/authorization (per-user identity where possible) and TLS.
  • Consider per-environment servers (dev writeable, prod read-only) with clearly different names.
  • Treat tool outputs as untrusted input to the model: data from logs, tickets and web pages may contain instructions.

Recap

  • MCP standardises tools, resources and prompts for AI clients; transports stdio and Streamable HTTP.
  • Build narrow, validated, capped tools; no generic command execution.
  • Enforce safety at the platform layer: a ServiceAccount bound to view (read-only, no Secrets).
  • Log calls, redact data, and add write capabilities only with approvals and separate credentials.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.