DevOps MCP — Secure MCP Server for Linux Server Automation

Un servidor MCP de control de acceso de tres niveles que permite a asistentes de IA (Claude Code, Cursor, Windsurf) escanear, planificar y operar servidores Linux de forma segura a través de SSH sin acceso completo de escritura. Incluye una puerta de token de consentimiento humano fuera de banda, escaneo automatizado de conflictos de puertos y un modo seguro predeterminado completamente de solo lectura para eliminar comandos destructivos accidentales en entornos de producción.

Documentación

devops-mcp

Un servidor MCP (Model Context Protocol) basado en modos que permite a los asistentes de IA (Claude Desktop, Cursor, Windsurf, …) operar realmente servidores Linux sin entregarles las llaves del reino.

El modelo puede conectarse, escanear, planificar y desplegar — pero cada paso que cambia el estado en un servidor tipo producción pasa por una puerta de consentimiento que la IA no puede auto-aprobar. El descubrimiento es de solo lectura por diseño.

┌─────────────────┐          MCP / stdio          ┌────────────────────┐
│  AI client      │  ───────────────────────────► │  devops-mcp        │
│  (Claude /      │                               │                    │
│   Cursor / …)   │  ◄─────────────────────────── │  ssh2 / docker /   │
└─────────────────┘                               │  child_process     │
                                                  └────────┬───────────┘
                                                           │ SSH
                                                           ▼
                                                   ┌────────────────┐
                                                   │  Your VPS      │
                                                   └────────────────┘

⚡ Configuración inicial (léelo una vez, hazlo una vez)

Hay exactamente cuatro pasos. No te saltes el paso 2.

1. Instalación

git clone <your-fork-url>.git devops-mcp
cd devops-mcp
npm install
npm run build

Requiere Node ≥ 18.

2. Genera tu token de elevación y guárdalo en un lugar donde no lo pierdas

# Linux / macOS
openssl rand -hex 24

# Windows PowerShell
$bytes = New-Object byte[] 24; (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($bytes); [BitConverter]::ToString($bytes).Replace("-","").ToLower()

# Or, via Node
node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"

Obtendrás algo como 6ba329add30b19a5a347178f7e3705fdea0ac1aa66cb9274.

🔑 GUARDA ESTE TOKEN ANTES DEL PASO 3.

Este token es lo único que se interpone entre la IA y el acceso descontrolado a producción. El modelo nunca lo ve. Cada vez que la IA quiera elevar al modo PROVISION/FULL, aprobar una acción destructiva, cambiar el rol de un servidor o escribir en un servidor tipo producción, lo pegas una vez.

Ponlo en un gestor de contraseñas. Si lo pierdes:

  • Puedes editar manualmente la configuración de tu cliente MCP para establecer uno nuevo, o
  • Puedes pedirle a la IA que llame a rotate_consent_token si aún tienes el anterior (lo cual es circular si has perdido ambos).

No hay flujo de recuperación. Esta es la puerta; no incluimos una puerta trasera.

3. Añade devops-mcp a la configuración de tu cliente MCP

Para Claude Desktop, edita claude_desktop_config.json:

  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Añade (o fusiona en el mcpServers existente):

{
  "mcpServers": {
    "devops-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/devops-mcp/dist/index.js"],
      "env": {
        "DEVOPS_MCP_ELEVATION_TOKEN": "<paste your token from step 2 here>",
        "LOG_LEVEL": "info"
      }
    }
  }
}

La misma estructura funciona para Cursor, Windsurf y cualquier otro cliente MCP: el bloque env es la forma estándar de MCP para pasar secretos.

4. Cierra y vuelve a abrir completamente tu cliente MCP

No "cierra la ventana". En Windows, eso significa bandeja del sistema → Salir. El token de elevación se lee al inicio; el cliente debe reiniciarse para que surta efecto.

Has terminado. La próxima vez que hables con la IA, di "añade mi servidor en …" y te guiará.


Por qué existe esto

Los servidores MCP genéricos de "ejecutar cualquier comando" son peligrosos en máquinas de producción. Un modelo con shell completo en un servidor en vivo puede — y lo hará — reiniciar el servicio equivocado, desplegar en un puerto en uso, docker prune un volumen de base de datos, o escalarse a root porque nada le dijo que no lo hiciera.

devops-mcp traza una línea dura entre leer y cambiar:

  • Leer siempre está permitido (dentro de una lista blanca SAFE de solo lectura).
  • Cambiar en un servidor tipo producción requiere el token del humano — pasado fuera de banda, invisible para el modelo.
  • Desplegar un nuevo proyecto pasa por una verificación de conflicto de puertos y un script revisable, no 40 comandos ad-hoc.

Características

Control de acceso

  • Modo de tres niveles: SAFE (predeterminado, lista blanca de solo lectura), PROVISION (instalaciones del sistema, caducidad predeterminada de 1 h), FULL (root, caducidad predeterminada de 30 min).
  • Token de consentimiento fuera de banda — la elevación y las aprobaciones requieren una cadena que solo el usuario tiene. El modelo literalmente no puede leerla.
  • Puerta de escritura en producción — en servidores role: production (o cualquier servidor que el escáner marque como productionLikely), cualquier comando no-SAFE requiere consentToken + acknowledgeProductionWrite: true. Los comandos catastróficos — aquellos que son irrecuperables sin una copia de seguridad (rm de una ruta no temporal, rm -rf /…, dd of=/dev/…, mkfs, SQL DROP TABLE/DATABASE, docker rmi, docker volume rm, docker rm -v, docker system prune) — además requieren backupVerified: true. Las escrituras ordinarias y las operaciones recuperables (editar un archivo, rm /tmp/scratch, docker rm simple de un contenedor que puede recrearse desde su imagen) no necesitan backupVerified. Los rechazos muestran el comando resuelto exacto.
  • Política por servidorallowedModes, blockedCommands, allowedPaths, requireApproval viven en config/<server-id>/server.json y se aplican en cada comando SSH.
  • role obligatorioadd_server no permitirá que la IA establezca silenciosamente el rol por defecto; debe preguntar al usuario, y la respuesta incluye un bloque roleConsequences que la IA te lee.
  • Rotación de tokenrotate_consent_token genera un token nuevo (por defecto en modo de prueba; apply: true actualiza atómicamente la configuración de tu cliente MCP).
  • Rotación de credencialesupdate_server_credentials rota la contraseña, intercambia la clave SSH (incluidas claves cifradas mediante keyPassphrase), o migra host/usuario/puerto sin volver a añadir el servidor. El rol, las restricciones y el perfil de escaneo permanecen intactos. Cierra cualquier sesión activa a ese servidor primero, valida las nuevas credenciales con una conexión de prueba, y está sujeto a consentimiento en producción.
  • Listo para AWS / EC2 .pem — incorpora con el archivo .pem + nombre de usuario + IP. Refiérelo en su lugar (externalKeyPath) o cópialo en el paquete de configuración (keyFilePath); añade keyPassphrase solo si la clave está cifrada.
  • Múltiples conexiones simultáneas, clave por serverId — el MCP mantiene una conexión SSH por servidor, no un único espacio global. Claude Desktop ejecuta un único proceso MCP compartido en todas tus conversaciones; con una conexión global, dos chats trabajando en dos servidores se pisarían mutuamente ("sesión 1 en servidor A, sesión 2 se conecta a B, ahora los comandos de A golpean B"). Las conexiones con clave permiten que ambas coexistan. run_command toma un serverId: opcional cuando hay exactamente un servidor conectado, obligatorio cuando hay dos o más — una llamada ambigua se rechaza en lugar de adivinarse. Cada respuesta de run_command muestra target.serverId y activeConnections.
  • Anti-desviación de objetivo — las respuestas de run_command, set_mode y get_current_mode llevan la identidad del servidor conectado, para que una conversación nunca pueda terminar operando silenciosamente la máquina equivocada. disconnect_server toma un serverId opcional (o "all").
  • Incorporación consciente de sesiones en vivoadd_server muestra el/los servidor(es) actualmente conectado(s) en su respuesta y le dice a la IA que no cambie automáticamente al recién añadido sin preguntar.

Descubrimiento y planificación

  • Escaneo de descubrimiento del servidor — sonda de solo lectura de SO, hardware, puertos en escucha, stack instalado (docker / nginx / apache / node / pm2), contenedores en ejecución, sitios nginx analizados, servicios systemd. La salida se persiste como un ServerProfile.
  • Diff de perfil al reconectardiff_server_profile vuelve a escanear e informa qué cambió desde la instantánea guardada.
  • Conciencia de conflicto de puertoscheck_port_conflict devuelve el proceso en escucha + una sugerencia de puerto libre antes del despliegue.
  • Planifica, no disparesplan_deployment devuelve un script bash idempotente que el usuario revisa. El MCP no lo ejecuta.

Endurecimiento de seguridad

  • Todos los argumentos de comandos se citan con shell antes de llegar al shell remoto. No más cargas útiles sh -c "<long script>" que se dividen en el nivel de shell equivocado.
  • El validador inspecciona los argumentosrun_command({command:"ls", args:["; rm -rf /"]}) ya no se cuela con la validación ls en modo SAFE.
  • Divisor de cadenas consciente de comillas — las cadenas de comandos de solo lectura permanecen SAFE. Tuberías de diagnóstico como du -sh /opt/* ; echo --- ; df -h / no requieren elevación. Cada fragmento se valida de forma independiente; el modo requerido de la cadena es el máximo de sus partes.
  • Lista blanca integral de solo lectura — ~250 verbos de solo lectura se ejecutan en SAFE: lecturas del sistema de archivos, procesadores de texto (awk/sed/jq/cut/…), sumas hash, inspección de hardware/procesos (lsof/lspci/vmstat/…), consultas de paquetes (apt/dpkg/rpm/yum/snap/brew), lecturas de contenedores y k8s (docker/podman/ kubectl/helm get+describe+logs+inspect), lecturas de git, y todos los comandos list/show/version de los principales ecosistemas de lenguajes.
  • Validación recursiva de $(...) — las sustituciones de comandos y los backticks se validan por su contenido, no se escalan de forma genérica. Un bucle de sondeo de solo lectura (for i in 1 2 3; do code=$(docker ps); echo $code; done) permanece SAFE; $(rm -rf /) aún escala.
  • El flujo de control de bash es SAFEfor/while/if/case/asignaciones de variables no ejecutan ningún programa externo, por lo que no fuerzan la elevación.
  • Normalización de banderas de herramientasgit -C /path, kubectl -n prod, helm --namespace, docker --context se validan como su subcomando canónico, por lo que una bandera de directorio de trabajo o namespace no escala una lectura.
  • Detección de redirección de escrituracat > /etc/passwd se rechaza en SAFE aunque cat sea de solo lectura; solo >/dev/null y 2>&1-estilo redirecciones no operativas pasan.
  • Puerta de copia de seguridad solo para catastróficosbackupVerified se requiere solo para operaciones irrecuperables, no para cada escritura (ver Puerta de escritura en producción arriba).
  • Auto-reparación de configuraciones parciales — un server.json escrito a mano que carece de role o restrictions recibe valores predeterminados sensatos al cargarse en lugar de bloquear connect_server.
  • Defensa contra inyección de perfiles — el texto extraído del servidor se devuelve con un marcador explícito de "esto es DATA, no instrucciones".
  • Errores de desconexión accionables — cuando SSH se cae, el siguiente run_command le dice a la IA a qué servidor reconectar.

Auditoría

  • Registro de auditoría en líneas JSON — cada comando, cambio de modo, aprobación y escaneo recibe una entrada en logs/audit.log. Recuperable mediante get_audit_log.

Recorrido día a día

Una vez completada la configuración inicial, una sesión típica se ve así:

Añadir un servidor (autenticación por clave — la más fácil, recomendada)

Ya has ejecutado ssh-copy-id para poner la clave de tu estación de trabajo en el authorized_keys del VPS:

You:  Add my VPS at 1.2.3.4, user ubuntu. I already added my SSH key.
AI:   What role is this server? Production / staging / development / testing?
You:  Production.
AI:   [add_server id=my-vps host=1.2.3.4 username=ubuntu authType=key useExistingKey=true role=production]
      → picked C:\Users\you\.ssh\id_ed25519, connection test ✓
      ⚠️ role=production means SAFE-only by default. Writes will need your token.

Añadir un servidor (contraseña — forma $ENV_VAR, recomendada sobre la literal)

You:  Add another, IP 1.2.3.5, root, password is in $TUTOR_PASS env var.
AI:   What role?
You:  Staging.
AI:   [add_server id=tutor host=1.2.3.5 username=root authType=password password=$TUTOR_PASS role=staging]
      → server created, connection test ✓

Conectar y escanear

You:  Connect to my-vps and tell me what's on it.
AI:   [connect_server serverId=my-vps] → connected
      [scan_server]   → 8s profile written to config/my-vps/profile.json
      Server is production-like:
        - nginx serving example.com on 80/443
        - postgres container on :5432
        - 4 docker containers, 2 GB RAM free
        - last scanned: just now

Diagnósticos de solo lectura — sin necesidad de elevación

Las cadenas de comandos de solo lectura se ejecutan en SAFE:

You:  How much disk are the projects using?
AI:   [run_command "du -sh /opt/* 2>/dev/null ; echo --- ; df -h /" executor=ssh]
      → ran in SAFE mode (read-only chain, no elevation required)

Desplegar un nuevo proyecto

You:  Deploy https://github.com/me/newapp on this box, port 8000.
AI:   [check_port_conflict port=8000] → in use by "node" (the example.com app)
      Port 8000 is taken. Suggested free port: 8001. Use 8001 or stop the existing app?
You:  Use 8001.
AI:   [plan_deployment port=8001 runtime=node ...] → returns a 26-line bash script
      Here's the script. Please review.
You:  Looks good. Run it.
AI:   [run_command ...] → refused: production write-gate.
      To run this I need your elevation token and confirmation that a backup exists.
You:  Token is <paste>. Yes, snapshot taken this morning.
AI:   [run_command consentToken=<…> acknowledgeProductionWrite=true backupVerified=true]
      → ✓ deployed

Rotar credenciales (la contraseña del VPS cambió, o intercambiaste tu clave SSH)

You:  I rotated my-vps's root password. New one is in $MY_VPS_PASS_NEW.
AI:   my-vps is role=production — for the rotation I need your elevation token.
You:  Token is <paste>.
AI:   [update_server_credentials serverId=my-vps authType=password
        password=$MY_VPS_PASS_NEW consentToken=<…>]
      → closed active SSH session (was connected), new creds tested ✓
      Reconnect with connect_server when ready.
You:  Connect.
AI:   [connect_server serverId=my-vps] → ✓

El rol, las restricciones, la lista de comandos bloqueados y el perfil de escaneo se conservan. Solo cambian los campos de autenticación.

Actualizar el rol de un servidor más tarde

You:  Actually my-vps is staging now, not production.
AI:   This is a production-touching change, please confirm with the token.
You:  Token is <paste>.
AI:   [update_server serverId=my-vps role=staging applyRoleDefaults=true consentToken=<…>]
      → role changed; allowedModes now [SAFE, PROVISION].

Rotar el token (cuando el anterior se ha filtrado, p. ej., en un chat)

You:  Generate a new elevation token and update Claude Desktop's config.
AI:   For verification, paste the current token.
You:  <paste current>
AI:   [rotate_consent_token consentToken=<current> apply=true]
      → 🔑 NEW TOKEN: <new>  ← SAVE THIS NOW, in a password manager.
      Claude Desktop must be fully restarted for the new token to take effect.
      Until then, the OLD token still works on this running session.

Modos de acceso

ModoCaducidad predeterminadaQué permite
SAFEsin caducidadLista blanca de solo lectura: ls, cat, df, ss, docker ps, nginx -T, etc. Las cadenas de comandos todos-SAFE también funcionan.
PROVISION1 horaapt/yum, docker run/build/stop, systemctl start/stop, nginx, ufw, operaciones de archivos
FULL30 minutosCualquier cosa, incluyendo fdisk, dd, shutdown, rm -rf /

La elevación requiere:

  • acknowledgeRisk: true (la IA lo establece)
  • consentToken: "<your token>" (solo tú lo tienes)

La degradación siempre está permitida y es instantánea. Las sesiones caducan automáticamente de vuelta a SAFE.


Referencia de herramientas (32 herramientas)

Ciclo de vida del servidor

HerramientaModoQué hace
add_serverSAFEOnboarding de una sola vez. Cinco rutas de autenticación: password (literal o $ENV_VAR), keyFilePath (copiar una clave en la configuración), privateKey (pegar en línea), externalKeyPath (apuntar a una clave existente sin copiarla), useExistingKey (selección automática de ~/.ssh/id_*). Requiere role. Prueba automáticamente la conexión. Devuelve roleConsequences.
update_serverSAFECambiar rol, allowedModes, blockedCommands, allowedPaths, requireApproval, nombre o descripción. Tocar producción requiere consentToken. Los campos de autenticación NO son mutables aquí — ver update_server_credentials.
update_server_credentialsSAFERotar contraseña, intercambiar clave SSH, migrar host/usuario/puerto. Cierra primero cualquier sesión SSH activa a este servidor. Prueba las nuevas credenciales por defecto. Rol/restricciones/profile.json preservados. Los servidores de producción requieren consentToken.
setup_server_configSAFENivel inferior: init / add / status. Misma primitiva que usa add_server.
list_serversSAFEListar todos los servidores configurados
test_connectionSAFEIntentar conectarse por SSH a un servidor configurado (no ejecuta comandos)
connect_serverSAFEAbrir la sesión SSH de trabajo para comandos posteriores
disconnect_serverSAFECerrar la sesión SSH

Descubrimiento (todo solo lectura)

HerramientaModoQué hace
scan_serverSAFESondear SO / hardware / puertos / stack / cargas de trabajo. Persiste config/<id>/profile.json. Sin escrituras en el objetivo.
get_server_profileSAFELeer el perfil guardado sin volver a escanear
diff_server_profileSAFEVolver a escanear e informar qué cambió. No sobrescribe el perfil guardado a menos que accept: true
check_port_conflictSAFE¿Está el puerto X en uso? Devuelve el listener + una sugerencia de puerto libre
list_containersSAFEListar contenedores Docker en el servidor conectado
list_playbooksSAFEListar playbooks de aprovisionamiento disponibles

Ejecución y despliegue

HerramientaModoQué hace
run_commandvaríaEjecutar un comando (local / ssh / docker). Los argumentos se citan con shell; las cadenas se dividen y cada fragmento se valida; se aplica la puerta de escritura de producción.
plan_deploymentSAFEGenerar un script bash idempotente (clonar + compilar + pm2/docker). Rechaza en conflicto de puerto a menos que acknowledgeConflict: true. SIN EJECUCIÓN.
run_playbookPROVISIONEjecutar un playbook de aprovisionamiento predefinido
install_dockerPROVISIONInstalar Docker + Compose
install_nginxPROVISIONInstalar Nginx
configure_nginxPROVISIONGenerar configuración de proxy inverso nginx + recargar. Usa heredoc para evitar errores de citado de shell.
deploy_appvaríaPrimitiva de despliegue de nivel inferior (git clone + compilar + iniciar). Todos los valores interpolados se citan con shell.
container_actionSAFE / PROVISIONstart / stop / restart / logs / inspect
transfer_filesSAFE (descarga) / varía (subida)Subida/descarga SFTP de archivos, carpetas (recursivo) o archivos comprimidos. extract: true desempaqueta un .zip/.tar.gz/.tgz/.tar/.tar.bz2/.tar.xz/.gz subido en el servidor; verifyChecksum: true hace sha256 de extremo a extremo en archivos individuales. Las subidas a un servidor tipo producción pasan por la puerta de escritura.

Modo, consentimiento, auditoría

HerramientaQué hace
get_current_modeModo actual + permisos + tiempo restante
set_modeCambiar modo. La elevación requiere acknowledgeRisk + consentToken
approve_actionAprobar una acción pendiente de alto riesgo. Requiere consentToken
list_pending_approvalsListar solicitudes de aprobación en cola
rotate_consent_tokenGenerar un nuevo token de elevación. apply: true reescribe atómicamente la configuración del cliente MCP. Requiere el token actual. Lea las advertencias de la respuesta antes de reiniciar el cliente.
generate_ssh_keyGenerar un par de claves SSH de sesión con caducidad automática
revoke_ssh_keyRevocar una clave SSH de sesión
get_audit_logSeguir / filtrar logs/audit.log (analiza líneas JSON, filtra por since y action)
health_checkVitalidad + versión + modo actual

Opciones de autenticación (add_server)

Cinco formas de autenticarse, elegidas por authType + qué campo de clave/contraseña se establece:

OpciónCampos de esquemaCuándo usar
ContraseñaauthType:"password" + password (literal o $ENV_VAR)Cuando la autenticación por clave no está configurada. Prefiere $ENV_VAR para que la contraseña no esté en disco en la configuración.
Copiar un archivo de claveauthType:"key" + keyFilePathTienes un archivo PEM que quieres almacenar junto con la configuración del servidor (paquete portátil)
Pegar clave en líneaauthType:"key" + privateKeySolo tienes el texto de la clave
Apuntar a clave existenteauthType:"key" + externalKeyPathYa tienes ~/.ssh/whatever — no copies, solo referencia. ~ se expande.
Auto-buscar tu claveauthType:"key" + useExistingKey: trueTu ~/.ssh/id_* predeterminado ya está en authorized_keys en el servidor. Lo más fácil.

El manejador valida exactamente una fuente de clave por llamada. Combinar, por ejemplo, useExistingKey y keyFilePath se rechaza con un error claro.

Cualquier ruta de clave (keyFilePath / externalKeyPath) acepta un keyPassphrase opcional (literal o $ENV_VAR) para claves privadas cifradas.

AWS EC2 (el caso .pem)

Obtienes un archivo .pem, un nombre de usuario (ubuntu, ec2-user, admin, …) y una IP/DNS pública. Dos formas:

// Reference the .pem where it sits (recommended — nothing copied)
{
  "id": "my-ec2", "host": "ec2-1-2-3-4.compute.amazonaws.com",
  "username": "ec2-user", "authType": "key",
  "externalKeyPath": "C:\\Users\\you\\Downloads\\my-key.pem",
  "role": "production"
}

// Or copy the .pem into the server's config folder (portable bundle)
{
  "id": "my-ec2", "host": "1.2.3.4", "username": "ubuntu",
  "authType": "key",
  "keyFilePath": "C:\\Users\\you\\Downloads\\my-key.pem",
  "role": "staging"
}

La mayoría de las claves AWS no tienen frase de contraseña — omite keyPassphrase. Si la tuya está cifrada, agrega "keyPassphrase": "$MY_PEM_PASS" y establece esa variable de entorno.

sshd moderno + autenticación por contraseña: ssh2 necesita tryKeyboard: true para configuraciones de sshd que usan PAM (Ubuntu 22.04+, Debian 12, Amazon Linux 2023, RHEL 9). devops-mcp lo establece automáticamente — las contraseñas funcionan incluso cuando el servidor tiene PasswordAuthentication no y solo permite keyboard-interactive.


Configuración del servidor en disco

Cada servidor vive en su propia carpeta:

config/
├── my-vps/
│   ├── server.json     # config (host, user, auth, role, restrictions)
│   ├── key.pem         # optional SSH private key (only if you used keyFilePath / privateKey)
│   └── profile.json    # written by scan_server
└── _example/
    └── server.json     # template

Ejemplo de server.json:

{
  "name": "Production Web",
  "host": "1.2.3.4",
  "port": 22,
  "username": "ubuntu",
  "authType": "key",
  "keyFile": "production.pem",
  "role": "production",
  "restrictions": {
    "allowedModes": ["SAFE"],
    "blockedCommands": ["rm -rf", "shutdown", "reboot", "dd"],
    "requireApproval": true
  },
  "description": "Main production web server"
}

Para un flujo de trabajo externalKeyPath (la clave permanece en ~/.ssh/):

{
  "name": "My VPS",
  "host": "1.2.3.4",
  "port": 22,
  "username": "ubuntu",
  "authType": "key",
  "externalKeyPath": "C:\\Users\\you\\.ssh\\id_ed25519",
  "role": "production"
}

Para autenticación por contraseña (siempre prefiere $ENV_VAR):

{
  "authType": "password",
  "password": "$MY_VPS_PASS"
}

$NAME se resuelve a process.env.NAME al momento de la conexión. No comprometas contraseñas literales.

Si a un server.json le falta role o tiene restrictions: {}, el MCP completa los valores predeterminados de role: "development" al cargar (y advierte en los registros). Esto evita que connect_server se bloquee con configuraciones escritas a mano.


Variables de entorno

VariablePropósitoPredeterminado
DEVOPS_MCP_ELEVATION_TOKENToken de consentimiento fuera de banda. Establézcalo. Sin él, set_mode / approve_action / la puerta de escritura de producción aceptan el booleano propio de la IA como consentimiento y el servidor registra una advertencia fuerte.no establecido (modo consultivo)
DEVOPS_MCP_NO_CONSOLE_LOGEstablecer a 1 para suprimir los registros de consola stderr (los registros de archivo aún se escriben)no establecido
LOG_LEVELdebug / info / warn / errorinfo
LOG_DIRDónde escribir combined.log, error.log, audit.log./logs
NODE_ENV(Informativo; los registros van a stderr de todos modos para que MCP stdio no se corrompa)development

Modelo de seguridad

Contra qué protege devops-mcp

  • Modelo ejecutándose a ciegas en producción — los comandos de escritura en un servidor con role: production o productionLikely: true se rechazan sin el token de consentimiento + confirmación explícita + (para operaciones catastróficas e irrecuperables) backupVerified.
  • Aprobaciones autoconcedidas — el modelo no puede fabricar consentToken porque nunca ve DEVOPS_MCP_ELEVATION_TOKEN.
  • Inyección de argumentos — cada argumento para run_command se cita con shell antes de llegar al shell remoto. Los scripts de varias líneas dentro de las cargas útiles de sh -c sobreviven intactos.
  • Comandos de contrabando en argumentos — el validador inspecciona command + args juntos, por lo que run_command({command:"ls", args:["; rm -rf /"]}) se eleva correctamente a FULL.
  • Rechazos de cadenas demasiado amplias — las cadenas de comandos de solo lectura permanecen en SAFE. Cada fragmento se valida de forma independiente; solo gana el peor.
  • Inyección de prompt desde contenido escaneado — banners, etiquetas de contenedores, líneas de registro se devuelven con un marcador de 'datos no confiables'. La respuesta de la herramienta le dice al modelo: mostrar, no ejecutar.
  • Colisiones de puerto silenciosasplan_deployment y check_port_conflict muestran conflictos antes del despliegue.
  • Inyección de shell en ayudantes de despliegue/configuración — cada valor interpolado se cita con shell; las configuraciones de nginx se escriben mediante heredoc; los nombres de rama y las claves de variables de entorno se validan.
  • Los rechazos de la puerta de escritura de producción repiten el comando exacto — para que puedas leer lo que estaba a punto de ejecutarse, no la paráfrasis de la IA.

Lo que devops-mcp no hace

  • No aísla el servidor conectado. Una vez que estás en modo FULL con el token, el modelo puede hacer cualquier cosa que el usuario SSH pueda.
  • No cifra el token de consentimiento en reposo en tu configuración del cliente MCP.
  • No respalda tus datos — backupVerified es una atestación humana, no una verificación.

Ver SECURITY.md para el modelo de amenazas completo.


Gestión de tokens

El token de elevación es una cadena estática almacenada en DEVOPS_MCP_ELEVATION_TOKEN en tu configuración del cliente MCP. No caduca.

Lo que caduca:

  • sesión de modo FULL — 30 min por defecto
  • sesión de modo PROVISION — 1 h por defecto
  • Claves SSH de sesión de generate_ssh_key — 30 min por defecto

Cuando una sesión de modo se agota, vuelve a SAFE; la IA vuelve a pedir el mismo token para re-elevar.

Rotando el token

You:  Rotate the elevation token and update Claude Desktop's config.
AI:   For verification, paste the current token.
You:  <paste>
AI:   [rotate_consent_token consentToken=<current> apply=true]
      → new token: <new>
      → 🔑 SAVE THIS NOW. Without it you're locked out of every write operation.
      → Fully quit and reopen Claude Desktop to activate it.

El MCP escribe el nuevo token atómicamente en tu configuración del cliente (solo la clave DEVOPS_MCP_ELEVATION_TOKEN — todo lo demás en el archivo se preserva). El proceso MCP en ejecución sigue usando el token antiguo hasta que reinicies el cliente.

Si pierdes ambos tokens, el antiguo y el nuevo, entre la rotación y el reinicio, edita manualmente la configuración del cliente para establecer uno nuevo — ese es el flujo de recuperación.


Estructura del proyecto

src/
├── index.ts                       # MCP entry point (stdio)
├── types/                         # TypeScript types
├── core/
│   ├── logger.ts                  # JSON-lines structured logger + audit logger
│   ├── mode-manager.ts            # SAFE / PROVISION / FULL state machine
│   ├── command-validator.ts       # Allowlist + quote-aware chain splitter + wrapper-token scan
│   ├── server-config-manager.ts   # config/<id>/server.json + profile.json + auto-heal
│   ├── server-scanner.ts          # SAFE-mode discovery (read-only by design)
│   ├── ssh-key-manager.ts         # Session SSH keys with auto-expiry
│   └── approval-manager.ts        # Approval queue
├── executors/                     # Local / SSH / Docker — all shell-quote args
├── playbooks/                     # Provisioning playbooks (Docker, Nginx, …)
└── tools/
    ├── tool-schemas.ts            # Zod schemas + MCP tool definitions
    └── tool-handlers.ts           # The actual handlers

Desarrollo

npm run dev        # watch mode (tsx)
npm run build      # tsc → dist/
npm test           # vitest
npm run test:run   # vitest run (CI mode)
npm run lint       # eslint src/**/*.ts

Contribuciones

PRs bienvenidos. Ver CONTRIBUTING.md.

Al agregar una nueva herramienta que escribe en el servidor conectado, asegúrate de que pase por BaseExecutor.execute() para que se apliquen el validador de modo y la puerta de escritura de producción. No hagas shell out directamente desde un manejador, y si debes interpolar un valor en un comando de shell, usa el ayudante shellQuote en el ejecutor — los errores históricos en este código base han sido todos errores de citado.

Licencia

MIT — ver LICENSE.