Serencp

Visualizador de console serial de VM

Documentação

SERENCP - Guia de Uso do Servidor MCP de Console de VM e Visualizador ao Vivo

(testado com QEMU/KVM/virt-manager e OpenCode)

Visão Geral

O script serencp.pl fornece um servidor MCP (Model Context Protocol) padrão para comunicação bidirecional com consoles seriais de VMs através de uma ponte de socket interna baseada em Perl.

Ele usa IO::Pty para criar um pseudo-terminal (PTY) para o console serial da VM. Ele gerencia múltiplas VMs atribuindo portas TCP exclusivas para comunicação e fornece um loop de eventos multiplexado de alto desempenho.

Principais Recursos:

  • PTY Persistente: Mantém uma conexão estável com o console serial da VM.
  • Reinício Automático com Backoff Exponencial: Detecta automaticamente desconexões da VM e reinicia a ponte com backoff exponencial inteligente (1s inicial, 60s máximo) para evitar tempestades de reconexão.
  • Buffer Circular: Mantém um buffer circular das últimas 1000 linhas de saída (máximo de 10MB por VM).
  • Acesso Multi-Cliente: Suporta múltiplos clientes simultâneos via sockets Unix dedicados para entrada (/tmp/serial_${VM_NAME}.in) e saída (/tmp/serial_${VM_NAME}.out).
  • MCP Padrão: Suporta métodos padrão tools/list e tools/call para descoberta e execução de ferramentas.
  • Assinaturas de Recursos: Controla notificações ao vivo de saída da VM usando métodos padrão MCP resources/subscribe e resources/unsubscribe.
  • Reaproveitamento de Processos: Manipulador SIGCHLD integrado previne processos zumbis de forks.
  • Sistema de Escrita Não-Bloqueante: Escritas verdadeiramente não-bloqueantes com fila de buffer opcional para entrega confiável de dados.
  • Níveis de Log Configuráveis: Níveis de log debug, info e erro com filtragem baseada em prioridade.
  • Notificações de Progresso: Suporta notificações de progresso MCP para operações de longa duração.
  • Limpeza Robusta: Bloco END garante limpeza adequada em saída anormal (crash, _exit, die).

Pré-requisitos

  • Sistema Operacional: Requer estritamente um sistema tipo *nix (Linux, macOS, BSD, etc.). Windows NÃO é suportado (exceto via WSL).
  • Módulos Perl: Os seguintes módulos não-núcleo são necessários:
    • IO::Pty - Criação de pseudo-terminal
  • VM rodando com console serial em uma porta TCP (padrão inicia em 4555).
  • Não requer permissões de root.
  • Recurso de Terminal Automático: Requer um emulador de terminal suportado para abrir automaticamente uma janela. Usa um modo cliente interno e não requer socat.
  • Opções de Linha de Comando:
    • --socket <path>: Executar em modo cliente de socket Unix (conectar à ponte existente) (não se preocupe com isso a menos que esteja em ambiente não-MCP)
    • --terminal <name>: Especificar emulador de terminal preferido (ignora a detecção automática)

Emuladores de Terminal Suportados

O script detecta e suporta automaticamente os seguintes emuladores de terminal (testados quanto à disponibilidade em tempo de execução):

macOS (Prioridade):

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

Linux/Unix Moderno:

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

Nível intermediário:

  • konsole (KDE)
  • gnome-terminal (GNOME)
  • tilix (terminal de tiling GTK3)
  • terminator (terminal de tiling avançado)
  • xfce4-terminal (desktop XFCE)

Legado:

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

O script usa um sistema de detecção baseado em prioridade:

  1. Primeiro honra a opção explícita do usuário --terminal
  2. Depois verifica a variável de ambiente TERM_PROGRAM (macOS/VSCode/Warp/Hyper)
  3. Depois verifica a variável de ambiente TERMINAL
  4. Finalmente tenta terminais em ordem de prioridade até que um funcione

A detecção agora realiza um teste de inicialização para verificar se o terminal pode realmente abrir antes de selecioná-lo, garantindo uma abertura de terminal mais confiável. Se nenhum terminal for detectado, fornece mecanismos de fallback e notificações de erro. Sempre prioriza os melhores emuladores de terminal gráficos.

Configuração

Constantes Padrão (Versão 1.1)

  • Porta VM Padrão: 4555
  • Tamanho do Buffer Circular: 1000 linhas
  • Máximo de Bytes do Buffer: 10MB por VM
  • Linhas de Histórico do Console: 60 linhas (enviadas a novos clientes)
  • Timeout de Leitura: 2 segundos (interno, para a ferramenta legada read)
  • Backoff de Reinício: Inicial 1s, Máximo 60s (backoff exponencial)
  • Buffer de Escrita: Máximo de 1MB por destino
  • Versão do Protocolo: 2025-06-18

Configuração do Servidor MCP

Certifique-se de que o servidor MCP está configurado em opencode.jsonc:

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

Você também pode especificar um terminal explicitamente:

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

Servidor HTTP Experimental (http_experimental.pl)

http_experimental.pl é um servidor MCP Streamable HTTP experimental (v2.0) que expõe as mesmas 5 ferramentas (start, stop, status, read, write) via HTTP em vez de stdio. Ele escuta por padrão em http://127.0.0.1:8080/mcp, usa Server-Sent Events (SSE) para transmitir notificações ao vivo de saída da VM (mas ainda precisa da ferramenta 'read' após cada chamada à ferramenta 'write' (limitação atual, precisa de mais investigação)), e suporta assinaturas de recursos baseadas em sessão com CORS habilitado. Inicie com:

./http_experimental.pl &

Depois registre-o como um servidor MCP remoto:

Exemplo OpenCode (~/.opencode/opencode.jsonc)

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

Certifique-se de que seu sistema operacional convidado está configurado para usar o console serial.

Exemplo GRUB:

GRUB_CMDLINE_LINUX_DEFAULT="console=ttyS0,115200n8"

Exemplo /etc/inittab:

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

Exemplo systemd:

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

Exemplo de configuração XML 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 Padrão

tools/list

Lista todas as ferramentas disponíveis.

  • Solicitação: {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
  • Resposta: Lista de ferramentas com seus esquemas de entrada.

tools/call

Executa uma ferramenta específica.

  • Formato da Solicitação:
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "tools/call",
      "params": {
        "name": "tool_name",
        "arguments": { ... }
      }
    }
    

Notificações de Saída ao Vivo

O servidor suporta transmissão em tempo real da saída da VM através de notificações de protocolo MCP e assinaturas de recursos. Isso fornece feedback imediato sem necessidade de polling.

O cliente MCP deve usar o método padrão MCP resources/subscribe para começar a receber notificações de saída da VM, e resources/unsubscribe para parar de recebê-las.

Notificações de Saída da VM

A saída da VM é transmitida automaticamente como notificações JSON-RPC 2.0 usando o padrão de recursos MCP:

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

Parâmetros da Notificação

  • uri: URI do recurso no formato vm://<vm_name>/output
  • content: Os dados de saída reais (seguros para UTF-8)
  • stream: "stdout" ou "stderr"

Benefícios

  • Feedback em Tempo Real: A saída da VM aparece imediatamente sem polling
  • Eficiente: Modelo baseado em push reduz sobrecarga comparado ao polling
  • Seguro para UTF-8: Dados binários são convertidos para UTF-8 com representações escapadas para caracteres não imprimíveis
  • Compatível com Versões Anteriores: A ferramenta existente read continua funcionando para acesso baseado em pull
  • Padrão de Recursos MCP: Usa notifications/resources/updated padronizado para saída da VM
  • Acesso Baseado em URI: Saída da VM acessível via URI de recurso vm://<vm_name>/output

Controle de Nível de Log

O servidor suporta níveis de log configuráveis: debug, info e error. Por padrão, o log de debug está habilitado.

Ferramentas Disponíveis

Todas as ferramentas incluem anotações MCP aprimoradas para melhor integração com a interface do usuário (title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint).

O servidor fornece 5 ferramentas para gerenciamento do console serial da VM. Notificações ao vivo de saída da VM são controladas via métodos padrão MCP resources/subscribe e resources/unsubscribe (não ferramentas separadas).

1. start

Inicia a ponte para uma VM específica. Se uma ponte já existir, ela é reiniciada para garantir um estado limpo com estado de backoff exponencial renovado. Novo comportamento: Abre automaticamente uma janela de terminal gráfico vinculada à sessão usando o cliente interno de serencp.pl. O PID deste terminal é armazenado para evitar janelas duplicadas.

  • Argumentos: {"vm_name": "string", "port": "number"} (porta é opcional, padrão: 4555)
  • Retorna: {"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}
  • Exemplo: tools/call {"name": "start", "arguments": {"vm_name": "MYVM", "port": 4555}}
  • Anotações: Não-destrutiva, idempotente, mundo aberto

2. status

Verifica o status da ponte.

  • Argumentos: {"vm_name": "string"}
  • Retorna: {"running": true/false, "vm_name": "...", "port": ..., "buffer_size": ...}
  • Anotações: Somente leitura, mundo fechado

3. read

Lê toda a saída disponível do socket Unix de saída dedicado do console serial da VM com timeout de 2 segundos. A saída ao vivo também é transmitida via notificações.

  • Argumentos: {"vm_name": "string"}
  • Retorna: {"success": true, "output": "..."}
  • Anotações: Somente leitura, mundo aberto

4. write

Envia um comando ao console serial da VM via seu socket Unix de entrada dedicado.

  • Argumentos: {"vm_name": "string", "text": "command"}
  • Retorna: {"success": true/false, "message": "..."}
  • Exemplo: tools/call {"name": "write", "arguments": {"vm_name": "MYVM", "text": "ls -l /"}}
  • Anotações: Não-destrutiva, não-idempotente, mundo aberto

5. stop

Para a ponte de uma VM específica, limpando todos os PTYs, processos filhos e sockets Unix temporários.

  • Argumentos: {"vm_name": "string"}
  • Retorna: {"success": true/false, "message": "..."}
  • Anotações: Destrutiva (para a ponte), idempotente, mundo fechado

Arquitetura

O script conecta-se ao console serial da VM como cliente e fornece dois servidores de socket Unix: um para entrada em /tmp/serial_${VM_NAME}.in e um para saída em /tmp/serial_${VM_NAME}.out. Suporta tanto um modo cliente de socket Unix interno quanto abertura automática de terminal. O servidor MCP manipula comandos JSON-RPC e respostas via notificações compatíveis com MCP.

Reinício com Backoff Exponencial

Quando uma VM desconecta, a ponte agora usa backoff exponencial para prevenir tempestades de reconexão:

  • Backoff inicial: 1 segundo
  • Backoff máximo: 60 segundos
  • Rastreamento de estado por VM: Cada VM mantém seu próprio temporizador de backoff
  • Backoff é redefinido em conexão bem-sucedida

Sistema de Escrita Não-Bloqueante

A Versão 1.1 introduz um sistema de escrita verdadeiramente não-bloqueante com três modos:

  • Modo 0 (Não-Bloqueante Puro): Retorna imediatamente se houver bloqueio
  • Modo 1 (Com Buffer): Enfileira dados se não puder escrever imediatamente
  • Modo 2 (Legado): Tenta novamente com timeout (padrão para compatibilidade com versões anteriores)
  • Buffer de escrita máximo: 1MB por destino
  • Liberação automática do buffer no loop de eventos

Manipulação Aprimorada de UTF-8

  • Dados binários da VM são convertidos para UTF-8 com segurança
  • Caracteres não imprimíveis são escapados para transporte JSON
  • Preserva a integridade dos dados garantindo compatibilidade JSON

Modo Cliente de Socket Unix Interno

O script pode ser executado em modo cliente para conectar a uma ponte existente:

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

Este modo fornece acesso direto ao terminal do console serial da VM através da interface de socket Unix.

Seleção Explícita de Terminal

Você pode especificar explicitamente qual terminal usar:

./serencp.pl --terminal wezterm

Isso ignora a detecção automática e usa o 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

O servidor MCP pai usa IO::Select para multiplexar:

  1. STDIN: Comandos JSON-RPC do LLM ou OpenCode.
  2. PTY Master: Dados em tempo real de/para a VM via ponte filha.
  3. Unix Input/Output Sockets: Ouvintes para conexões de terminal externas.
  4. Unix Clients: Sessões de terminal ativas conectadas aos sockets Unix.

Quando a VM desconecta, o pai detecta o fechamento do PTY e reinicia automaticamente o processo filho da ponte para manter a persistência.

Diagrama de Sequência

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

Acesso ao Terminal

Para interação direta fora do ambiente MCP, você pode usar o próprio script como cliente especificando o socket de saída:

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

Novas conexões de saída recebem automaticamente as últimas 60 linhas de histórico. Notificações de saída ao vivo são enviadas automaticamente quando dados da VM são recebidos, fornecendo transmissão em tempo real sem polling. Para enviar entrada, o cliente abre e escreve automaticamente no socket de entrada correspondente (/tmp/serial_${VM_NAME}.in).

Solução de problemas

  • Nenhuma janela de terminal é aberta: Se a abertura automática do terminal falhar, primeiro verifique se aplicativos gráficos podem ser iniciados a partir de um terminal root (por exemplo: pluma). Em seguida, verifique se as seguintes variáveis de ambiente estão configuradas corretamente:

    • XAUTHORITY: Necessária para autenticação X11 (por exemplo, /root/.Xauthority)
    • XDG_RUNTIME_DIR: Deve ser definida para o diretório de runtime do usuário (por exemplo, /run/user/0)
    • DBUS_SESSION_BUS_ADDRESS: Necessária para comunicação de sessão D-Bus

    Para corrigir esses problemas, execute o script a partir de uma sessão X11 adequada onde essas variáveis são definidas automaticamente, ou exporte-as manualmente:

    export XAUTHORITY=/root/.Xauthority
    export XDG_RUNTIME_DIR=/run/user/0
    
  • Falha na detecção do terminal: Use a opção --terminal para especificar explicitamente seu emulador de terminal:

    ./serencp.pl --terminal wezterm
    
  • Falha ao obter ferramentas: Certifique-se de que o script seja executado em um ambiente onde a entrada/saída padrão seja capturada. Use tools/list para verificar a conectividade.

  • Bridge não está em execução: Chame start antes de tentar ler ou escrever.

  • Sem notificações em tempo real: Certifique-se de que seu cliente MCP suporte o tratamento de notificações. As notificações são enviadas automaticamente quando a saída da VM é recebida. Use o método padrão MCP resources/subscribe para assinar os recursos vm://<vm_name>/output.

  • Permissão de Socket: Certifique-se de que /tmp seja gravável pelo usuário que executa o servidor MCP.

  • Verificação de Sintaxe: Execute perl -c serencp.pl para verificar a integridade do script.

  • O servidor MCP falhou ao iniciar: Verifique se todos os módulos Perl necessários estão carregados. Execute perl -c serencp.pl para verificar a sintaxe e o carregamento dos módulos. Se você vir "Can't locate ... in @INC", instale o módulo ausente (por exemplo, cpan IO::Pty para módulos não essenciais).

  • Buffer de escrita cheio: Se você vir avisos de "Write buffer full", o destino não está acompanhando os dados. Isso é normal em cenários de alta taxa de transferência e os dados serão descartados.

  • Backoff exponencial ativo: Se a bridge continuar reiniciando, você verá atrasos crescentes entre as tentativas de reconexão (1s, 2s, 4s... até 60s). Isso é intencional para evitar tempestades de conexão.

Sobre

O nome serencp é um jogo de palavras:

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

Sinta-se à vontade para contribuir

Como é um script complexo, sua ajuda / pull requests são muito apreciados!