Skip to main content
Glama
Lukx19

robot-nxt-control

by Lukx19

Robot NXT Control MCP

Local MCP server for compatible programmable-brick hardware connected to Windows 11 by USB/WinUSB.

Compatibility and trademarks

This independent project is not affiliated with, sponsored by, or endorsed by the LEGO Group. LEGO, MINDSTORS, and NXT are trademarks of the LEGO Group. They are used in this documentation only to identify compatible hardware, software, protocols, and third-party dependencies; they are not part of this project's name, server identifier, or plugin identifier.

Migration from earlier releases

The old plugin and MCP-server identifier has been replaced by robot-nxt-control, and the executables are now named robot-nxt-control-mcp, robot-nxt-control-mcp-stdio, and robot-nxt-control-mcp-http. Reinstall the editable package after pulling this change and replace earlier MCP configuration entries with the examples below.

Related MCP server: KentraBOT MCP Server

Install in Claude Desktop or Codex desktop (Windows)

Complete the Windows installation first. These desktop apps start the MCP server themselves, so do not run robot-nxt-control-mcp-stdio.exe manually. The examples assume this repository is at C:\Users\lukas\workspace\NXT-MCP; replace that part in every path if your checkout is elsewhere.

Claude Desktop

  1. Fully quit Claude Desktop (including its tray icon).

  2. Open %APPDATA%\Claude\claude_desktop_config.json. Create the file if it does not exist. If it already has an mcpServers object, add only the robot-nxt-control entry below.

  3. Save the file and start Claude Desktop again. The server should appear in Settings → Developer → MCP servers.

{
  "mcpServers": {
    "robot-nxt-control": {
      "command": "C:\\Users\\lukas\\workspace\\NXT-MCP\\.venv\\Scripts\\robot-nxt-control-mcp-stdio.exe",
      "cwd": "C:\\Users\\lukas\\workspace\\NXT-MCP"
    }
  }
}

The same ready-to-copy configuration is in packaging/claude-desktop/mcp.json.

Codex desktop

The Codex desktop host and Codex CLI use the shared MCP configuration in %USERPROFILE%\.codex\config.toml. Add this block (or run the equivalent codex mcp add command below), then restart the Codex app:

[mcp_servers.robot-nxt-control]
command = "C:\\Users\\lukas\\workspace\\NXT-MCP\\.venv\\Scripts\\robot-nxt-control-mcp-stdio.exe"
cwd = "C:\\Users\\lukas\\workspace\\NXT-MCP"
startup_timeout_sec = 10
tool_timeout_sec = 120

PowerShell alternative:

codex mcp add robot-nxt-control -- C:\Users\lukas\workspace\NXT-MCP\.venv\Scripts\robot-nxt-control-mcp-stdio.exe
codex mcp list

For the ChatGPT desktop MCP UI: Settings → MCP servers → Add server, choose STDIO, enter robot-nxt-control, use the same executable as the command, save, then restart the app. Local Codex clients support both STDIO and Streamable HTTP and share this MCP configuration. Official OpenAI MCP documentation

First check

Open a new chat and ask for nxt_info. If it cannot connect, first confirm the robot works with ./.venv/Scripts/nxt-test.exe --log-level=debug; then verify that every configured path exists and that the NXT is switched on. Movement tools control real hardware: begin with nxt_info or query_all_state, then use low power and bounded movement commands.

MCP transports, hosts, and verification

The same create_server() factory powers both transports. robot-nxt-control-mcp-stdio is the local-process transport for Claude Desktop, Claude Code, Codex CLI, Codex desktop, and local Codex plugins. It writes protocol traffic only to stdout.

Use the supplied JSON as a starting configuration, replacing the absolute workspace path after moving the checkout:

  • Claude Desktop: packaging/claude-desktop/mcp.json

  • Claude Code plugin: packaging/claude-code/

  • Codex local plugin: C:\Users\lukas\plugins\robot-nxt-control (created in the personal marketplace)

For protocol testing, start Streamable HTTP on loopback:

.\.venv\Scripts\robot-nxt-control-mcp-http.exe --port 8000
npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8000/mcp --method tools/list
npx @modelcontextprotocol/conformance server --url http://127.0.0.1:8000/mcp --suite active

Run the repository checks with .\.venv\Scripts\python.exe -m pytest. They include an in-process MCP negotiation, tools/list, annotations, and tools/call test, in addition to controller and behavior tests. conformance-baseline.yml records only generic scenarios requiring optional MCP features this focused hardware server does not advertise; each entry is a burn-down assertion, so the runner flags stale entries.

robot-nxt-control-mcp-http binds to 127.0.0.1 by default and refuses non-loopback binding unless NXT_MCP_ALLOW_REMOTE=true is explicitly set. A cloud client cannot reach a USB NXT directly: run this server next to the robot and place a production HTTPS reverse proxy with OAuth/token validation, authorization, audit logs, and network restrictions in front of the Streamable HTTP endpoint. Never expose the USB-control endpoint publicly with only the environment override.

See ARCHITECTURE.md for the module design, MCP and behavior execution flows, USB stack, safety model, and Mermaid diagrams.

Windows 11 installation

Use Python 3.11 x64. From PowerShell:

py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -e ".[test]"

1. Install the NXT device driver with Zadig

Turn the NXT on and connect it by USB. In PowerShell, confirm that Windows sees the normal-mode device:

Get-PnpDevice -PresentOnly |
  Where-Object InstanceId -match 'VID_0694&PID_0002' |
  Format-List Status,FriendlyName,InstanceId,Problem

The hardware ID must contain USB\VID_0694&PID_0002. If Problem is CM_PROB_FAILED_INSTALL or Device Manager shows Code 28, the driver is missing.

  1. Download Zadig only from https://zadig.akeo.ie/.

  2. Run Zadig as Administrator.

  3. Select Options > List All Devices.

  4. Select the entry whose USB ID is exactly 0694:0002. Use the ID, not only the displayed device name.

  5. Choose WinUSB in the driver selector.

  6. Click Install Driver or Replace Driver.

  7. Disconnect and reconnect the NXT, leaving it switched on.

Do not select 03EB:6124; that is the NXT bootloader/firmware-update mode. Do not replace drivers for any unrelated USB device. Installing WinUSB may prevent the old LEGO NXT-G software from talking to the brick until its LEGO/Fantom driver is restored.

2. Install the x64 libusb runtime for PyUSB

WinUSB is the Windows device driver. PyUSB separately needs the user-space libusb-1.0.dll. The repository includes a helper that downloads the official libusb 1.0.30 archive, verifies its SHA-256, and installs the VS2022 x64 DLL beside this environment's python.exe:

.\scripts\install-libusb-runtime.ps1

The helper requires 7z.exe on PATH. To install manually, download libusb-1.0.30.7z from the official libusb GitHub release, extract VS2022\MS64\dll\libusb-1.0.dll, and copy it to .venv\Scripts\libusb-1.0.dll. Do not use an MS32 DLL with 64-bit Python.

Verify the runtime independently:

$env:PATH = "$PWD\.venv\Scripts;$env:PATH"
.\.venv\Scripts\python.exe -c "import usb.backend.libusb1 as b; assert b.get_backend() is not None; print('libusb OK')"

The MCP server automatically adds a DLL installed beside its virtual-environment Python to its own search path. nxt-test.exe is an external NXT-Python command, so either run the $env:PATH line above first or activate the virtual environment before using it.

3. Test the brick

Verify the hardware before MCP:

.\.venv\Scripts\nxt-test.exe --log-level=debug

A successful test prints the brick name, battery level, protocol version, and firmware version. If it still reports no brick, recheck the 0694:0002 device in Device Manager and confirm that its driver is WinUSB.

Firmware

No firmware installation or update is needed when the NXT boots normally and Windows shows VID 0694 / PID 0002. The MCP server uses standard NXT direct commands and also reports the installed firmware and protocol versions through nxt_info.

Only perform firmware recovery if the brick cannot boot normally and Windows instead shows VID 03EB / PID 6124, which is Atmel SAM-BA firmware-update mode. Recovery erases and rewrites brick firmware and is outside the normal MCP setup:

  1. Do not install the normal NXT WinUSB rule against 03EB:6124.

  2. Restore/use the firmware-update driver required by the original LEGO MINDSTORMS NXT software.

  3. In that software, use Tools > Update NXT Firmware with an official NXT firmware image.

  4. After recovery, power-cycle the brick. It must return as 0694:0002; then install WinUSB for that normal-mode device again if necessary.

NXT-Python deliberately does not provide firmware flashing. Do not invoke firmware boot mode or attempt an update merely to troubleshoot NoBackendError, Code 28, or an MCP connection failure.

Then open the MCP Inspector:

.\.venv\Scripts\mcp.exe dev src\nxt_mcp\server.py

For a local MCP host, configure a stdio server with command .venv\Scripts\robot-nxt-control-mcp.exe and the repository as its working directory.

Declare the sensors attached to the brick before starting the server so the whole-brick snapshot can return typed readings immediately:

$env:NXT_SENSOR_MAP = "1:touch,2:light,4:ultrasonic"
.\.venv\Scripts\robot-nxt-control-mcp.exe

Calling read_sensor or a sensor-driven motor command also remembers that port's type for later snapshots.

High-level tools

  • move_motor_relative(port="C", power=40, degrees=2000) moves C forward by 2000 encoder degrees. Use negative power for the opposite direction. The adapter uses a tight USB encoder loop because NXT-Python's standard turn() loop polls too slowly for small movements. Use lower power, such as 20, for movements around 45 degrees.

  • zero_motor_position(port="C") defines the current C encoder position as absolute 0. move_motor_absolute(port="C", target_degrees=-90, power=20) then moves to -90. Absolute movement derives direction from the target; its power is a positive magnitude.

  • motor_position(port="C") reports the absolute/program-relative encoder position and the raw tacho counters.

  • run_motor(port="C", power=-30) runs continuously in the negative direction until a stop command or a bounded behavior stops it. With regulated=true, power is the NXT regulated speed setting, not a calibrated degrees-per-second value.

  • run_motors(ports=["B", "C"], powers=[30, -30]) starts a motor group in one MCP call. stop_motors, move_motors_relative, and move_motors_absolute operate on groups in the same way. Lists are positional: each power/degree value belongs to the port at the same index.

  • run_motor_until_sensor(port="B", power=40, sensor_port=1, sensor_type="touch", condition="pressed") runs B until touch S1 is pressed.

  • run_motors_until_sensor(ports=["B", "C"], powers=[40, 40], sensor_port=4, sensor_type="ultrasonic", condition="lte", threshold=20) drives both motors until an obstacle is at most 20 cm away, then stops the entire group.

  • run_motor_until_sensor(port="B", power=40, sensor_port=4, sensor_type="ultrasonic", condition="lte", threshold=20) runs B until an obstacle is at most 20 cm away.

  • query_all_state(format="text") returns one compact text snapshot of the brick, all motor ports, and all sensor ports. Use format="json" for structured data.

  • cycle_motor_on_touch(port="C", touch_port=1, cycles=5) runs forward until S1 is pressed, reverses until it is released, repeats five times, and beeps after success. Every press and release phase has its own timeout and encoder-travel ceiling.

Every sensor-driven motion has a timeout and an encoder travel limit. Reaching either limit stops the motor and returns ok: false with the reason. The motor is also stopped if a sensor or USB read fails.

Extended diagnostics, storage, and telemetry

  • motor_state(port) informa sobre la regulación, el estado de ejecución, los tacómetros y el estado de salida configurado. drive_sync(left_port, right_port, power, turn_ratio=0) usa la regulación síncrona del firmware NXT para un par de tracción diferencial; wait_motors(...) tiene un plazo y detiene sus puertos al agotarse el tiempo.

  • read_sensor_raw(port, sensor_type?), wait_sensor(...) y sensor_stream(...) exponen diagnósticos acotados, esperas de sensor con anti-rebote y muestras finitas.

  • Para los sensores de luz, color y ultrasónicos, llame a zero_sensor_reference(port, sensor_type) y después a read_sensor_relative(port, sensor_type). Devuelve el cambio respecto al cero capturado más el valor absoluto. El color usa la intensidad de luz reflejada (no la etiqueta discreta rojo/azul/etc.) para una resta significativa.

  • log_start, log_status, log_stop y log_export proporcionan telemetría CSV acotada del lado del host. Los canales válidos son battery_mv, motor:A a motor:C, y sensor:1:touch (u otro tipo/puerto de sensor compatible).

  • list_files, read_file, write_file y delete_file gestionan archivos de usuario NXT acotados. Las escrituras se limitan a .txt, .csv, .dat y .rso; la reproducción de sonido usa play_sound_file(name) y stop_sound().

  • mailbox_send / mailbox_receive admiten mensajes de hasta 58 bytes UTF-8; i2c_transaction es una operación opcional de baja velocidad limitada a cargas útiles de solicitud y respuesta de 16 bytes. set_brick_name y keep_alive son los comandos directos administrativos compatibles.

El protocolo estándar de comandos directos NXT no puede dibujar en la pantalla LCD del NXT ni leer sus botones. Esas funciones de NXT-G/ROBOTC requieren un programa puente residente en el NXT instalado por separado; este servidor no las expone intencionadamente.

Semántica de posicionamiento de motores

Los contadores del encoder NXT son grados en el eje del motor, no grados de orientación del robot ni milímetros lineales. Las relaciones de transmisión, la circunferencia de las ruedas y el deslizamiento deben gestionarse mediante un comportamiento del robot si se necesitan unidades físicas.

El cero absoluto lo mantiene el contador de rotación relativo al programa del firmware NXT. No es un sensor de homing y no es persistente: apagar y encender el ladrillo o iniciar/detener un programa .rxe invalida la referencia. Realice el homing contra un sensor táctil y llame a zero_motor_position de nuevo antes de confiar en los objetivos absolutos.

Los comandos de grupo inician motores usando paquetes USB consecutivos dentro de un único bloqueo de controlador. Evitan la desviación de ida y vuelta MCP/LLM y monitorizan todos los encoders juntos, pero no son tiempo real estricto ni están sincronizados mecánicamente por fase. Para un robot de dos ruedas esto es apropiado para conducción normal; la sincronización de precisión puede requerir un programa de control residente en el NXT.

Limitación del informe de hardware

El firmware NXT informa del estado configurado de cada puerto, pero no puede saber con seguridad si un motor inactivo está físicamente conectado. La consulta de todo el ladrillo informa por tanto de todos los estados A/B/C del firmware en lugar de afirmar la presencia de motores. Los puertos de sensores que ya han sido configurados por read_sensor o por un comando basado en sensores muestran valores tipados; otros puertos de sensores muestran su estado de firmware sin procesar. Consultar el estado sin procesar no reconfigura puertos ni energiza brevemente el hardware.

No use comandos MCP directos de motores mientras un programa .rxe esté controlando los mismos puertos.

Comportamientos Python en el lado del PC a través de MCP

El NXT de serie no ejecuta Python. Este servidor puede en su lugar guardar y ejecutar comportamientos Python restringidos en el PC; cada operación del robot sigue cruzando la frontera del controlador, por lo que los scripts no abren USB, no instancian sensores NXT-Python ni implementan sus propios bucles de sondeo.

Ejemplo de comportamiento:

def run(robot):
    robot.configure_sensor(1, "touch")

    for _ in range(5):
        robot.motor_until("C", 20, 1, "pressed")
        robot.motor_until("C", -20, 1, "released")

    robot.play_tone(440, 500)
    return "completed 5 touch cycles"

El mismo ejemplo se incluye como behaviors/touch_cycle.py. Use estas herramientas MCP:

validate_behavior(source)
submit_behavior(name, source)
list_behaviors()
get_behavior(name)
run_behavior(name, timeout_seconds=120)

La interfaz robot visible para el script contiene:

configure_sensor(port, sensor_type)
read_sensor(port, sensor_type)
read_sensor_raw(port, sensor_type=None)
zero_sensor_reference(port, sensor_type)
read_sensor_relative(port, sensor_type)
wait_sensor(port, sensor_type, condition, ...)
sensor_stream(port, sensor_type, ...)
log_start(channels, interval_ms=100, duration_seconds=10)
log_status(job_id)
log_stop(job_id)
log_export(job_id)
motor_until(port, power, sensor_port, condition, sensor_type="touch", ...)
motor_for_ticks(port, power, ticks, ...)
motor_position(port)
zero_motor_position(port)
motor_to(port, target_degrees, power=20, ...)
run_motor(port, power, regulated=True)
drive_sync(left_port, right_port, power, turn_ratio=0)
wait_motors(ports, ...)
stop_motor(port, brake=False)
run_motors(ports, powers, regulated=True)
stop_motors(ports, brake=False)
motors_relative(ports, powers, degrees, ...)
motors_absolute(ports, powers, target_degrees, ...)
motors_until(ports, powers, sensor_port, condition, ...)
state(format="text")
play_tone(frequency_hz=440, duration_ms=500)
play_sound_file(name, loop=False)
stop_sound()
sleep(seconds)

Las importaciones, clases, manejo de excepciones, acceso a atributos privados y llamadas fuera de los métodos documentados del robot y las funciones integradas básicas se rechazan. Los scripts están limitados a 64 KiB, solo uno puede ejecutarse a la vez, y run_behavior acepta un plazo de 1 a 300 segundos. Todos los motores se detienen cuando un script termina o lanza una excepción.

Esta validación pretende evitar el acceso accidental fuera de la interfaz del robot; no es una zona de pruebas de seguridad para código hostil. Solo conceda acceso MCP a usuarios locales de confianza. Establezca NXT_BEHAVIOR_DIR antes de iniciar el servidor para almacenar los comportamientos en un lugar distinto del directorio behaviors por defecto, bajo el directorio de trabajo del servidor.

Para una demostración de línea de comandos que siga usando MCP stdio en lugar de importar el controlador directamente:

.\.venv\Scripts\python.exe scripts\mcp-behavior-client.py list
.\.venv\Scripts\python.exe scripts\mcp-behavior-client.py submit touch_cycle behaviors\touch_cycle.py
.\.venv\Scripts\python.exe scripts\mcp-behavior-client.py run touch_cycle --timeout 120

El comando final realiza movimiento físico. El cliente solo llama a herramientas MCP; el servidor MCP carga el comportamiento y es dueño de toda la comunicación con el NXT.

Prueba sin ladrillo

$env:PYTHONPATH = "src"
py -3.11 -m pytest
Install Server
A
license - permissive license
B
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to control a two-track robot through movement commands, providing independent track control, high-level directional driving (forward, backward, left, right), and emergency stop functionality.
  • A
    license
    B
    quality
    B
    maintenance
    Enables LLMs to control a Minecraft bot through the Mineflayer API, allowing for tasks like building, mining, and inventory management via natural language. It supports complex interactions including coordinate-based movement, block manipulation, and real-time game chat.
    53
    23
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to control hardware devices like Arduino, Raspberry Pi, 3D printers, CNC machines, and custom robots via serial ports and HTTP. Provides tools for device discovery, command sending, sensor reading, servo control, G-code execution, and emergency stops with safety features.
    MIT

View all related MCP servers

Related MCP Connectors

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

  • Control Unreal Engine to browse assets, import content, and manage levels and sequences. Automate…

  • Gateway between LLM agents and world data through eight tools and a bundled endpoint catalog.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Lukx19/NXT-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server