Serencp

Visor de consola serie de VM

Documentación

SERENCP - Servidor MCP de Consola de VM y Guía de Uso del Visor en Vivo

(probado con QEMU/KVM/virt-manager y OpenCode)

Resumen

El script serencp.pl proporciona un servidor MCP (Model Context Protocol) estándar para comunicación bidireccional con consolas seriales de VM a través de un puente de sockets interno basado en Perl.

Utiliza IO::Pty para crear un pseudo-terminal (PTY) para la consola serial de la VM. Gestiona múltiples VMs asignando puertos TCP únicos para la comunicación y proporciona un bucle de eventos multiplexado de alto rendimiento.

Características Principales:

  • PTY Persistente: Mantiene una conexión estable con la consola serial de la VM.
  • Reinicio Automático con Retroceso Exponencial: Detecta automáticamente desconexiones de la VM y reinicia el puente con retroceso exponencial inteligente (1s inicial, 60s máximo) para prevenir tormentas de reconexión rápidas.
  • Búfer Circular: Mantiene un búfer circular de las últimas 1000 líneas de salida (máximo 10MB por VM).
  • Acceso Multi-Cliente: Soporta múltiples clientes simultáneos mediante sockets Unix dedicados para entrada (/tmp/serial_${VM_NAME}.in) y salida (/tmp/serial_${VM_NAME}.out).
  • MCP Estándar: Soporta métodos estándar tools/list y tools/call para descubrimiento y ejecución de herramientas.
  • Suscripciones a Recursos: Controla notificaciones de salida de VM en vivo usando métodos estándar de MCP resources/subscribe y resources/unsubscribe.
  • Reaprovechamiento de Procesos: El manejador SIGCHLD integrado previene procesos zombis de forks.
  • Sistema de Escritura No Bloqueante: Escrituras verdaderamente no bloqueantes con cola de búfer opcional para entrega confiable de datos.
  • Niveles de Registro Configurables: Niveles de registro de depuración, información y error con filtrado basado en prioridad.
  • Notificaciones de Progreso: Soporta notificaciones de progreso de MCP para operaciones de larga duración.
  • Limpieza Robusta: El bloque END asegura una limpieza adecuada en salidas anormales (crash, _exit, die).

Requisitos Previos

  • Sistema Operativo: Requiere estrictamente un sistema tipo *nix (Linux, macOS, BSD, etc.). Windows NO es compatible (excepto mediante WSL).
  • Módulos Perl: Se requieren los siguientes módulos no estándar:
    • IO::Pty - Creación de pseudo-terminal
  • VM ejecutándose con consola serial en un puerto TCP (por defecto comienza en 4555).
  • No requiere permisos de root.
  • Característica de Terminal Automática: Requiere un emulador de terminal compatible para abrir automáticamente una ventana. Utiliza un modo cliente interno y no requiere socat.
  • Opciones de Línea de Comandos:
    • --socket <path>: Ejecutar en modo cliente de socket Unix (conectar al puente existente) (no te preocupes por esto a menos que estés en un entorno no MCP)
    • --terminal <name>: Especificar el emulador de terminal preferido a usar (omite la detección automática)

Emuladores de Terminal Compatibles

El script detecta y soporta automáticamente los siguientes emuladores de terminal (probados para disponibilidad en tiempo de ejecución):

macOS (Prioridad):

  • Ghostty.app (acelerado por GPU)
  • WezTerm.app
  • iTerm.app / iTerm2.app
  • Terminal.app (terminal integrado de macOS)

Linux/Unix Moderno:

  • wezterm (multiplataforma)
  • kitty (acelerado por GPU, probado)
  • alacritty (acelerado por GPU)
  • ghostty (acelerado por GPU moderno)
  • foot (terminal Wayland)

Nivel Medio:

  • konsole (KDE)
  • gnome-terminal (GNOME)
  • tilix (terminal de mosaico GTK3)
  • terminator (terminal de mosaico avanzado)
  • xfce4-terminal (escritorio XFCE)

Legado:

  • xterm (terminal X11 clásico)
  • urxvt (rxvt Unicode)

El script utiliza un sistema de detección basado en prioridad:

  1. Primero honra la opción explícita del usuario --terminal
  2. Luego verifica la variable de entorno TERM_PROGRAM (macOS/VSCode/Warp/Hyper)
  3. Luego verifica la variable de entorno TERMINAL
  4. Finalmente prueba terminales en orden de prioridad hasta que uno funcione

La detección ahora realiza un lanzamiento de prueba para verificar que el terminal realmente pueda abrirse antes de seleccionarlo, asegurando un lanzamiento de terminal más confiable. Si no se detecta ningún terminal, proporciona mecanismos de respaldo y notificaciones de error. Siempre prioriza los mejores emuladores de terminal gráficos.

Configuración

Constantes Predeterminadas (Versión 1.1)

  • Puerto VM Predeterminado: 4555
  • Tamaño del Búfer Circular: 1000 líneas
  • Máximo de Bytes del Búfer: 10MB por VM
  • Líneas de Historial de Consola: 60 líneas (enviadas a nuevos clientes)
  • Tiempo de Espera de Lectura: 2 segundos (interno, para la herramienta legada read)
  • Retroceso de Reinicio: Inicial 1s, Máximo 60s (retroceso exponencial)
  • Búfer de Escritura: Máximo 1MB por destino
  • Versión del Protocolo: 2025-06-18

Configuración del Servidor MCP

Asegúrate de que el servidor MCP esté configurado en opencode.jsonc:

"mcp": {
    "serencp": {
        "type": "local",
        "command": ["perl", "/path/to/serencp.pl"],
        "enabled": true
    }
}

También puedes especificar un terminal explícitamente:

"mcp": {
    "serencp": {
        "type": "local",
        "command": ["perl", "/path/to/serencp.pl","--terminal","wezterm"],
        "enabled": true
    }
}

Servidor HTTP Experimental (http_experimental.pl)

http_experimental.pl es un servidor MCP Streamable HTTP experimental (v2.0) que expone las mismas 5 herramientas (start, stop, status, read, write) sobre HTTP en lugar de stdio. Escucha por defecto en http://127.0.0.1:8080/mcp, usa Server-Sent Events (SSE) para transmitir notificaciones de salida de VM en vivo (pero aún necesita la herramienta 'read' para obtener después de cada llamada a la herramienta 'write' (limitación actual, necesita más investigación)), y soporta suscripciones a recursos basadas en sesión con CORS habilitado. Lánzalo con:

./http_experimental.pl &

Luego regístralo como un servidor MCP remoto:

Ejemplo de Opencode (~/.opencode/opencode.jsonc)

"mcp": {
    "serencp": {
        "type": "remote",
        "url": "http://127.0.0.1:8080/mcp",
        "enabled": true
    }
}

Asegúrate de que tu sistema operativo invitado esté configurado para usar la consola serial.

Ejemplo de GRUB:

GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0,115200n8"

Ejemplo de /etc/inittab:

T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100

Ejemplo de systemd:

systemctl enable serial-getty@ttyS0.service
systemctl start serial-getty@ttyS0.service

Ejemplo de configuración XML de QEMU/KVM:

<serial type="tcp">
  <source mode="bind" host="127.0.0.1" service="4555" tls="no"/>
  <protocol type="raw"/>
  <target type="isa-serial" port="0">
    <model name="isa-serial"/>
  </target>
  <alias name="serial0"/>
</serial>

Métodos MCP Estándar

tools/list

Lista todas las herramientas disponibles.

  • Solicitud: {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
  • Respuesta: Lista de herramientas con sus esquemas de entrada.

tools/call

Ejecuta una herramienta específica.

  • Formato de Solicitud:
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "tools/call",
      "params": {
        "name": "tool_name",
        "arguments": { ... }
      }
    }
    

Notificaciones de Salida en Vivo

El servidor soporta transmisión de salida de VM en tiempo real a través de notificaciones de protocolo MCP y suscripciones a recursos. Esto proporciona retroalimentación inmediata sin necesidad de sondeo.

El cliente MCP debe usar el método estándar de MCP resources/subscribe para comenzar a recibir notificaciones de salida de VM, y resources/unsubscribe para dejar de recibirlas.

Notificaciones de Salida de VM

La salida de VM se transmite automáticamente como notificaciones JSON-RPC 2.0 usando el patrón de recursos MCP:

{
    "jsonrpc": "2.0",
    "method": "notifications/resources/updated",
    "params": {
        "uri": "vm://<vm_name>/output",
        "content": "output data here",
        "stream": "stdout"
    }
}

Parámetros de Notificación

  • uri: URI del recurso en el formato vm://<vm_name>/output
  • content: Los datos de salida reales (seguro para UTF-8)
  • stream: "stdout" o "stderr"

Beneficios

  • Retroalimentación en Tiempo Real: La salida de VM aparece inmediatamente sin sondeo
  • Eficiente: El modelo basado en push reduce la sobrecarga comparado con el sondeo
  • Seguro para UTF-8: Los datos binarios se convierten a UTF-8 con representaciones escapadas para caracteres no imprimibles
  • Retrocompatible: La herramienta existente read continúa funcionando para acceso basado en pull
  • Patrón de Recursos MCP: Usa notifications/resources/updated estandarizado para la salida de VM
  • Acceso Basado en URI: La salida de VM es accesible a través del URI de recurso vm://<vm_name>/output

Control de Nivel de Registro

El servidor soporta niveles de registro configurables: debug, info y error. Por defecto, el registro de depuración está habilitado.

Herramientas Disponibles

Todas las herramientas incluyen anotaciones MCP mejoradas para mejor integración de UI (título, readOnlyHint, destructiveHint, idempotentHint, openWorldHint).

El servidor proporciona 5 herramientas para la gestión de la consola serial de VM. Las notificaciones de salida de VM en vivo se controlan mediante métodos estándar de MCP resources/subscribe y resources/unsubscribe (no herramientas separadas).

1. start

Inicia el puente para una VM específica. Si ya existe un puente, se reinicia para asegurar un estado limpio con estado de retroceso exponencial fresco. Comportamiento nuevo: Abre automáticamente una ventana de terminal gráfica vinculada a la sesión usando el cliente interno de serencp.pl. El PID de este terminal se almacena para evitar ventanas duplicadas.

  • Argumentos: {"vm_name": "string", "port": "number"} (el puerto es opcional, predeterminado: 4555)
  • Devuelve: {"success": true, "message": "...", "port": 4555, "socket_in": "/tmp/serial_VM_NAME.in", "socket_out": "/tmp/serial_VM_NAME.out", "session_id": "session_...", "terminal_pid": 1234}
  • Ejemplo: tools/call {"name": "start", "arguments": {"vm_name": "MYVM", "port": 4555}}
  • Anotaciones: No destructiva, idempotente, mundo abierto

2. status

Verifica el estado del puente.

  • Argumentos: {"vm_name": "string"}
  • Devuelve: {"running": true/false, "vm_name": "...", "port": ..., "buffer_size": ...}
  • Anotaciones: Solo lectura, mundo cerrado

3. read

Lee toda la salida disponible del socket Unix de salida dedicado de la consola serial de la VM con un tiempo de espera de 2 segundos. La salida en vivo también se transmite mediante notificaciones.

  • Argumentos: {"vm_name": "string"}
  • Devuelve: {"success": true, "output": "..."}
  • Anotaciones: Solo lectura, mundo abierto

4. write

Envía un comando a la consola serial de la VM a través de su socket Unix de entrada dedicado.

  • Argumentos: {"vm_name": "string", "text": "command"}
  • Devuelve: {"success": true/false, "message": "..."}
  • Ejemplo: tools/call {"name": "write", "arguments": {"vm_name": "MYVM", "text": "ls -l /"}}
  • Anotaciones: No destructiva, no idempotente, mundo abierto

5. stop

Detiene el puente para una VM específica, limpiando todos los PTYs, procesos hijos y sockets Unix temporales.

  • Argumentos: {"vm_name": "string"}
  • Devuelve: {"success": true/false, "message": "..."}
  • Anotaciones: Destructiva (detiene el puente), idempotente, mundo cerrado

Arquitectura

El script se conecta a la consola serial de la VM como cliente y proporciona dos servidores de socket Unix: uno para entrada en /tmp/serial_${VM_NAME}.in y uno para salida en /tmp/serial_${VM_NAME}.out. Soporta tanto un modo cliente de socket Unix interno como la apertura automática de terminal. El servidor MCP maneja comandos y respuestas JSON-RPC mediante notificaciones compatibles con MCP.

Reinicio con Retroceso Exponencial

Cuando una VM se desconecta, el puente ahora usa retroceso exponencial para prevenir tormentas de reconexión:

  • Retroceso inicial: 1 segundo
  • Retroceso máximo: 60 segundos
  • Seguimiento de estado por VM: Cada VM mantiene su propio temporizador de retroceso
  • El retroceso se reinicia en conexión exitosa

Sistema de Escritura No Bloqueante

La versión 1.1 introduce un sistema de escritura verdaderamente no bloqueante con tres modos:

  • Modo 0 (No Bloqueante Puro): Devuelve inmediatamente si se bloquearía
  • Modo 1 (Con Búfer): Pone en cola los datos si no puede escribir inmediatamente
  • Modo 2 (Legado): Reintenta con tiempo de espera (predeterminado para retrocompatibilidad)
  • Búfer de escritura máximo: 1MB por destino
  • Vaciado automático del búfer en el bucle de eventos

Manejo Mejorado de UTF-8

  • Los datos binarios de la VM se convierten a UTF-8 de forma segura
  • Los caracteres no imprimibles se escapan para el transporte JSON
  • Preserva la integridad de los datos mientras asegura la compatibilidad JSON

Modo Cliente de Socket Unix Interno

El script puede ejecutarse en modo cliente para conectarse a un puente existente:

./serencp.pl --socket /tmp/serial_${VM_NAME}.out

Este modo proporciona acceso directo al terminal de la consola serial de la VM a través de la interfaz de socket Unix.

Selección Explícita de Terminal

Puedes especificar explícitamente qué terminal usar:

./serencp.pl --terminal wezterm

Esto omite la detección automática y usa el terminal especificado.

graph TD
    VM["VM Serial Console (TCP:127.0.0.1:4555+)"]
    Bridge["Perl Bridge Child (Forked)"]
    PTY["Pseudo-Terminal (PTY Master)"]
    MCP["serencp MCP Server (Main Event Loop)"]
    Client["MCP Client (LLM / Opencode)"]
    UnixIn["Unix Input Socket (/tmp/serial_VM_NAME.in)"]
    UnixOut["Unix Output Socket (/tmp/serial_VM_NAME.out)"]
    ScriptClient["serencp.pl --socket /tmp/serial_VM_NAME.out"]
    ExtClients["External Clients (optional)"]
    LiveTerminal["Live Terminal View (Auto-Spawned)"]

    VM <--> Bridge
    Bridge <--> PTY
    PTY <--> MCP
    MCP <--> Client
    MCP -.-> UnixIn
    MCP -.-> UnixOut
    UnixOut <--> ScriptClient
    UnixOut <--> LiveTerminal
    UnixOut -.-> ExtClients
    ScriptClient --> UnixIn
    LiveTerminal --> UnixIn
    ExtClients --> UnixIn

El servidor MCP padre usa IO::Select para multiplexar:

  1. STDIN: Comandos JSON-RPC del LLM o Opencode.
  2. PTY Master: Datos en tiempo real desde/hacia la VM a través del puente hijo.
  3. Unix Input/Output Sockets: Oyentes para conexiones de terminal externas.
  4. Unix Clients: Sesiones de terminal activas conectadas a los sockets Unix.

Cuando la VM se desconecta, el padre detecta el cierre del PTY y reinicia automáticamente el proceso hijo del puente para mantener la persistencia.

Diagrama de Secuencia

sequenceDiagram
autonumber
participant VM as VM (serial console)
participant TCP as IO::Socket::INET (TCP 127.0.0.1:port)
participant Bridge as Bridge process (child)
participant PTY as IO::Pty (master/slave)
participant MCP as MCP server (parent)
participant USockIn as IO::Socket::UNIX (/tmp/serial_<vm>.in)
participant USockOut as IO::Socket::UNIX (/tmp/serial_<vm>.out)
participant Term as Terminal client

%% Initial connection
MCP->>Bridge: fork() + PTY creation
Bridge->>TCP: TCP connection to the VM's serial port
TCP-->>Bridge: OK (socket connected)
Bridge-->>MCP: READY via pipe

%% VM -> user flow
VM-->>TCP: Serial output (bytes)
TCP-->>Bridge: Raw data
Bridge-->>PTY: Write into PTY slave
PTY-->>MCP: Data read from PTY master
MCP->>MCP: Buffer ring + JSON stdout notification
MCP-->>USockOut: Make data available to output clients

Term-->>USockOut: Unix output socket connection (read-only)
USockOut-->>MCP: New connection accepted()
MCP-->>Term: History + live stream

%% User -> VM flow
Term->>USockIn: Unix input socket connection (write-only)
USockIn-->>MCP: New connection accepted()
Term->>USockIn: Keyboard input (command)
USockIn->>MCP: Client data
MCP-->>PTY: Write into PTY master
PTY-->>Bridge: Data read from PTY slave
Bridge-->>TCP: Write to TCP socket
TCP-->>VM: Command received on the serial console

Acceso al Terminal

Para interacción directa fuera del entorno MCP, puedes usar el script mismo como cliente especificando el socket de salida:

./serencp.pl --socket /tmp/serial_${VM_NAME}.out

Las nuevas conexiones de salida reciben automáticamente las últimas 60 líneas de historial. Las notificaciones de salida en vivo se envían automáticamente cuando se reciben datos de la VM, proporcionando transmisión en tiempo real sin sondeo. Para enviar entrada, el cliente abre y escribe automáticamente en el socket de entrada correspondiente (/tmp/serial_${VM_NAME}.in).

Solución de problemas

  • No se abre ninguna ventana de terminal: Si el lanzamiento automático de terminal falla, primero verifica si las aplicaciones gráficas se pueden lanzar desde una terminal de root (p. ej.: pluma) Luego verifica que las siguientes variables de entorno estén configuradas correctamente:

    • XAUTHORITY: Requerida para la autenticación X11 (p. ej., /root/.Xauthority)
    • XDG_RUNTIME_DIR: Debe estar configurada con el directorio de ejecución del usuario (p. ej., /run/user/0)
    • DBUS_SESSION_BUS_ADDRESS: Requerida para la comunicación de sesión D-Bus

    Para solucionar estos problemas, ejecuta el script desde una sesión X11 adecuada donde estas variables se configuren automáticamente, o expórtalas manualmente:

    export XAUTHORITY=/root/.Xauthority
    export XDG_RUNTIME_DIR=/run/user/0
    
  • Falla la detección de terminal: Usa la opción --terminal para especificar explícitamente tu emulador de terminal:

    ./serencp.pl --terminal wezterm
    
  • No se pudieron obtener las herramientas: Asegúrate de que el script se ejecute en un entorno donde se capture la entrada/salida estándar. Usa tools/list para verificar la conectividad.

  • El puente no está en ejecución: Llama a start antes de intentar leer o escribir.

  • Sin notificaciones en vivo: Asegúrate de que tu cliente MCP admita el manejo de notificaciones. Las notificaciones se envían automáticamente cuando se recibe la salida de la VM. Usa el método estándar de MCP resources/subscribe para suscribirte a los recursos vm://<vm_name>/output.

  • Permiso de socket: Asegúrate de que /tmp sea escribible por el usuario que ejecuta el servidor MCP.

  • Verificación de sintaxis: Ejecuta perl -c serencp.pl para verificar la integridad del script.

  • El servidor MCP no pudo iniciarse: Verifica que todos los módulos Perl requeridos estén cargados. Ejecuta perl -c serencp.pl para verificar la sintaxis y la carga de módulos. Si ves "Can't locate ... in @INC", instala el módulo faltante (p. ej., cpan IO::Pty para módulos no incluidos en el núcleo).

  • Búfer de escritura lleno: Si ves advertencias de "Write buffer full", el destino no está manteniendo el ritmo de los datos. Esto es normal en escenarios de alto rendimiento y los datos se descartarán.

  • Retroceso exponencial activo: Si el puente se reinicia constantemente, verás retrasos crecientes entre los intentos de reconexión (1s, 2s, 4s... hasta 60s). Esto es intencional para evitar tormentas de conexiones.

Acerca de

El nombre serencp es un juego de palabras:

  • seren - Serenidad / Serial
  • cp - MCP (Model Context Protocol)

Siéntete libre de contribuir

¡Como es un script complejo, tu ayuda / pull requests son muy apreciados!