Network Table MCP

Servidor MCP que expõe as NetworkTables (NT4) da FRC para agentes de IA. Lê, escreve e monitora as tabelas de rede de um robô.

Documentação

nt-mcp-server

Um servidor MCP autônomo para ler, escrever e monitorar dados de NetworkTables do FRC. Ele permite que um agente de IA converse com as tabelas de rede de um robô através do protocolo NetworkTables. O alvo principal é a simulação local do RobotPy (python -m robotpy sim em 127.0.0.1:5810).

dependências fixadas (fastmcp==3.4.7, pyntcore==2026.2.2).

Executar o servidor

uv run nt-mcp-server

Ou, de forma equivalente, a partir de um checkout sem o shim do uv:

python -m nt_mcp_server

O servidor roda em stdio, que é como os clientes MCP (como o opencode) conversam com ele.

Conectar à simulação NetworkTables do RobotPy

A simulação deve estar em execução antes que o servidor possa ler ou escrever qualquer coisa útil. Inicie-a a partir do próprio projeto e venv:

cd <path-to-try-robotpy> && .venv\Scripts\activate && python -m robotpy sim

O servidor conecta-se a 127.0.0.1:5810 por padrão, que é onde a simulação escuta.

Conectar à NetworkTables de um Robô Real

Na maioria dos casos, basta gravar a NetworkTables e usar o MCP para analisar as gravações.

Se você quiser conectar-se à NetworkTables de um robô real em tempo real, você precisa ter 2 adaptadores de rede no seu dispositivo: um para Internet e outro para comunicação com o robô. Uma abordagem é usar seu adaptador sem fio para conectar-se ao Wi-Fi e usar um cabo Ethernet para conectar-se ao robô. Alternativamente, você pode obter um Adaptador de Rede USB como segundo adaptador. Em ambos os casos, pode ser necessário configurar o roteamento de rede do seu dispositivo.

Registrar em qualquer agente

O servidor é um pacote executável via uvx a partir do git, então qualquer cliente MCP pode iniciá-lo sem um checkout local ou um venv pré-construído. Execute-o sob demanda:

uvx --from git+https://github.com/Mzzj114/nt-mcp-server.git nt-mcp-server

Ou instale o script de console uma vez e execute-o em qualquer lugar:

uv tool install "git+https://github.com/Mzzj114/nt-mcp-server.git"
uvx nt-mcp-server

Adicione esta entrada à configuração MCP do seu agente (mostrada aqui como o opencode.jsonc de nível de projeto do opencode):

{
  "mcp": {
    "nt": {
      "type": "local",
      "command": [
        "uvx",
        "--from",
        "git+https://github.com/Mzzj114/nt-mcp-server.git",
        "nt-mcp-server"
      ],
      "enabled": true
    }
  }
}

Verifique com opencode mcp list a partir da raiz do projeto: o servidor nt deve aparecer como conectado.

Carregar a habilidade nt-mcp-workflow

O repositório inclui uma habilidade de agente em skills/nt-mcp-workflow/ que ensina um agente a conduzir uma investigação de NetworkTables (ao vivo, replay offline e gravação). O opencode não verifica um diretório .agents/skills local do projeto — ele apenas carrega automaticamente ~/.agents/skills, ~/.claude/skills, .opencode/skill(s)/ do projeto e diretórios listados sob skills.paths explícito. Adicione este bloco ao seu opencode.jsonc (mesmo arquivo da entrada mcp acima):

{
  "skills": {
    "paths": ["../nt-mcp-server/skills"]
  }
}

O caminho relativo é resolvido em relação ao diretório que contém o arquivo de configuração, então ajuste-o para onde seu checkout deste repositório estiver em relação a essa configuração. O opencode verifica skills.paths recursivamente em busca de **/SKILL.md, então apontar para o diretório skills do repositório expõe nt-mcp-workflow.

O opencode carrega sua configuração uma vez na inicialização e não faz hot-reload — reinicie o opencode após editar a configuração para que a habilidade apareça.

Gravar NetworkTables em NDJSON (offline)

O CLI nt-recorder conecta-se a um servidor NT4 ao vivo e escreve eventos de valor em um arquivo .ndjson com timestamp. Ele roda no laptop de desenvolvimento e lê NT da simulação ou do robô; nenhuma alteração no lado do robô é necessária.

uv run nt-recorder --prefixes /SmartDashboard/ --output-dir recordings

Ou, sem checkout local, execute-o diretamente do git via uvx (mesma fonte do servidor):

uvx --from git+https://github.com/Mzzj114/nt-mcp-server.git nt-recorder --prefixes /SmartDashboard/ --output-dir recordings

Ou instale a ferramenta uma vez para que tanto nt-mcp-server quanto nt-recorder estejam no PATH, e então execute qualquer um sem re-buscar:

uv tool install "git+https://github.com/Mzzj114/nt-mcp-server.git"
nt-recorder --prefixes /SmartDashboard/ --output-dir recordings

O gravador é um script de console autônomo, independente do servidor MCP — executar o servidor via uvx não o inicia, e o agente não precisa estar conectado para gravar.

Opções:

  • --prefixes — prefixos de tópicos para assinar (padrão: /)
  • --output-dir — diretório para arquivos de saída (padrão: ./recordings)
  • --duration — gravar por N segundos e depois sair (padrão: executar até Ctrl+C)
  • --team — conectar via número da equipe em vez de IP/porta do servidor
  • --server-ip / --server-port — endereço do servidor NT4 (padrão: 127.0.0.1:5810)
  • --identity — string de identidade do cliente (padrão: nt-recorder)
  • --quiet — suprimir saída de status para stderr

Os arquivos de saída são nomeados nt-record-<YYYY-MM-DDTHHMMSSZ>.ndjson (sem dois-pontos, seguro para Windows). Cada linha é {"time": float, "topic": str, "value": jsonable}. Códigos de saída: 0 limpo, 1 falha de conexão, 2 erro de disco/E/S.

Execute uv cache prune ou uv cache clear se você não quiser que arquivos de cache permaneçam no seu dispositivo após uvx.

Ferramentas

O servidor expõe 18 ferramentas (13 ao vivo + 5 offline). Cada resposta de ferramenta ao vivo inclui um sinalizador connected.

Ferramentas ao vivo

FerramentaDescrição
nt_connectInicia o cliente NT4 e aguarda uma conexão ao vivo. Passe exatamente um alvo: team_number, ou server_ip/server_port com o server_ip padrão. Parâmetros: server_ip="127.0.0.1", server_port=5810, team_number=None, identity="nt-mcp", timeout_seconds=5.0. O sucesso retorna {"connected": true, "status": "connected", "target": {...}} onde target relata o alvo resolvido (ex.: {"kind": "server", "server_ip": "127.0.0.1", "server_port": 5810} ou {"kind": "team", "team_number": 8326, "server_port": 5810}). Duas recusas retornam {"connected": false, "error": ...}: um alvo ambíguo (tanto team_number quanto um server_ip não padrão) é rejeitado antes de qualquer mudança de estado, e redirecionar um cliente em execução é rejeitado — chame nt_disconnect primeiro. Em timeout, a resposta adiciona diagnósticos connections, elapsed_seconds e um hint de roteamento. O caminho team_number resolve endereços de robô via busca por número de equipe do pyntcore; a lista exata de endereços ainda não foi verificada contra hardware real, então o target resolvido é relatado em vez de assumido.
nt_disconnectPara o cliente NT4 e encerra assinaturas persistentes. Retorna {"connected": false, "status": "disconnected"}.
nt_connection_infoRetorna o estado da conexão: {"connected": bool, "connections": [{"remote_id", "remote_ip", "last_update"}], "target": resolved_target | null, "version": str}. target é o alvo de conexão resolvido (null antes da primeira conexão); version é a versão do pacote instalado ("unknown" quando os metadados estão ausentes).
nt_getRetorna o valor normalizado em JSON de um tópico. Resposta: {"connected": bool, "value": jsonable | null}.
nt_get_multipleRetorna todos os tópicos solicitados. Resposta: {"connected": bool, "values": {topic: value}}.
nt_get_infoRetorna metadados do tópico. Resposta: {"connected": bool, "info": {name, type_str, properties} | null}.
nt_setPublica um valor. Resposta: {"connected": bool, "ok": bool, "warning": str | null}. Adicione strict_type_check=True para recusar incompatibilidades de tipo.
nt_set_multipleEscreve cada par {topic: value}. Resposta: {"connected": bool, "results": {...}, "warnings": {...}}.
nt_list_topicsLista nomes de tópicos, filtrados por prefix, regex e/ou wildcard. Resposta: {"connected": bool, "topics": [...]}.
nt_subscribeAmostra atualizações sob prefixos por duration segundos. Mudança significativa: o parâmetro format foi removido em favor de output, que tem como padrão "file" — a mesma janela é capturada em uma gravação NDJSON (formato nt-recorder, gravada em output_dir ou NT_RECORDINGS_DIR) e a resposta é um recibo compacto sem valores de amostra. Modos inline: output="summary" retorna min/max/média/último por tópico; output="samples" retorna {topic: [{"time", "value"}, ...]} bruto. Ambos os modos inline são limitados por limit (por tópico), max_rows (total, padrão 5000) e um teto final de 60.000 caracteres — cada descarte declara truncated: true. Modos inline recusam um prefixo "/" vazio; output="file" o aceita. sample_interval reduz eventos para um por tópico por intervalo; change_only ignora mudanças numéricas iguais ou abaixo do limite.

Recibo nt_subscribe padrão (sem valores de amostra, algumas centenas de caracteres serializados, bem abaixo do orçamento de 20.000 caracteres):

{
  "connected": true,
  "output": "file",
  "recording_id": "nt-record-2026-09-18T120000Z.ndjson",
  "path": "recordings\\nt-record-2026-09-18T120000Z.ndjson",
  "duration_seconds": 10.0,
  "rows": 214,
  "topic_count": 6,
  "topics": ["/SmartDashboard/gyro_angle", "/SmartDashboard/left_speed"],
  "topics_truncated": false,
  "truncated": false
}

topics lista no máximo 50 nomes (topics_truncated: true quando existem mais); rows é o número de eventos gravados e truncated é verdadeiro quando o limite de captura max_rows interrompeu a gravação antecipadamente. | nt_start_subscription | Abre uma assinatura persistente. Resposta: {"connected": bool, "subscription_id": str, "started": bool}. | | nt_poll_subscription | Lê amostras em buffer de uma assinatura persistente. Resposta: {"connected": bool, "samples": {topic: [...]}}. | | nt_stop_subscription | Para uma assinatura persistente. Resposta: {"connected": bool, "stopped": bool}. |

Ferramentas de gravação offline

FerramentaDescrição
nt_list_recordingsLista gravações. Cada entrada inclui id, path, size_bytes, modified (float Unix) e modified_iso (UTC ISO-8601).
nt_get_recording_infoRetorna duração, contagem total de amostras, contagem de tópicos e metadados do arquivo para uma gravação.
nt_get_historyRetorna histórico de eventos para um tópico. Suporta last_seconds, sample_interval e format="summary". A resposta sempre declara rows (entradas retornadas), total_rows (todos os eventos correspondentes, contados mesmo além do recorte) e truncated (total_rows > rows, ou o teto de 60.000 caracteres descartou linhas) para que o recorte nunca seja silencioso.
nt_list_topics_offlineLista nomes únicos de tópicos em uma gravação, filtrados por prefix, regex e/ou wildcard.
nt_subscribe_offlineRetorna eventos para todos os tópicos sob prefixos. Suporta last_seconds, sample_interval e format="summary". limit (padrão 1000 por tópico) e max_rows (padrão 5000 total) limitam o payload; como nt_get_history, a resposta sempre declara rows / total_rows / truncated. Um prefixo "/" vazio é recusado — restrinja-o (ex.: /SmartDashboard/) ou use o CLI nt-recorder para despejar uma gravação inteira.

As ferramentas offline leem do diretório definido pela variável de ambiente NT_RECORDINGS_DIR (padrão: ./recordings). As gravações são apenas locais — o gravador roda no laptop de desenvolvimento e lê NT da simulação/robô; nenhuma alteração no lado do robô é necessária.

O CLI nt-recorder também suporta --team N para conectar via número da equipe; a ferramenta MCP nt_connect expõe a mesma escolha via team_number.

Desenvolvimento

  • Venv Python 3.14.0 em .venv; dependências instaladas de requirements.txt (fastmcp==3.4.7, pyntcore==2026.2.2).
  • Testes: uv run pytest tests/ -v

Notas de versão

0.2.0 — mudanças significativas

  • Reformulação da saída nt_subscribe: o parâmetro format foi removido e substituído por output, que tem como padrão "file". Chamadores que passaram format= devem mudar para output=. No modo "file", a resposta é um recibo compacto (caminho da gravação, contagem de linhas, lista de tópicos) sem valores de amostra; modos inline ("summary", "samples") são limitados por limit, max_rows e um teto de 60.000 caracteres.
  • Ferramentas offline retornam recibos: nt_get_history agora inclui rows / total_rows / truncated; nt_subscribe_offline retorna {recording_id, topics, rows, total_rows, truncated}. O recorte nunca é silencioso.
  • Proteções de alvo nt_connect: um alvo ambíguo (tanto team_number quanto um server_ip não padrão) é rejeitado antes de qualquer mudança de estado, e redirecionar um cliente em execução é recusado — chame nt_disconnect primeiro.
  • nt_connection_info ganhou target e version: target é o alvo de conexão resolvido (null antes da primeira conexão); version é a versão do pacote instalado.

Licença

Este projeto está sob a Licença MIT.