ground truth for firmware

MCU_NOW

Give your agent a physical test bench.

AI agents write brilliant code until they hit reality. MCU_NOW gives your agent programmatic access to real microcontrollers—Arduino, ESP32, nRF52, and more. It compiles, flashes, power-cycles, and debugs on live silicon with zero human intervention.

Stop simulating. Start executing.

espressif_esp32_devkitraspberrypi_picost_nucleoarduino_unoarduino_nanoarduino_megaheltec_wireless_trackerxiao_nrf52840_plusadafruit_feather_esp32_v2rig_dc_motorrig_stepperrig_servoti_tiva_c_launchpadti_msp430_launchpadmicrochip_curiosity_nanoteensy_4_1microbit_v2espressif_esp8266_devkitgeneric_stm32_bluepillm5stack_stickc_pluslilygo_tbeamlilygo_tdisplayxiao_esp32s3arduino_nicla_voicearduino_nano_33_blearduino_giga_r1seeed_grove_vision_ai_v2
live telemetry

Fleet status

Loading fleet status…

reference

Error codes

error_codeMeaning
BUILD_ERRORThe submitted firmware failed to build or link before it could be flashed.
FLASH_FAILEDThe flashing tool could not write the firmware to the board.
FIRMWARE_TOO_LARGEThe submitted artifact exceeds the known flash size ceiling for this board_type.
FIRMWARE_CRASHEDThe firmware panicked, asserted, or reset itself abnormally during execution — see result.crash and result.reset_reason.
NO_SERIAL_OUTPUTThe board produced no serial output at all during the capture window — the firmware may have flashed but never actually started running.
FIRMWARE_BRICKED_BOARDThis job's board did not come back after reflashing to idle and appears bricked — it has been taken offline and a brick incident was recorded.
BOARD_OFFLINEThe target board was not reachable by the runner at dispatch time.
RUNNER_ERRORThe runner hit an unexpected internal error while executing this job — the board's recovery could not be attempted and it has been taken offline pending manual inspection.
TIMEOUT_NO_RESPONSEThe board never produced the expected output before the job timeout.
INVALID_BOARD_TYPEThe requested board_type is not one this farm can service.
interface

REST API

POST/v1/run

Submit + long-poll (up to ~60s) for an inline result. 202 with a job_id if it hasn't finished yet.

POST/v1/jobs

Submit and return immediately (async). Poll the job separately.

GET/v1/jobs/{id}

Fetch current job state — status, and result once done.

GET/v1/status

Public. Per-board-type availability and queue depth. No auth required.

Auth: Authorization: Bearer <api_key>, created from your dashboard.

interface

MCP tools

One Streamable HTTP endpoint at /mcp, exposing these tools.

[
  {
    "name": "run_firmware_test",
    "description": "Flash the given firmware artifact onto a real physical dev board of the requested type and run it, waiting for and returning a structured pass/fail result.",
    "input_schema": {
      "type": "object",
      "properties": {
        "board_type": {
          "type": "string",
          "enum": [
            "espressif_esp32_devkit",
            "raspberrypi_pico",
            "st_nucleo",
            "arduino_uno",
            "arduino_nano",
            "arduino_mega",
            "heltec_wireless_tracker",
            "xiao_nrf52840_plus",
            "adafruit_feather_esp32_v2",
            "rig_dc_motor",
            "rig_stepper",
            "rig_servo",
            "ti_tiva_c_launchpad",
            "ti_msp430_launchpad",
            "microchip_curiosity_nano",
            "teensy_4_1",
            "microbit_v2",
            "espressif_esp8266_devkit",
            "generic_stm32_bluepill",
            "m5stack_stickc_plus",
            "lilygo_tbeam",
            "lilygo_tdisplay",
            "xiao_esp32s3",
            "arduino_nicla_voice",
            "arduino_nano_33_ble",
            "arduino_giga_r1",
            "seeed_grove_vision_ai_v2"
          ]
        },
        "artifact_url": {
          "type": "string",
          "description": "Cloud Storage path/URL to the firmware binary to flash."
        },
        "timeout_s": {
          "type": "integer",
          "minimum": 1,
          "maximum": 280,
          "description": "Max seconds to wait (default 60)."
        }
      },
      "required": [
        "board_type",
        "artifact_url"
      ]
    }
  },
  {
    "name": "search_firmware_examples",
    "description": "Search this farm's curated knowledge base (reference examples, known issues, pinout/datasheet links) for a board type.",
    "input_schema": {
      "type": "object",
      "properties": {
        "board_type": {
          "type": "string",
          "enum": [
            "espressif_esp32_devkit",
            "raspberrypi_pico",
            "st_nucleo",
            "arduino_uno",
            "arduino_nano",
            "arduino_mega",
            "heltec_wireless_tracker",
            "xiao_nrf52840_plus",
            "adafruit_feather_esp32_v2",
            "rig_dc_motor",
            "rig_stepper",
            "rig_servo",
            "ti_tiva_c_launchpad",
            "ti_msp430_launchpad",
            "microchip_curiosity_nano",
            "teensy_4_1",
            "microbit_v2",
            "espressif_esp8266_devkit",
            "generic_stm32_bluepill",
            "m5stack_stickc_plus",
            "lilygo_tbeam",
            "lilygo_tdisplay",
            "xiao_esp32s3",
            "arduino_nicla_voice",
            "arduino_nano_33_ble",
            "arduino_giga_r1",
            "seeed_grove_vision_ai_v2"
          ]
        },
        "query": {
          "type": "string",
          "description": "Free-text filter over category/title/description/known-issue text."
        }
      },
      "required": [
        "board_type"
      ]
    }
  },
  {
    "name": "get_upload_url",
    "description": "Get a short-lived signed URL to upload a firmware binary to before calling run_firmware_test.",
    "input_schema": {
      "type": "object",
      "properties": {
        "filename": {
          "type": "string",
          "description": "Filename to store the artifact as (default \"firmware.bin\")."
        }
      },
      "required": []
    }
  }
]
walkthrough

Try it

REST — request

curl -X POST https://YOUR_PROJECT.web.app/v1/run \
  -H "Authorization: Bearer ftf_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "board_type": "espressif_esp32_devkit",
    "artifact_url": "uploads/your-uid/local/firmware.bin",
    "timeout_s": 60
  }'

REST — response

{
  "job_id": "8f3c2e1a-...",
  "status": "done",
  "code_review": { "risk_score": 0, "category": "none", "flagged_lines": [], "analyzed": ["binary"] },
  "queue": { "position": 1, "jobs_ahead": 0, "avg_run_time_s": 68, "estimated_wait_s": 0 },
  "result": {
    "status": "pass",
    "summary": "Firmware booted, ran to completion, and matched expected behavior.",
    "error_code": null,
    "log_url": "gs://.../results/8f3c2e1a/run.log",
    "log_excerpt": "[boot] ready\n[app] self-test passed",
    "log_truncated": false,
    "timing": { "flash_ms": 1820, "boot_to_first_output_ms": 340, "execution_ms": 4210 },
    "reset_reason": "power_on",
    "crash": { "type": null, "backtrace": null },
    "resource_usage": { "free_heap_bytes": 142031, "stack_high_water_mark": 1024 },
    "witness_data": null,
    "board_context": { "farm_id": "default-farm", "board_instance_id": "espressif_esp32_devkit-sim-2" }
  }
}

REST — blocked by security review

Every submission is scanned before it's ever queued. A submission that scores above the risk threshold is rejected outright — no job is created.

HTTP/1.1 403 Forbidden
{
  "error": "Submission blocked by security review: risk_score 92 (threshold 70), category \"raw_network\". Flagged: ..."
}

MCP — tools/call request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "run_firmware_test",
    "arguments": {
      "board_type": "espressif_esp32_devkit",
      "artifact_url": "uploads/your-uid/local/firmware.bin",
      "timeout_s": 60
    }
  }
}

MCP client config

{
  "mcpServers": {
    "mcu-now": {
      "url": "https://YOUR_PROJECT.web.app/mcp",
      "headers": {
        "Authorization": "Bearer ftf_your_api_key_here"
      }
    }
  }
}

Sign in and visit your dashboard to generate a real key and download this filled in.