Skip to main content
Glama
JMG3ND

mcp-despliegue-local

by JMG3ND
README.md
# mcp-despliegue-local

Agente de diagnóstico de **solo lectura** para servicios Node/Nitro que corren bajo
**pm2 en un servidor Windows on-premise**. Se expone como servidor MCP por HTTP, así que
un asistente de IA puede contestar, sin que nadie entre por escritorio remoto:

1. ¿Qué build corre ahí, y desde cuándo?
2. ¿Está vivo?
3. ¿Qué dice el log?
4. ¿Qué configuración tiene cargada?
5. **¿El proceso vivo arrancó con la configuración que hoy está en disco, o con una anterior?**

La quinta es la que justifica el proyecto. pm2 conserva el entorno con el que arrancó cada
proceso, y `pm2 restart` **no lo recarga y no lo dice**. El síntoma es «cambié el archivo y no
pasa nada» y cuesta media hora cada vez. El agente compara por huella el archivo en disco
contra el entorno del proceso vivo y responde directamente si divergen.

## Qué NO es

- **No despliega.** No construye, no copia, no reinicia, no escribe. Ni una sola ruta de
  escritura existe en la tabla de enrutado — no está desactivada por bandera, no existe.
  El despliegue está diseñado por escrito en [`docs/V1-DESPLIEGUE.md`](docs/V1-DESPLIEGUE.md)
  y deliberadamente no implementado.
- **No es un monitor.** No agrega, no alerta, no guarda serie temporal. Contesta preguntas
  cuando se las hacen.
- **No adivina.** No prueba `/health` ni `/healthz` a ver si suena. Cada servicio declara cómo
  se sondea, o declara que no se sondea.
- **No exige nada a los servicios vigilados.** Ni una línea de código, ni un endpoint de versión,
  ni una variable nueva. Todo se infiere desde el disco.

## Por qué existe

En un servidor de planta nadie sabe qué versión corre. Los servicios de este tipo declaran
`version: "0.0.0"` en su `package.json`, no exponen commit ni fecha de build, y no tienen
endpoint de versión. Preguntar «¿está actualizado?» no tiene respuesta, y la que se da es
un recuerdo.

El agente responde con **evidencia etiquetada**: cada dato de build viene con su origen y su
nivel de confianza, y esa etiqueta nunca sube de tono al resumirse. Una herramienta de
diagnóstico que exagera lo que sabe es una que miente. Los límites están escritos, uno a uno,
en [`docs/DIAGNOSTICO.md`](docs/DIAGNOSTICO.md) — es el documento que más importa de este repo.

## Arranque en 10 minutos

Requiere Node ≥ 22.19 y pm2 en la máquina vigilada.

```bash
git clone <este-repo> C:\agentes\mcp-despliegue-local
cd C:\agentes\mcp-despliegue-local
npm install
```

1. **Descriptor.** Copia `ejemplos/servicios.ejemplo.mjs` a tu repo privado (nunca a este) y
   describe tus servicios. Empieza por uno solo, con `sonda: { tipo: 'ninguna' }`.
   Esquema completo en [`docs/DESCRIPTORES.md`](docs/DESCRIPTORES.md).

2. **Entorno.** `cp env.ejemplo .env` y rellena:

   ```
   DESPLIEGUE_LOCAL_TOKEN=<32 caracteres o más, generado al azar>
   DESPLIEGUE_LOCAL_DESCRIPTORES=D:\servicios\configuracion\servicios.mjs
   ```

   Genera el token con `node -p "require('crypto').randomBytes(32).toString('hex')"`.
   Con menos de 32 caracteres el agente **sale con código 1** y te dice por qué.

3. **Arranca bajo pm2.**

   ```bash
   pm2 start ecosystem.config.cjs
   pm2 save
   ```

4. **Verifica sin un modelo delante.** `/salud` no pide token y no devuelve datos:

   ```bash
   curl http://127.0.0.1:7717/salud
   ```

   ```json
   { "ok": true, "contrato": 1, "version": "0.1.0", "arrancadoEn": "2026-09-08T09:12:44.001Z",
     "expuesto": false, "descriptores": { "cargados": 4, "invalidos": 0, "avisos": 1 } }
   ```

5. **Registra el MCP** en el consumidor. No hace falta OAuth ni un puente stdio: el agente
   **es** el servidor MCP por Streamable HTTP y `.mcp.json` admite cabeceras estáticas con
   expansión de variables de entorno.

   ```json
   { "mcpServers": { "planta": {
     "type": "http",
     "url": "https://agente.example.com/mcp",
     "headers": { "Authorization": "Bearer ${TOKEN_DESPLIEGUE_LOCAL}" }
   } } }
   ```

   Con `headers` puesto, un 401 se reporta como fallo de conexión y **no** dispara el flujo OAuth.

El runbook completo — proxy inverso, servicio de arranque, verificación paso a paso — está en
[`docs/INSTALACION.md`](docs/INSTALACION.md).

## Salida de ejemplo

`estado_de_los_servicios` es la primera llamada de cualquier diagnóstico y casi siempre la
única que hace falta:

```
4 servicios descritos, 3 en línea.

inventario     en línea    3 d 4 h    build 2026-09-04 (sello.json, confianza alta)
               sonda http /contrato → 200 en 41 ms. Alcance: http.

etiquetas      en línea    3 d 4 h    build 2026-09-04 (nitro.json, confianza media)
               sonda http /api/etiqueta → 400 en 18 ms, esperado. Alcance: proceso:
               contesta, pero esto no prueba que emita etiquetas.

telemetria     en línea      41 min   build 2026-09-08 (mtime de entrada, confianza BAJA —
               probablemente la fecha del copiado, no la del build).
               sonda websocket → 101. Alcance: proceso.
               AVISO: el archivo de configuración en disco no es el que el proceso
               tiene cargado. Divergen: NIVEL_LOG, INTERVALO_MS. Remedio:
               pm2 startOrRestart, no pm2 restart.

pedidos        detenido    —          build 2026-08-27 (nitro.json, confianza media)
               Sin sonda por decisión del descriptor: solo tiene endpoints de negocio
               que exigen base viva, así que un 200 no probaría nada.
               Señales en disco: datos/cola.jsonl, 0 bytes, sin cambios desde hace 6 d.
```

Nada de eso es JSON crudo por accidente: toda salida de una herramienta MCP es prosa en
español ya traducida a una acción.

## Las cinco herramientas MCP

| Herramienta | Qué responde |
|---|---|
| `estado_de_los_servicios` | Pase de lista. La primera llamada, y casi siempre la única |
| `detalle_de_servicio` | Expediente de uno: build con su evidencia, proceso pm2, rutas, señales, protegidos |
| `configuracion_de_servicio` | Qué configuración tiene cargada, y si el proceso arrancó con esa o con otra |
| `bitacora_de_servicio` | Últimas N líneas del log (def. 50, tope duro 500, filtro aplicado en el servidor) |
| `estado_del_agente` | Distingue «el agente no corre» de «el agente corre pero no sabe que ese servicio existe» |

## Documentación

| Documento | Para quién |
|---|---|
| [`docs/ARQUITECTURA.md`](docs/ARQUITECTURA.md) | Quien va a leer o escribir el código |
| [`docs/DESCRIPTORES.md`](docs/DESCRIPTORES.md) | Quien describe sus servicios. Esquema campo a campo |
| [`docs/INSTALACION.md`](docs/INSTALACION.md) | Quien lo instala en el servidor. Runbook |
| [`docs/DIAGNOSTICO.md`](docs/DIAGNOSTICO.md) | Quien lee las respuestas. **Los límites de lo que el agente sabe** |
| [`docs/SEGURIDAD.md`](docs/SEGURIDAD.md) | Quien tiene que aprobar poner esto en una red industrial |
| [`docs/V1-DESPLIEGUE.md`](docs/V1-DESPLIEGUE.md) | Quien implemente el despliegue. Diseñado, no construido |
| [`CLAUDE.md`](CLAUDE.md) | Quien edite este repo, humano o asistente |

## Estado

**v0 implementado.** Los dieciséis módulos de `src/` están escritos, con 115 tests unitarios
y una prueba de aceptación de 41 comprobaciones que corre contra pm2 de verdad y un servicio
de juguete.

```bash
npm test                      # 115 tests unitarios
npm run utiles:aceptacion     # 41 comprobaciones contra pm2 real (monta y borra su banco)
npm run verificar             # higiene pública con historial + tests. Antes de publicar
```

La prueba que decide si esto sirve está en `utiles/prueba-de-aceptacion.mjs`: cambia una
variable, hace `pm2 restart` —el comando equivocado— y comprueba que el agente **detecta la
divergencia**; luego `pm2 startOrRestart` y comprueba que desaparece.

El despliegue (v1) sigue **diseñado por escrito y sin implementar**, en
[`docs/V1-DESPLIEGUE.md`](docs/V1-DESPLIEGUE.md).

## Licencia

MIT. Ver [`LICENSE`](LICENSE).