Servidores MCP para DevOps: qué son y dos que puedes crear esta semana
Servidores MCP para DevOps: brindan a Claude acceso en tiempo real a Kubernetes, AWS y libros de procedimientos. Los tres tipos de servidores, un servidor MCP de Kubernetes de solo lectura y errores comunes.
Fuentes: What Is an MCP Server and Why Every DevOps Team Should Have One in 2026
MCP, el Protocolo de Contexto de Modelo, es un estándar abierto de Anthropic que permite a los modelos de IA conectarse a sistemas externos de manera estructurada: herramientas, APIs, bases de datos y servicios. Sin él, Claude funciona de forma aislada y cada dato debe introducirse manualmente. Con él, Claude puede comunicarse directamente con esos sistemas.
El “servidor” en MCP es simplemente un pequeño programa. Expone herramientas y datos a Claude, gestiona la comunicación bidireccional y se ejecuta dondequiera que estén los datos, ya sea en una computadora portátil, un clúster o un servidor. No se trata de una infraestructura pesada; un servidor MCP funcional puede ser un script de Python de 50 líneas.
Una analogía que ayuda
Las integraciones con Slack son una buena comparación. Antes de ellas, alguien tenía que publicar manualmente el mensaje “Se inició el despliegue en producción”. Después, el pipeline CI/CD lo publica automáticamente, y Slack se convierte en un centro de control que sabe qué está sucediendo. MCP hace lo mismo para Claude: antes, una persona tenía que proporcionarle datos sobre la infraestructura; luego, mantiene una conexión directa y puede verificarlo por sí mismo.
Por qué los equipos de DevOps son los que más ganan
La mayoría de los ejemplos de uso de MCP están dirigidos a desarrolladores de aplicaciones. Los ingenieros de DevOps, SRE y de plataforma ganan aún más, ya que su trabajo consiste en gestionar sistemas que ya cuentan con APIs: Kubernetes, AWS, Datadog o Grafana, PagerDuty, Terraform Cloud, GitHub, Confluence y Notion. Cada uno de ellos puede conectarse a Claude a través de MCP. Una vez hecho esto, ya no es necesario pasar de una herramienta a otra para recopilar información; el trabajo se reduce a hacer preguntas.
Qué hacen los equipos con los servidores MCP
- Preguntas sobre clústeres. Al preguntar qué despliegues en producción han estado activos durante más de 30 días sin actualizaciones, Claude se conecta al servidor MCP de Kubernetes, el cual consulta el clúster y devuelve la respuesta. No se necesita kubectl ni copiar y pegar.
- Preguntas sobre costos. Al preguntar qué servicios de AWS tuvieron un aumento esta semana en comparación con la anterior, Claude se conecta a un servidor de AWS y a Cost Explorer, y la respuesta es un desglose en lenguaje sencillo.
- Contexto de incidentes. Una pregunta sobre los incidentes en PagerDuty de las últimas 24 horas y si alguno sigue abierto, obtiene el estado actual desde un servidor de PagerDuty y lo resume.
- Búsqueda de guías de procedimientos. Al solicitar la guía relacionada con el agotamiento del pool de conexiones a bases de datos, se busca en la base de conocimientos a través de un servidor de Confluence y se devuelve el documento exacto.
- Auditorías de seguridad. Al preguntar si algún grupo de seguridad permite el acceso 0.0.0.0/0 en puertos distintos al 443, un servidor de AWS escanea esos grupos y marca las infracciones.
Tres tipos de servidores
Los equipos que comienzan suelen terminar creando tres tipos.
Servidores de observabilidad de solo lectura
Estos conectan a Claude con las API de monitoreo e infraestructura para que pueda describir el estado actual de las cosas. Ejemplos típicos son un servidor de estado de clústeres Kubernetes, un servidor de inventario de AWS, un servidor de métricas de Datadog y un servidor de registros de CloudWatch. El riesgo es muy bajo porque Claude solo puede leer. Este es el punto de partida, ya que genera confianza antes de conceder cualquier acceso de escritura.
Servidores de base de conocimientos
Estos enlaces conectan a Claude con la documentación interna para búsquedas y recuperación de información: un servidor de manuales en Confluence o Notion, un servidor de wiki en GitHub, una carpeta con archivos markdown locales o un repositorio de registros de decisiones de arquitectura. El riesgo es prácticamente nulo, ya que se trata de búsquedas en documentos. Son importantes porque hacen que Claude parezca realmente conocer la empresa.
Servidores de acción
Estos permiten a Claude realizar tareas como crear tickets, activar flujos de trabajo o reiniciar servicios. Ejemplos incluyen la creación de tickets en Jira, la gestión de incidentes en PagerDuty, la creación de solicitudes de pull en GitHub y las notificaciones en Slack. El riesgo varía de medio a alto, ya que ahora Claude está modificando cosas. Los controles de aprobación siempre deben estar antes de las operaciones de escritura, de modo que el reinicio de un pod nunca se realice sin un paso de confirmación.
Creación de un servidor MCP para Kubernetes
A continuación se muestra un servidor Kubernetes de solo lectura funcional en Python. Primero, instale el SDK:
pip install mcp kubernetes --break-system-packages
Luego guarde el servidor como k8s_mcp_server.py:
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
from kubernetes import client, config as k8s_config
# Load kubeconfig
try:
k8s_config.load_incluster_config()
except:
k8s_config.load_kube_config()
v1 = client.CoreV1Api()
apps_v1 = client.AppsV1Api()
# Create MCP server
app = Server("k8s-devops-server")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
"""Tell Claude what tools are available."""
return [
types.Tool(
name="get_pods",
description="Get all pods in a namespace with their status, restarts, and age.",
inputSchema={
"type": "object",
"properties": {
"namespace": {
"type": "string",
"description": "Kubernetes namespace (e.g. production, staging)"
}
},
"required": ["namespace"]
}
),
types.Tool(
name="get_pod_logs",
description="Get recent logs from a pod or deployment.",
inputSchema={
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Pod or deployment name"
},
"namespace": {
"type": "string",
"description": "Kubernetes namespace"
},
"lines": {
"type": "integer",
"description": "Number of log lines to return (default 50)"
}
},
"required": ["name", "namespace"]
}
),
types.Tool(
name="get_deployments",
description="List all deployments with replica counts and status.",
inputSchema={
"type": "object",
"properties": {
"namespace": {
"type": "string",
"description": "Kubernetes namespace"
}
},
"required": ["namespace"]
}
),
types.Tool(
name="get_events",
description="Get recent Kubernetes events. Useful for diagnosing issues.",
inputSchema={
"type": "object",
"properties": {
"namespace": {
"type": "string",
"description": "Kubernetes namespace"
}
},
"required": ["namespace"]
}
),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
"""Handle tool calls from Claude."""
if name == "get_pods":
namespace = arguments["namespace"]
pods = v1.list_namespaced_pod(namespace=namespace)
lines = [f"Pods in {namespace}:\n"]
for pod in pods.items:
phase = pod.status.phase
restarts = sum(
cs.restart_count
for cs in (pod.status.container_statuses or [])
)
name_str = pod.metadata.name
lines.append(f" {name_str}: {phase} | restarts: {restarts}")
return [types.TextContent(type="text", text="\n".join(lines))]
elif name == "get_pod_logs":
pod_name = arguments["name"]
namespace = arguments["namespace"]
lines = arguments.get("lines", 50)
import subprocess
result = subprocess.run(
f"kubectl logs {pod_name} -n {namespace} --tail={lines}",
shell=True, capture_output=True, text=True
)
output = result.stdout or result.stderr or "No logs found"
return [types.TextContent(type="text", text=output)]
elif name == "get_deployments":
namespace = arguments["namespace"]
deployments = apps_v1.list_namespaced_deployment(namespace=namespace)
lines = [f"Deployments in {namespace}:\n"]
for d in deployments.items:
desired = d.spec.replicas or 0
available = d.status.available_replicas or 0
status = "✅" if desired == available else "⚠️"
lines.append(
f" {status} {d.metadata.name}: {available}/{desired} replicas"
)
return [types.TextContent(type="text", text="\n".join(lines))]
elif name == "get_events":
namespace = arguments["namespace"]
events = v1.list_namespaced_event(namespace=namespace)
# Sort by last timestamp, show recent ones
sorted_events = sorted(
events.items,
key=lambda e: e.last_timestamp or e.event_time,
reverse=True
)[:20]
lines = [f"Recent events in {namespace}:\n"]
for e in sorted_events:
lines.append(
f" [{e.type}] {e.reason}: {e.message[:100]}"
)
return [types.TextContent(type="text", text="\n".join(lines))]
return [types.TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Para conectarlo a Claude Desktop, edite el archivo de configuración, que se encuentra en una de estas rutas:
Mac: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
y añada la entrada del servidor:
{
"mcpServers": {
"kubernetes": {
"command": "python",
"args": ["/path/to/k8s_mcp_server.py"]
}
}
}
Reinicie Claude Desktop y estará listo. Al preguntar cuántos pods están en ejecución en producción, Claude realiza la verificación correspondiente.
Creación de un servidor de runbooks
Este podría ser aún más valioso. Dirige a Claude hacia los runbooks del equipo, de modo que, en medio de un incidente, Claude encuentra el adecuado en lugar de que alguien busque manualmente en Confluence.
import asyncio
import os
import glob
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
app = Server("runbook-server")
RUNBOOKS_PATH = os.path.expanduser("~/runbooks") # point to your runbooks folder
def load_all_runbooks() -> dict[str, str]:
"""Load all markdown runbooks from the folder."""
runbooks = {}
for filepath in glob.glob(f"{RUNBOOKS_PATH}/**/*.md", recursive=True):
with open(filepath, 'r', errors='replace') as f:
content = f.read()
name = os.path.relpath(filepath, RUNBOOKS_PATH)
runbooks[name] = content
return runbooks
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="search_runbooks",
description=(
"Search your team's runbooks for a specific topic, service, or error. "
"Use this when someone asks about a procedure, a known issue, "
"or how to fix something."
),
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "What to search for (e.g. 'database connection pool', 'nginx 502', 'SSL cert renewal')"
}
},
"required": ["query"]
}
),
types.Tool(
name="list_runbooks",
description="List all available runbooks by category.",
inputSchema={
"type": "object",
"properties": {}
}
),
types.Tool(
name="get_runbook",
description="Get the full content of a specific runbook by name.",
inputSchema={
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Runbook filename (from list_runbooks)"
}
},
"required": ["name"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
runbooks = load_all_runbooks()
if name == "list_runbooks":
names = sorted(runbooks.keys())
output = "Available runbooks:\n" + "\n".join(f" - {n}" for n in names)
return [types.TextContent(type="text", text=output)]
elif name == "search_runbooks":
query = arguments["query"].lower()
matches = []
for rb_name, content in runbooks.items():
if query in content.lower() or query in rb_name.lower():
# Return first 500 chars as preview
preview = content[:500].strip()
matches.append(f"📄 {rb_name}\n{preview}\n...")
if not matches:
return [types.TextContent(
type="text",
text=f"No runbooks found for '{query}'"
)]
return [types.TextContent(
type="text",
text=f"Found {len(matches)} runbook(s):\n\n" + "\n\n---\n\n".join(matches)
)]
elif name == "get_runbook":
rb_name = arguments["name"]
content = runbooks.get(rb_name)
if not content:
return [types.TextContent(type="text", text=f"Runbook not found: {rb_name}")]
return [types.TextContent(type="text", text=content)]
return [types.TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Agréguelo a la configuración de Claude Desktop junto con el otro servidor:
{
"mcpServers": {
"kubernetes": {
"command": "python",
"args": ["/path/to/k8s_mcp_server.py"]
},
"runbooks": {
"command": "python",
"args": ["/path/to/runbook_mcp_server.py"]
}
}
}
Después de eso, una pregunta como “¿cuál es el manual de procedimientos para un alto uso de memoria en los pods de API?” devuelve el propio manual del equipo y no un resultado de búsqueda genérico.
Servidores que ya existen
No todo tiene que construirse desde cero. La colección oficial de servidores de referencia en GitHub, bajo la organización modelcontextprotocol, abarca GitHub (repositorios, solicitudes de pull, problemas y commits), Postgres, Slack, una búsqueda en la base de conocimientos de AWS, acceso al sistema de archivos local y la búsqueda web de Brave. Existen servidores creados por la comunidad para Datadog (métricas, paneles de control y monitores), PagerDuty (incidentes y alertas) y Jira (tickets y sprints). Vale la pena revisar primero esa colección, ya que alguien podría haber creado ya lo que se necesita.
Errores que evitar
- Conceder acceso de escritura demasiado pronto. Comience con modo solo lectura. Una vez ganada la confianza, agregue las herramientas de escritura una por una.
- Crear un servidor gigantesco. Los servidores pequeños y especializados son más fáciles de mantener, depurar y entregar a personas específicas. Un
k8s-serverdebería contener solo herramientas de Kubernetes, unaws-serversolo herramientas de AWS y unrunbook-serversolo documentos. - Omitir el registro de actividades. Registre cada llamada a las herramientas, incluyendo qué consultó Claude, cuándo y qué resultado obtuvo, especialmente antes de que lleguen las herramientas de escritura.
- Omitir la aprobación para operaciones de escritura. Cualquier acción que genere un ticket, reinicie un servicio o fusione una solicitud de integración merece un paso de confirmación, con Claude mostrando primero de qué se trata.
Una mañana con una configuración adecuada
Una sesión típica comienza solicitando el estado de producción. Claude verifica el servidor Kubernetes e informa sobre los despliegues y los pods, marca un pod con reinicios repetidos y luego sigue investigando el problema.
You: What's the state of production right now?
Claude: [checks kubernetes MCP server]
3 deployments all healthy, 47/47 pods running.
One pod in payments namespace has 8 restarts - api-worker-6d4f9b.
Everything else looks stable.
You: Why is that pod restarting?
Claude: [checks pod logs via MCP]
Logs show OOMKilled. Memory limit is 256Mi but it's hitting
248Mi regularly. Looks like it needs a bump to at least 512Mi.
Want me to check if we have a runbook for this?
You: Yes
Claude: [searches runbook MCP server]
Found it - "memory-limit-tuning.md"
The runbook says: increase limit in Helm values,
run helm upgrade, monitor for 10 minutes.
Last updated by Rahul on March 12th.
You: What did our AWS bill look like this week?
Claude: [checks AWS MCP server]
$4,823 this week vs $4,210 last week - up 14.5%.
Spike is from EC2 in us-east-1. Looks like
3 r6g.2xlarge instances were added on Tuesday.
Toda esa operación dura unos 90 segundos, sin cambiar de pestaña, sin copiar y pegar y sin buscar.
Empezando esta semana
Construir cinco servidores a la vez es un error. Elija uno. Para la mayoría de los equipos DevOps, la mejor opción inicial es el servidor de runbooks, ya que es de solo lectura, brinda valor de inmediato y funciona con los documentos que ya existen. El proceso consta de tres pasos:
- Reúna los runbooks del equipo en una sola carpeta, como
~/runbooks/. - Use el código del servidor de runbooks anterior y diríjalo a esa carpeta.
- Añádalo a la configuración de Claude Desktop y reinícielo.
Esto proporciona un asistente que conoce los procedimientos del equipo. Cuando demuestre ser fiable, añada el servidor de Kubernetes, luego AWS, y después lo que venga a continuación.
La visión general
MCP es aún joven y su ecosistema está creciendo rápidamente, pero ya hay algunas cosas claras. Los equipos que cuentan con servidores MCP tienen una experiencia de IA notablemente diferente a la de aquellos que no los tienen. Pasar de una ventana de chat a algo que funciona como un compañero de operaciones requiere algo fundamental: un servidor que exponga los sistemas adecuados. Y los equipos que están desarrollando estos sistemas en la actualidad están sentando las bases para la forma en que la IA se utilizará en el trabajo de infraestructura. Esto no es un proyecto de investigación; los equipos ya los utilizan en entornos de producción, y el servidor Kubernetes mencionado anteriormente puede estar listo para funcionar en unos 20 minutos.
Leer más
- Un servidor MCP personal que permite a Claude buscar los runbooks de tu equipo — Indexa los runbooks y análisis posteriores en formato Markdown en ChromaDB, expone una búsqueda semántica a través de un servidor MCP y permite a Claude encontrar los procedimientos para incidentes en segundos.
- Notas prácticas: kagent: El marco de trabajo de agente de IA nativo de Kubernetes que deberías utilizar — Guía paso a paso de Notas prácticas: kagent: El marco de trabajo de agente de IA nativo de Kubernetes que deberías utilizar: contratos, verificaciones y espacios de código listos para usar para que los equipos implementen este patrón.