Juniper Junos MCP Server
Um servidor MCP para interagir com dispositivos de rede Juniper Junos usando LLMs.
Documentação
junos-mcp-server
Um servidor Model Context Protocol (MCP) para dispositivos Juniper Junos que permite interações de LLMs com equipamentos de rede.
Sumário
- junos-mcp-server
- Sumário
- Aviso de Segurança Importante
- Aviso de Configuração Importante
- Começando
- Iniciar o Junos MCP Server
- Configuração
- Uso com Docker
- Configuração do Dispositivo Junos
- Integração com VSCode + GitHub Copilot
- Autenticação para Acesso ao MCP Server
- GuardRails para Commit de Configuração
- GuardRails para Executar Comandos Operacionais
- Usando o MCP Server com o Juniper Cloud-Native Router (JCNR)
- Guia do Desenvolvedor
Aviso de Segurança Importante
Aviso: Este servidor permite acesso de LLMs à sua infraestrutura de rede. Revise cuidadosamente estas considerações de segurança.
Requisitos de Segurança
-
Conformidade com Políticas Corporativas: Use este servidor somente se a política da sua empresa permitir o envio de dados de dispositivos Junos para serviços de LLM.
-
Segurança do Servidor: Sempre proteja seu Junos MCP server antes de implantá-lo em ambientes de produção.
-
Autenticação: Não use autenticação por senha para implantações de produção. Recomendamos fortemente o uso de autenticação baseada em chave SSH para maior segurança.
-
Estratégia de Implantação: Até que seu MCP server esteja devidamente protegido, implante apenas localmente para fins de teste. Não implante servidores remotos em produção sem medidas de segurança adequadas.
Boas Práticas de Segurança
- Use autenticação por chave SSH em vez de senhas
- Implemente controles de acesso de rede adequados
- Monitore e registre todas as atividades do MCP server
- Realize auditorias e atualizações de segurança regularmente
- Siga as políticas de segurança da sua organização
Aviso de Configuração Importante
Aviso: O Junos MCP server suporta alterações de configuração, mas certifique-se de usar essa funcionalidade somente quando desejar que configurações geradas por LLM sejam carregadas e commitadas no seu roteador Junos.
Sempre revise a configuração gerada pelo LLM e permita a execução da ferramenta somente se for a configuração correta para o seu caso de uso.
Começando
Obtenha o código.
git clone https://github.com/Juniper/junos-mcp-server.git
cd junos-mcp-server
pip install -r requirements.txt
Executando com uv
Se você estiver usando uv, pode executar o servidor diretamente:
uv run python jmcp.py -f devices.json -t stdio
Iniciar o Junos MCP Server
python3.11 jmcp.py --help
Junos MCP Server
options:
-h, --help show this help message and exit
-f DEVICE_MAPPING, --device-mapping DEVICE_MAPPING
the name of the JSON file containing the device mapping
-H HOST, --host HOST Junos MCP Server host
-t TRANSPORT, --transport TRANSPORT
Junos MCP Server transport
-p PORT, --port PORT Junos MCP Server port
O Junos MCP server suporta tanto o transporte streamable-http quanto o stdio. Não use --host com o transporte stdio.
Variáveis de Ambiente
JUNOS_TIMEOUT: Tempo limite de comando em segundos para operações CLI do Junos.JMCP_POOL_IDLE_TIMEOUT: Tempo limite de inatividade em segundos para conexões SSH/NETCONF em pool.- Sessões SSH são reutilizadas entre chamadas de ferramentas via um pool de conexões; uma thread de limpeza em segundo plano (executando uma vez por minuto) fecha conexões ociosas por mais tempo que esse limite.
- Padrão:
300. Valores inválidos são registrados e o padrão é usado.
Configuração
Configuração para Claude Desktop (transporte stdio)
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "python3",
"args": ["jmcp.py", "-f", "devices.json", "-t", "stdio"]
}
}
}
Configuração para Claude Desktop (usando uv)
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "uv",
"args": ["run", "python", "jmcp.py", "-f", "devices.json", "-t", "stdio"]
}
}
}
Nota: Forneça o caminho absoluto para jmcp.py e o arquivo devices.json.
Configuração para Claude Desktop (contêiner Docker)
{
"mcpServers": {
"jmcp": {
"type": "stdio",
"command": "/usr/local/bin/docker",
"args": [
"run",
"--rm",
"-i",
"-v",
"devices.json:/app/config/devices.json",
"-v",
"vsrx_keypair.pem:/app/config/vsrx_keypair.pem",
"junos-mcp-server:latest"
]
}
}
}
Uso com Docker
Construir Contêiner Docker
docker build -t junos-mcp-server:latest .
Executando com Configurações Padrão
Por padrão, o contêiner Docker executa com transporte stdio:
docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json
junos-mcp-server:latest
Isso usa o comando padrão: python jmcp.py -f /app/config/devices.json -t stdio
Substituindo Argumentos Padrão
Você pode substituir qualquer argumento especificando o comando completo:
Para transporte stdio:
docker run --rm -it -v /path/to/your/devices.json:/app/config/devices.json
junos-mcp-server:latest python jmcp.py -f /app/config/devices.json -t stdio
Para transporte streamable-http:
Segurança: o transporte streamable-http se recusa a iniciar sem um arquivo
.tokensválido. Gere um compython jmcp_token_manager.py generate --id <token-id>e monte-o no contêiner como mostrado abaixo. Consulte Autenticação para detalhes, ou passe--allow-unauthenticated-httppara desenvolvimento local somente com loopback.
docker run --rm -it \
-v /path/to/your/devices.json:/app/config/devices.json \
-v /path/to/.tokens:/app/.tokens \
-p 30030:30030 \
junos-mcp-server:latest \
python jmcp.py -f /app/config/devices.json -t streamable-http -H 0.0.0.0
Para streamable-http com porta personalizada:
docker run --rm -it \
-v /path/to/your/devices.json:/app/config/devices.json \
-v /path/to/.tokens:/app/.tokens \
-p 8080:8080 \
junos-mcp-server:latest \
python jmcp.py -f /app/config/devices.json -t streamable-http -p 8080 -H 0.0.0.0
Nota:
- Sempre monte seu arquivo de configuração de dispositivos usando
-v /path/to/you/ devices.json:/app/config/devices.json - Para transporte streamable-http, exponha a porta usando
-p host_port:container_port - Monte quaisquer arquivos de chave privada SSH se estiver usando autenticação por chave (ex.:
-v /path/to/key.pem:/app/config/key.pem)
Construa o contêiner Docker para o Junos MCP Server
docker build -t junos-mcp-server:latest .
Nota: Monte seu arquivo de configuração devices.json e monte quaisquer outros arquivos; no
meu caso, estou usando um arquivo pem para autenticação por chave privada SSH, então também estou
montando vsrx_keypair.pem
Configuração do Dispositivo Junos
O Junos MCP server suporta autenticação baseada em password bem como autenticação baseada em SSH key
(veja as configurações dos 2 primeiros roteadores [router-1, router-2]).
{
"router-1": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"auth": {
"type": "password",
"password": "pwd"
}
},
"router-2": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"auth": {
"type": "ssh_key",
"private_key_path": "/path/to/private/key.pem"
}
},
"router-3": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"ssh_config": "~/.ssh/config_dc",
"auth": {
"type": "ssh_key",
"private_key_path": "/path/to/private/key.pem"
}
},
"router-4": {
"ip": "ip-addr",
"port": 22,
"username": "user",
"ssh_config": "/home/user/.ssh/config_jumphost",
"auth": {
"type": "password",
"password": "pwd"
}
}
}
O Junos MCP server também oferece suporte para ProxyCommand. (veja as configurações dos 2 últimos roteadores
[router-3, router-4]), o que permite acessar um dispositivo de destino através
de um host intermediário que suporta netcat. Isso é útil quando você
só pode fazer login no dispositivo de destino através do host intermediário (jumphost).
Este é um exemplo de um arquivo de configuração SSH sendo usado .ssh/config_jumphost:
# Jumphost VM Connection
Host jumphost-vm
HostName 10.2.11.200
User root
# Used for MCP server
IdentityFile /home/user/.ssh/id_rsa_claude
IdentitiesOnly yes
StrictHostKeyChecking no
# cRPD Devices (via jump host)
Host dt-crpd1 dtwin-crpd1 digital-twin-crpd1 clab-digital-twin-eop6-pe1
HostName 172.20.20.11
User claude
IdentityFile c
# ProxyJump jumphost-vm # Not working with JunOS MCP
ProxyCommand ssh -l root jumphost-vm nc %h 22 2>/dev/null
StrictHostKeyChecking no
Nota #1: O valor de Port deve ser um inteiro (tipicamente 22 para SSH).
Nota #2: IdentityFile recomendação: use o caminho completo (ex.: /home/user/.ssh /id_rsa_claude rather than ~/.ssh/id_rsa_claude).
Integração com VSCode + GitHub Copilot
Inicie Seu Servidor
python3.11 jmcp.py -f devices.json
[06/11/25 08:26:11] INFO Starting MCP server 'jmcp-server' with transport
'streamable-http' on http://127.0.0.1:30030/mcp
INFO: Started server process [33512]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:30030 (Press CTRL+C to quit)
Aponte para Esta URL na Sua Configuração do VSCode
{
"mcp": {
"servers": {
"my-junos-mcp-server": {
"url": "http://127.0.0.1:30030/mcp/"
}
}
}
}
Nota: Você pode usar o Cmd+Shift+P do VSCode para configurar o MCP server.
Autenticação para Acesso ao MCP Server
O Junos MCP server suporta autenticação baseada em token para acesso seguro de clientes quando se usa o transporte streamable-http. Isso impede acesso não autorizado à sua infraestrutura de rede.
Comportamento da Autenticação
- Transporte stdio (Claude Desktop): Nenhuma autenticação necessária - seguro por design, pois executa localmente
- Transporte streamable-http (VSCode, clientes web): Autenticação baseada em token disponível
Gerenciamento de Tokens
O servidor inclui uma ferramenta CLI dedicada de gerenciamento de tokens:
jmcp_token_manager.py
Gerar um Novo Token
# Basic token generation
python jmcp_token_manager.py generate --id "vscode-dev"
# With description
python jmcp_token_manager.py generate --id "vscode-dev" --description "VSCode
development environment"
# Example output:
Generated new token:
ID: vscode-dev
Token: jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8
Description: VSCode development environment
Save this token securely - it won't be shown again!
Listar Todos os Tokens
python jmcp_token_manager.py list
# Example output:
ID Description Created
-------------------------------------------------------------------------------------
vscode-dev VSCode development environment
2025-01-28T10:30:00Z
prod-client Production client access
2025-01-28T09:15:00Z
Mostrar Valor do Token (Recuperação)
python jmcp_token_manager.py show --id "vscode-dev"
# Example output:
Token ID: vscode-dev
Token: jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8
Description: VSCode development environment
Created: 2025-01-28T10:30:00Z
Revogar um Token
python jmcp_token_manager.py revoke --id "vscode-dev"
# Example output:
Token 'vscode-dev' has been revoked
Status de Autenticação do Servidor
Para o transporte streamable-http, o servidor falha de forma segura: ele se recusa a
iniciar a menos que um arquivo .tokens válido e não vazio esteja presente. A única
maneira de iniciar sem tokens é a flag explícita --allow-unauthenticated-http,
que por sua vez é restrita a binds de loopback (127.0.0.1, ::1,
localhost).
Com tokens configurados:
python jmcp.py -f devices.json -t streamable-http
INFO - Token-based authentication enabled
INFO - Clients must send 'Authorization: Bearer <token>' header
INFO - Use jmcp_token_manager.py to manage tokens
INFO - Streamable HTTP server started on http://127.0.0.1:30030
Sem tokens configurados (padrão - recusa-se a iniciar):
python jmcp.py -f devices.json -t streamable-http
ERROR - Refusing to start streamable-http transport without authentication: .tokens file not found
ERROR - Generate a token with: python jmcp_token_manager.py generate --id <token-id>
ERROR - Or, for local development on loopback only, re-run with --allow-unauthenticated-http
Modo explícito sem autenticação (somente loopback, somente desenvolvimento):
python jmcp.py -f devices.json -t streamable-http --allow-unauthenticated-http
WARNING - *** Streamable HTTP authentication is DISABLED (--allow-unauthenticated-http). .tokens file not found. Server is open to any client that can reach 127.0.0.1:30030 and can commit configuration to mapped devices. Use only for local development. ***
INFO - Streamable HTTP server started on http://127.0.0.1:30030
Combinar --allow-unauthenticated-http com um bind não-loopback (por
exemplo -H 0.0.0.0) é rejeitado na inicialização.
Configuração do Cliente com Autenticação
Configuração do VSCode com Token
{
"mcp": {
"servers": {
"my-junos-mcp-server": {
"url": "http://127.0.0.1:30030/mcp/",
"headers": {
"Authorization": "Bearer
jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8"
}
}
}
}
}
Testando com curl
# Test authentication with valid token
curl -X POST "http://127.0.0.1:30030/mcp/" \
-H "Authorization: Bearer jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Test without token (should fail with 401)
curl -X POST "http://127.0.0.1:30030/mcp/" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Nota: MCP streamable-http requer o cabeçalho Accept: application/json, tex /event-stream.
Docker com Autenticação
Ao usar Docker, monte o arquivo .tokens para habilitar a autenticação:
# Generate token first (outside container)
python jmcp_token_manager.py generate --id "docker-client"
# Run container with token file mounted
docker run --rm -it \
-v /path/to/devices.json:/app/config/devices.json \
-v /path/to/.tokens:/app/.tokens \
-p 30030:30030 \
junos-mcp-server:latest \
python jmcp.py -f /app/config/devices.json -t streamable-http -H 0.0.0.0
Boas Práticas de Segurança
-
Segurança de Tokens:
- Armazene tokens de forma segura (gerenciadores de senhas, variáveis de ambiente)
- Use IDs de token descritivos para facilitar o gerenciamento
- Rotacione tokens regularmente revogando os antigos e gerando novos
- Nunca faça commit de tokens em controle de versão
-
Controle de Acesso:
- Gere tokens separados para diferentes clientes/ambientes
- Revogue tokens imediatamente quando não forem mais necessários
- Monitore logs do servidor para tentativas de acesso não autorizado
-
Segurança de Rede:
- Execute o servidor streamable-http atrás de um proxy reverso com HTTPS em produção
- Use regras de firewall para restringir o acesso à porta do MCP server
- Considere acesso VPN para clientes remotos
Formato do Arquivo de Tokens
O arquivo .tokens armazena tokens em formato JSON:
{
"vscode-dev": {
"token": "jmcp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8",
"description": "VSCode development environment",
"created": "2025-01-28T10:30:00Z"
},
"prod-client": {
"token": "jmcp_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2",
"description": "Production client access",
"created": "2025-01-28T09:15:00Z"
}
}
Importante: Mantenha este arquivo seguro e não o envie para controle de versão.
GuardRails para Commit de Configuração
A ferramenta load_and_commit_config agora inclui uma verificação de guardrail pré-commit que valida a configuração candidata submetida contra padrões em block.cfg antes que qualquer ação de commit no dispositivo seja tentada.
Como funciona
- Cada linha não-comentada em
block.cfgé tratada como um padrão bloqueado. - A correspondência de padrões é feita contra linhas de configuração normalizadas da
config_textsubmetida. - Padrões suportam tokens regex (por exemplo, para corresponder a nomes de usuário dinâmicos).
- Se qualquer linha corresponder, a solicitação é rejeitada e nenhuma configuração é carregada ou commitada.
Exemplo de block.cfg
# Blocked configuration prefixes/patterns for load_and_commit_config
set system root-authentication
set system login user ([^ ]+) authentication
Isso protege áreas de configuração comuns de alto risco (por exemplo, autenticação de root ou alterações não gerenciadas de credenciais de usuário local) de serem commitadas por automação.
GuardRails para Executar Comandos Operacionais
As ferramentas execute_junos_command e execute_junos_command_batch agora incluem guardrails de comando usando block.cmd.
Como funciona
- Cada linha não-comentada em
block.cmdé tratada como um padrão de comando regex. - Comandos submetidos são normalizados e verificados antes da execução.
- Se um comando corresponder a um padrão bloqueado, a execução é rejeitada.
- Para execução em lote, o comando bloqueado é rejeitado antes do envio aos roteadores.
Exemplo de block.cmd
# Blocked operational command prefixes/patterns for execute_junos_command
request system reboot
request system halt
request system power-cycle
request system power-off
request system zeroize
Isso bloqueia comandos disruptivos (ações de classe reboot/power/zeroize) enquanto ainda permite comandos operacionais somente leitura.
Usando o MCP Server com o Juniper Cloud-Native Router (JCNR)
JCNR é um roteador nativo de nuvem que executa em vários ambientes de nuvem. É possível usar este servidor MCP com JCNR também, seguindo os passos abaixo. Consulte a documentação do JCNR para mais detalhes sobre configuração.
- Configure o acesso SSH no JCNR em uma porta desejada diferente de 22. Isso é necessário porque o JCNR executa como um contêiner em um sistema operacional compartilhado. Executar SSH na porta padrão não é recomendado. Por padrão, o SSH está habilitado na porta 24. Mas é preferível alterar para a porta desejada dependendo das suas necessidades de rede.
- Habilite o método de autenticação para SSH. O JCNR suporta autenticação por chave SSH e por senha.
- Habilite Netconf sobre SSH. Isso está habilitado por padrão.
set system services netconf ssh
set system services ssh port 3030
set system services ssh root-login allow
set system root-authentication encrypted-password
"$6$3vvMI$RNemhmu9izWXzO46msh38frIg4VoeFNJWJZugxgnU.NQso3OQ00QWOIZmzNePD.MWjDOD
BBEYut/W7kfADdV." (or)
set system root-authentication load-key-file <public key>
Guia do Desenvolvedor
Esta seção explica a arquitetura do servidor MCP Junos e como estendê-lo com novas ferramentas.
Visão Geral da Arquitetura
O servidor MCP Junos usa o Model Context Protocol (MCP) para permitir que LLMs interajam com dispositivos de rede Juniper. A arquitetura do servidor consiste em:
- Núcleo do Servidor MCP (
jmcp.py): Lida com a comunicação do protocolo MCP - Manipuladores de Ferramentas: Funções individuais que implementam operações de rede específicas
- Registro de Ferramentas: Mapeia nomes de ferramentas para suas funções manipuladoras
- Camada de Transporte: Suporta stdio (Claude Desktop) e streamable-http (VSCode)
Como as Ferramentas Funcionam
Cada ferramenta no servidor MCP segue este fluxo:
Adicionando uma Nova Ferramenta
Adicionar uma nova ferramenta é um processo simples de 3 etapas:
Etapa 1: Criar uma Função Manipuladora
Crie uma função manipuladora assíncrona em jmcp.py (antes do dicionário TOOL_HANDLERS):
async def handle_my_new_tool(arguments: dict) -> list[types.ContentBlock]:
"""Handler for my_new_tool - describe what it does"""
# Extract arguments
router_name = arguments.get("router_name", "")
my_param = arguments.get("my_param", "default_value")
# Validate router exists
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
# Your tool logic here
log.debug(f"Executing my_new_tool on router {router_name}")
result = _run_junos_cli_command(router_name, f"show {my_param}")
return [types.TextContent(type="text", text=result)]
Etapa 2: Registrar o Manipulador
Adicione seu manipulador ao dicionário TOOL_HANDLERS (por volta da linha 330):
TOOL_HANDLERS = {
"execute_junos_command": handle_execute_junos_command,
"get_junos_config": handle_get_junos_config,
"junos_config_diff": handle_junos_config_diff,
"gather_device_facts": handle_gather_device_facts,
"get_router_list": handle_get_router_list,
"load_and_commit_config": handle_load_and_commit_config,
"my_new_tool": handle_my_new_tool, # Add your tool here
}
Etapa 3: Definir Metadados da Ferramenta
Adicione a definição da ferramenta ao método list_tools() (por volta da linha 410):
types.Tool(
name="my_new_tool",
description="Brief description of what your tool does",
inputSchema={
"type": "object",
"properties": {
"router_name": {"type": "string", "description": "The name of the
router"},
"my_param": {"type": "string", "description": "Description of
parameter"}
},
"required": ["router_name"] # List required parameters
}
)
Exemplo: Criando uma Ferramenta de Vizinhos BGP
Aqui está um exemplo completo de adição de uma ferramenta para mostrar vizinhos BGP:
# Step 1: Handler function
async def handle_show_bgp_neighbors(arguments: dict) -> list[types.ContentBloc
]:
"""Handler for show_bgp_neighbors tool"""
router_name = arguments.get("router_name", "")
neighbor_address = arguments.get("neighbor_address", "")
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
log.debug(f"Getting BGP neighbors from router {router_name}")
if neighbor_address:
cmd = f"show bgp neighbor {neighbor_address}"
else:
cmd = "show bgp summary"
result = _run_junos_cli_command(router_name, cmd)
return [types.TextContent(type="text", text=result)]
# Step 2: Add to TOOL_HANDLERS
TOOL_HANDLERS = {
# ... existing tools ...
"show_bgp_neighbors": handle_show_bgp_neighbors,
}
# Step 3: Add to list_tools()
types.Tool(
name="show_bgp_neighbors",
description="Show BGP neighbor information",
inputSchema={
"type": "object",
"properties": {
"router_name": {"type": "string", "description": "The name of the
router"},
"neighbor_address": {"type": "string", "description": "Optional:
specific neighbor IP"}
},
"required": ["router_name"]
}
)
Melhores Práticas para Desenvolvimento de Ferramentas
- Tratamento de Erros: Sempre trate erros de conexão e entradas inválidas com elegância
- Registro de Logs: Use o logger global
logpara depuração - Validação: Verifique se o roteador existe antes de tentar operações
- Documentação: Escreva descrições claras para ferramentas e parâmetros
- Tempos Limite: Suporte tempos limite configuráveis para operações de longa duração
- Formato de Retorno: Sempre retorne
list[types.ContentBlock]com conteúdo de texto - Parâmetro de Contexto: Use o parâmetro
context: Contextpara enviar progresso e mensagens de log ao cliente
Usando PyEZ para Operações Avançadas
Para operações além de comandos CLI, use PyEZ diretamente:
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
# Example: Using PyEZ tables
async def handle_get_interfaces(arguments: dict) -> list[types.ContentBlock]:
router_name = arguments.get("router_name", "")
if router_name not in devices:
result = f"Router {router_name} not found in the device mapping."
else:
device_info = devices[router_name]
try:
connect_params = prepare_connection_params(device_info, router_name)
with Device(**connect_params) as junos_device:
# Use PyEZ tables or other utilities
interfaces = junos_device.rpc.get_interface_information()
# Process interfaces...
result = "Interface information..."
except Exception as e:
result = f"Error: {e}"
return [types.TextContent(type="text", text=result)]
Testando Suas Ferramentas
- Testes Unitários: Teste funções manipuladoras com argumentos simulados
- Testes de Integração: Teste com dispositivos Junos reais ou vSRX
- Casos de Erro: Teste com roteadores inválidos, falhas de rede, etc.
Exemplo de teste:
# Test the handler directly
result = await handle_my_new_tool({
"router_name": "router-1",
"my_param": "test"
})
print(result[0].text)
Dicas de Depuração
- Habilite o registro de logs de depuração para ver a execução detalhada:
logging.basicConfig(level=logging.DEBUG)
- Use o transporte stdio para depuração mais fácil:
python jmcp.py -f devices.json -t stdio
- Teste comandos individuais manualmente:
result = _run_junos_cli_command("router-1", "show version")
print(result)