Skip to main content
Glama

esp32-mcp

An MCP server that gives an AI agent control of an ESP32: relays, GPIO pins and analog sensors. The Python side has no dependencies and implements the MCP stdio protocol by hand. The board runs a single Arduino sketch and speaks JSON over HTTP.

AI agent  <-- stdio, JSON-RPC 2.0 -->  esp32-mcp  <-- HTTP, JSON -->  ESP32 firmware

I built this because most "AI controls hardware" demos hide a cloud service in the middle. Here the agent talks to a process on your machine, that process talks to a board on your LAN, and nothing leaves the network.

Status

v0.1.0. The whole path works end to end against a simulated board: handshake, tool discovery, relay switching, sensor reads and GPIO reads and writes. The test suite runs without hardware. The sketch compiles for esp32:esp32:esp32 with arduino-cli 1.4.1 and ESP32 core 3.3.12 (74% flash, 15% RAM). CI does not compile the sketch yet, see Deferred.

Related MCP server: ESP32 MCP Server

What is in the box

Path

What it is

src/esp32_mcp/

MCP server, JSON-RPC framing, HTTP device client, tool registry

src/esp32_mcp/fake_device.py

Simulated ESP32 used by the tests and the demo

firmware/esp32_mcp/esp32_mcp.ino

Complete Arduino sketch, pin map at the top

scripts/demo.py

Spawns the server, runs the handshake and calls real tools

docs/wiring.md

Pin tables, wiring diagram, power and safety notes

tests/

pytest suite, no hardware required

Install

git clone https://github.com/StefanYasin/esp32-mcp.git
cd esp32-mcp
python -m pip install -e .

Python 3.9 or newer. There are no runtime dependencies.

Try it without hardware

python scripts/demo.py --fake

That starts the built in simulator, spawns esp32-mcp as a real subprocess and prints the full JSON-RPC exchange: initialize, tools/list, then calls to esp32_health, esp32_read_sensors, esp32_set_relay, esp32_gpio_write and esp32_gpio_read.

Flash the firmware

  1. Install the ESP32 board package in the Arduino IDE (Boards Manager -> esp32 by Espressif).

  2. Open firmware/esp32_mcp/esp32_mcp.ino.

  3. Edit the CONFIG block at the top: WiFi credentials, API_TOKEN, the relay table, the analog sensor table and the GPIO allow list.

  4. Select ESP32 Dev Module, upload, then open the serial monitor at 115200 baud.

  5. Note the IP address it prints, or use the mDNS name http://esp32-mcp.local.

curl http://esp32-mcp.local/health
curl -X POST http://esp32-mcp.local/relay/1/on
curl "http://esp32-mcp.local/gpio?pin=2&value=1"

The same thing from the command line, which is the path I used to check the sketch:

arduino-cli core install esp32:esp32
arduino-cli compile --fqbn esp32:esp32:esp32 firmware/esp32_mcp
arduino-cli upload --fqbn esp32:esp32:esp32 -p COM5 firmware/esp32_mcp

Swap COM5 for your port. Wiring, pin tables and the safety notes are in docs/wiring.md.

Connect an MCP client

Point the client at the esp32-mcp executable and give it the board URL. This is the Claude Desktop shape; other clients use the same command and env fields.

{
  "mcpServers": {
    "esp32": {
      "command": "esp32-mcp",
      "env": {
        "ESP32_MCP_URL": "http://192.168.1.42",
        "ESP32_MCP_TOKEN": ""
      }
    }
  }
}

If you installed into a virtualenv, use the absolute path to the esp32-mcp executable in that venv.

Environment variables:

Variable

Default

Meaning

ESP32_MCP_URL

http://esp32-mcp.local

Base URL of the board

ESP32_MCP_TOKEN

empty

Sent as X-Api-Token when set

ESP32_MCP_TIMEOUT

5.0

HTTP timeout in seconds

Flags --url, --token, --timeout and --verbose override the environment. Logs go to stderr so they never corrupt the protocol stream.

Tools

Tool

Arguments

What it does

esp32_health

none

Firmware version, IP, uptime, free heap and the relay table

esp32_read_sensors

none

Chip temperature plus every configured analog channel

esp32_set_relay

relay (1 to 8), state (on or off)

Switches one relay, idempotent

esp32_gpio_read

pin (0 to 39)

Reads pin mode and digital level

esp32_gpio_write

pin, optional value (0 or 1), optional mode (output, input, input_pullup)

Changes a pin mode and optionally drives it

Agents call esp32_health first to learn which relays and pins exist. Bad arguments come back as a tool result with isError: true and a message that says what to fix. An unknown tool name is a JSON-RPC -32602 error, which is what the MCP spec asks for.

Only pins in the firmware allow list can be touched. The default list leaves out the flash pins and the strapping pins.

HTTP API on the board

The server and the firmware share this contract. It is small on purpose so the sketch stays readable.

Method

Path

Response

GET

/health

{"ok":true,"device":"esp32-mcp","firmware":"0.1.0",...,"relays":[{"index":1,"name":"relay1","pin":16,"state":"off"}]}

GET

/sensors

{"ok":true,"sensors":[{"id":"chip_temp","value":41.5,"unit":"c"},{"id":"soil_moisture","pin":34,"raw":1820,"millivolts":1466,"value":44.4,"unit":"percent"}]}

GET or POST

/relay/{n}/on

{"ok":true,"relay":1,"name":"relay1","pin":16,"state":"on"}

GET or POST

/relay/{n}/off

{"ok":true,"relay":1,"name":"relay1","pin":16,"state":"off"}

GET

/gpio?pin=N

{"ok":true,"pin":2,"mode":"output","value":1}

POST

/gpio?pin=N&value=0|1

Same shape, sets the level

POST

/gpio?pin=N&mode=output|input|input_pullup

Same shape, sets the mode

Errors use the HTTP status plus {"ok":false,"error":"..."}: 400 for bad parameters, 401 for a bad token, 403 for a pin outside the allow list, 404 for an unknown relay or route, 405 for the wrong method.

Protocol details

The MCP stdio transport is newline delimited JSON-RPC 2.0, one message per line, UTF-8. This server implements:

  • initialize with protocol version 2024-11-05, tools capability and server instructions

  • notifications/initialized

  • ping

  • tools/list

  • tools/call

Anything else returns -32601 Method not found. Malformed JSON returns -32700 and the stream stays open. Notifications never get a response.

Tests

python -m pytest

88 tests covering the JSON-RPC layer, tool schemas and dispatch, the HTTP client, the simulator contract and the real subprocess stdio transport. No hardware and no network access required; the simulator binds to 127.0.0.1 on a random port.

There is also a compatibility check against the official MCP Python SDK, which is a separate optional install:

python -m pip install mcp
python scripts/interop_check.py

It starts the simulator, spawns the server through the SDK stdio client and checks the handshake, the tool list, a successful relay call and a rejected pin. I ran it against mcp 2.3.0 and it passes. It is not part of CI because the SDK would be a heavy dev dependency.

Security

The board API is plain HTTP with an optional shared token. That is fine on a trusted LAN and wrong on the public internet. Do not port forward the ESP32, and do not put this behind a public reverse proxy without TLS and real authentication.

Relays can switch mains voltage. Use an opto-isolated relay module, keep the mains side away from the logic side, and read docs/wiring.md before connecting anything.

Deferred

  • TLS and per client credentials

  • PWM and analog writes, servo control

  • Sensor drivers (DHT22, DS18B20, I2C) beyond the internal temperature and raw ADC channels

  • OTA firmware updates

  • A sketch compile job in CI. The ESP32 core is about a 1 GB download, so for now the check is a local compile

  • Newer MCP protocol revisions, the server pins 2024-11-05 for now

  • An interop job in CI against the official MCP SDK, the check currently runs by hand

License

MIT. See LICENSE.

Built by Stefan Yasin (https://stefanyasin.github.io/)

Available Tools

5 tools
esp32_gpio_readA

Read the mode and digital level of one GPIO pin. Only pins in the firmware allow list can be read.

Inputs: pin (required).

esp32_gpio_writeA

Set a GPIO pin mode (output, input, input_pullup) and optionally drive it high or low. Pass value only, mode only, or both. Only pins in the firmware allow list can be changed.

Inputs: pin (required), mode, value.

esp32_healthA

Read board health: firmware version, IP address, hostname, uptime, free heap and the relay table (index, name, pin, state). Call this first to learn what the board exposes.

Inputs: no named parameters.

esp32_read_sensorsA

Read every sensor configured in the firmware: the internal chip temperature and each analog channel, with raw ADC counts, millivolts and a scaled value.

Inputs: no named parameters.

esp32_set_relayA

Switch one relay on or off. Relay indexes are 1 based and are listed by esp32_health. The call is idempotent.

Inputs: relay (required), state (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 5 tools

    Disambiguation4/5

    Each tool has a distinct action: health (board identity/diagnostics), read_sensors (sensor values), set_relay (relay control), gpio_read, gpio_write. The only mild overlap is that esp32_health also reports the relay table and health metrics while esp32_read_sensors reports chip temperature, but the descriptions make the boundary clear enough.

    Naming Consistency4/5

    All tools share the esp32_ prefix and a consistent snake_case form. Minor deviation: read_sensors/set_relay use verb_noun ordering while gpio_read/gpio_write use noun_verb, though the gpio pair is internally consistent and readable.

    Tool Count5/5

    Five tools is a tight, well-scoped surface for a microcontroller: one discovery, one sensor read, one relay control, and a GPIO read/write pair. Nothing feels redundant or padded.

    Completeness4/5

    Core lifecycle is covered: discovery, sensor reading, relay actuation, and GPIO read/write. Gaps exist for reset/reboot, OTA/firmware update, and analog/PWM output, but these are minor and can be worked around for typical use.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Bridges the Model Context Protocol (MCP) with ESP32 devices running Tasmota firmware, enabling LLMs to send structured commands like toggling relays or reading sensors via HTTP.
      1
      ISC
    • A
      license
      Not graded
      quality
      B
      maintenance
      ESP32-based Streamable HTTP MCP server with embedded Lua scripting engine. Bridges Claude AI to physical devices — telescopes (INDI protocol), sensors, BLE peripherals, and TCP devices. Flash-and-run binary distribution, no build required.
      13
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      MCP server that gives AI agents direct access to IoT devices via the jettyd platform, enabling control and monitoring of ESP32 devices through natural language.
      5
      42 npm
      MIT