Install
$ agentstack add mcp-navado-esp32mcpserver ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
ESP32 MCP Server
[](https://github.com/navado/ESP32MCPServer/actions/workflows/build.yml)
A Model Context Protocol (MCP) server running on ESP32 (and Nordic nRF52840 in sensor-only mode), exposing real-world sensor data and bus protocols to AI assistants and automation tooling over WebSocket / JSON-RPC 2.0.
Connect an AI agent directly to your hardware: query I2C sensors, parse NMEA 0183 GPS/marine data, decode NMEA 2000 and OBD-II CAN frames, replay bus history — all through a standard MCP interface.
Features
| Capability | Details | |---|---| | MCP Protocol | JSON-RPC 2.0 over WebSocket, resource discovery & subscriptions | | I2C Sensors | Auto-scan + drivers for BME280, MPU6050, ADS1115, SHT31, BH1750, INA219 | | NMEA 0183 | GGA, RMC, VTG, GSV, MWV, DBT, DPT, HDG, HDT with XOR checksum validation | | NMEA 2000 / CAN | 29-bit PGNs: position, heading, speed, depth, wind, attitude, water temp | | OBD-II / CAN | Standard 11-bit OBD-II service 01/09 PID decoding (60+ PIDs) | | Bus History | Persistent ring buffers for CAN, NMEA, NMEA 2000, OBD-II — queryable via MCP | | Bus Readers | Time-bounded serial and CAN reads, raw or parsed mode, JSON output | | WiFi | AP setup UI + STA mode; credentials stored in NVS | | Discovery | mDNS (_mcp._tcp) + UDP capability broadcast | | Metrics | Heap, uptime, RSSI, histogram stats with boot persistence | | Multi-board | ESP32-S3, ESP32-C3, ESP32, Adafruit HUZZAH32, M5Stack, Nordic nRF52840-DK, Adafruit Feather nRF52840 | | Testing | Native unit tests (Unity), mock I2C / CAN / Serial / LittleFS |
Supported Boards
ESP32 Boards — Full MCP Server (WiFi + WebSocket + CAN)
| Board | PlatformIO env | MCU | Flash | RAM | WiFi | BLE | CAN | RGB LED | |---|---|---|---|---|---|---|---|---| | ESP32-S3-DevKitC-1 | esp32-s3-devkitc-1 | Xtensa LX7 × 2 @ 240 MHz | 8 MB | 512 KB + 8 MB PSRAM | ✓ | 5.0 | ✓ | GPIO48 | | ESP32-DevKitC V4 | esp32-devkitc | Xtensa LX6 × 2 @ 240 MHz | 4 MB | 520 KB | ✓ | 4.2 | ✓ | — | | ESP32-C3-DevKitM-1 | esp32-c3-devkitm-1 | RISC-V @ 160 MHz | 4 MB | 400 KB | ✓ | 5.0 | ✓ | GPIO8 | | Adafruit HUZZAH32 | adafruit-huzzah32 | Xtensa LX6 × 2 @ 240 MHz | 4 MB | 520 KB | ✓ | 4.2 | ✓ | — | | M5Stack Core ESP32 | m5stack-core | Xtensa LX6 × 2 @ 240 MHz | 16 MB | 520 KB | ✓ | 4.2 | ✗ | — |
nRF52840 Boards — Sensor Only (I2C + Serial, no WiFi)
| Board | PlatformIO env | MCU | Flash | RAM | BLE | |---|---|---|---|---|---| | Nordic nRF52840-DK | nrf52840-dk | Cortex-M4F @ 64 MHz | 1 MB | 256 KB | 5.0 + 802.15.4 | | Adafruit Feather nRF52840 | nrf52840-feather | Cortex-M4F @ 64 MHz | 1 MB | 256 KB | 5.0 |
> nRF52840 targets run a separate main_nrf.cpp entry point: sensors + I2C + Serial output only. No WiFi, no MCP WebSocket, no LittleFS, no bus history. For full MCP-over-WiFi, bridge via an ESP32-C3 in AT-command mode connected to UART1.
Board Feature Matrix
| Feature | ESP32-S3 | ESP32 V4 | ESP32-C3 | HUZZAH32 | M5Stack | nRF52840-DK | nRF52840-Feather | |---|---|---|---|---|---|---|---| | WiFi | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | | BLE | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | CAN / TWAI | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | | I2C | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | UART (ext) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | LittleFS | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | | MCP Server | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | | Bus History | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | | mDNS / UDP discovery | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
Architecture
flowchart TD
Client[AI Agent / MCP Client] -->|WebSocket :9000 JSON-RPC 2.0| MCPServer
subgraph ESP32
MCPServer -->|resources/list| Resources
MCPServer -->|sensors/i2c/scan| SensorMgr
MCPServer -->|sensors/read| SensorMgr
MCPServer -->|buses/serial/read| BusReader
MCPServer -->|buses/can/read| BusReader
MCPServer -->|bus/history/read| BusHistory
MCPServer -->|bus/history/config/*| BusHistory
SensorMgr -->|Wire I2C| I2CSensors[BME280 · MPU6050 · ADS1115\nSHT31 · BH1750 · INA219]
BusReader -->|UART| NMEAParser[NMEA 0183 Parser]
BusReader -->|CAN TWAI| CANParser[NMEA 2000 / OBD-II Parser]
BusReader -->|feed| BusHistory[Bus History\nRing Buffers]
BusHistory --> LittleFS
NetworkMgr[Network Manager] --> WiFi
NetworkMgr --> WebSetup[Setup UI :80]
DiscoveryMgr[Discovery Manager] -->|mDNS + UDP| Network
Metrics[Metrics System] --> LittleFS
uLogger --> LittleFS
end
MCPServer --> Metrics
MCPServer --> uLogger
Hardware
Pin Assignments (ESP32-S3-DevKitC-1)
| GPIO | Function | Connected to | |---|---|---| | IO8 | I2C SDA | All I2C sensors | | IO9 | I2C SCL | All I2C sensors | | IO16 | UART1 RX | GPS / chart plotter TX | | IO17 | UART1 TX | GPS / chart plotter RX (optional) | | IO4 | CAN RX | TJA1050 / SN65HVD230 CRXD | | IO5 | CAN TX | TJA1050 / SN65HVD230 CTXD |
nRF52840-DK Pin Assignments
| Pin | Function | |---|---| | P0.26 / D6 | I2C SDA | | P0.27 / D7 | I2C SCL | | P0.06 / TX | UART1 TX | | P0.08 / RX | UART1 RX |
Bill of Materials
| # | Part | Purpose | |---|---|---| | 1 | ESP32-S3-DevKitC-1 | Main MCU | | 1 | BME280 breakout | Temperature / Humidity / Pressure | | 1 | MPU6050 breakout | 6-axis IMU (optional) | | 1 | TJA1050 or SN65HVD230 | CAN bus transceiver (optional) | | 1 | GPS module (3.3 V UART) | NMEA 0183 position/time (optional) | | 2 | 4.7 kΩ resistors | I2C pull-ups | | — | Breadboard + jumpers | |
ESP32-S3-DevKitC-1 Wiring
┌──────────────────────────────────────┐
│ ESP32-S3-DevKitC-1 │
│ │
3V3 ───┤ 3V3 GND ├─── GND
GND ───┤ GND IO0 ├
│ │
I2C SDA ── IO8 ───┤ IO8 ┌──────────┐ IO17 ├─── UART1 TX → GPS
I2C SCL ── IO9 ───┤ IO9 │ ESP32 │ IO16 ├─── UART1 RX ← GPS
│ IO10 │ S3 │ IO15 ├
│ IO11 └──────────┘ IO14 ├
│ IO12 IO13 ├
│ IO3 IO4 ├─── CAN RX ← TJA1050
│ IO46 IO5 ├─── CAN TX → TJA1050
│ │
│ USB (flash / serial log) │
└───────────────────────────────┘
I2C Sensor Wiring
All sensors share one bus. Add 4.7 kΩ pull-ups from SDA and SCL to 3V3.
ESP32-S3 BME280 MPU6050 ADS1115 INA219
───────── ─────── ─────── ─────── ───────
IO8 (SDA)─────SDA──────────SDA──────────SDA──────────SDA
IO9 (SCL)─────SCL──────────SCL──────────SCL──────────SCL
3V3 ──────────VCC──────────VCC──────────VCC──────────VCC
GND ──────────GND──────────GND──────────GND──────────GND
SDO→GND AD0→GND ADDR→GND
(0x76) (0x68) (0x48) (0x40)
Other supported sensors (same bus wiring): SHT31 (0x44), BH1750 (0x23).
NMEA 0183 Wiring (GPS / Chart Plotter)
GPS / chart plotter ESP32-S3
─────────────────── ────────
TX (3.3 V TTL NMEA out) ─────── IO16 (UART1 RX)
RX (optional NMEA in) ─────── IO17 (UART1 TX)
GND ─────── GND
VCC (3.3 V) ─────── 3V3
> If the device outputs RS-232 levels, use a MAX3232 level shifter. Never connect RS-232 directly to ESP32 GPIO.
CAN Bus Wiring (NMEA 2000 / OBD-II)
TJA1050 / SN65HVD230 ESP32-S3
──────────────────── ────────
CTXD ─────────── IO5 (CAN TX)
CRXD ─────────── IO4 (CAN RX)
VCC (5 V for TJA1050) ─────────── VIN / external 5 V
GND ─────────── GND
CANH ──────────────── NMEA 2000 backbone / OBD-II CANH
CANL ──────────────── NMEA 2000 backbone / OBD-II CANL
> NMEA 2000 runs at 250 kbps. OBD-II runs at 500 kbps. Set the baud rate in your CANBusReader configuration accordingly.
Software Prerequisites
- PlatformIO Core CLI or the PlatformIO IDE plugin for VS Code
- Python 3.8+
- Git
pip install platformio
Build & Flash
# 1. Clone
git clone https://github.com/navado/ESP32MCPServer.git
cd ESP32MCPServer
# 2. Install library dependencies
pio pkg install
# 3. Upload web assets to LittleFS (optional but recommended)
pio run -t uploadfs -e esp32-s3-devkitc-1
# 4. Build firmware
pio run -e esp32-s3-devkitc-1
# 5. Flash
pio run -t upload -e esp32-s3-devkitc-1
# 6. Monitor
pio device monitor --baud 115200
nRF52840
pio run -e nrf52840-dk
pio run -t upload -e nrf52840-dk
First Run
- Power on the board. It broadcasts a WiFi AP:
ESP32_XXXXXX(last 6 hex digits of the MAC address). - Connect to that AP and open
http://192.168.4.1. - Enter your WiFi credentials and click Save.
- The board joins your network and prints its IP address on the serial console.
- Connect an MCP client to
ws://:9000.
Web Dashboard
A browser-based dashboard is served from flash at http:///app.
| Tab | What it does | |---|---| | Sensors | Scan I²C bus, display detected devices, read values, auto-refresh | | Metrics | List all metrics, live values, sparkline history chart | | Logs | Query and clear metric log entries | | MCP Console | Interactive JSON-RPC console with preset requests and live message log |
HTTP Endpoints (port 80)
| Method | Path | Description | |---|---|---| | GET | / | WiFi setup page | | POST | /save | Save WiFi credentials (ssid, password form params) | | GET | /status | Network status JSON | | WS | /ws | Real-time network status WebSocket | | GET | /app | Browser dashboard | | GET | /bus-history | Read bus history config JSON | | POST | /bus-history | Update bus history config JSON |
MCP API Reference (WebSocket port 9000)
All methods use JSON-RPC 2.0 over WebSocket.
Protocol
| Method | Params | Description | |---|---|---| | initialize | — | Server info, protocol version, capabilities | | resources/list | — | List registered MCP resources | | resources/read | uri | Read a resource by URI | | resources/subscribe | uri | Subscribe to push updates for a resource | | resources/unsubscribe | uri | Cancel a subscription |
Sensors
| Method | Params | Description | |---|---|---| | sensors/i2c/scan | — | Scan I²C bus; auto-detect and initialise drivers | | sensors/read | sensorId? | Read all channels of one sensor (omit ID to read all) |
sensors/i2c/scan
{"jsonrpc":"2.0","method":"sensors/i2c/scan","id":1}
{
"jsonrpc":"2.0","id":1,
"result":{"devices":[
{"address":118,"name":"BME280","supported":true,"parameters":["temperature","pressure","humidity"]},
{"address":104,"name":"MPU6050","supported":true,"parameters":["accel_x","accel_y","accel_z","gyro_x","gyro_y","gyro_z","temperature"]}
]}
}
sensors/read
{"jsonrpc":"2.0","method":"sensors/read","id":2,"params":{"sensorId":"bme280_0x76"}}
{
"jsonrpc":"2.0","id":2,
"result":{"readings":[
{"sensorId":"bme280_0x76","parameter":"temperature","value":22.4,"unit":"°C","timestamp":1234567890,"valid":true},
{"sensorId":"bme280_0x76","parameter":"humidity","value":58.1,"unit":"%RH","timestamp":1234567890,"valid":true},
{"sensorId":"bme280_0x76","parameter":"pressure","value":101320.0,"unit":"Pa","timestamp":1234567890,"valid":true}
]}
}
Bus History
Bus history accumulates CAN frames, NMEA lines, decoded NMEA 2000 messages, and OBD-II records in circular RAM buffers. The buffers are sized from a configurable RAM budget and their configuration is persisted to LittleFS.
| Method | Params | Description | |---|---|---| | bus/history/read | type, limit? | Read accumulated messages from history buffers | | bus/history/config/get | — | Read current buffer sizing configuration | | bus/history/config/set | config fields | Resize buffers and persist configuration |
bus/history/read
type must be one of: can, nmea, nmea2000, obdii. limit defaults to all stored entries.
{"jsonrpc":"2.0","method":"bus/history/read","id":3,"params":{"type":"can","limit":10}}
{
"jsonrpc":"2.0","id":3,
"result":{
"type":"can",
"frames":["7E8#0441050300000000","7E8#04410D0000000000"],
"count":2,
"capacity":500
}
}
{"jsonrpc":"2.0","method":"bus/history/read","id":4,"params":{"type":"nmea2000"}}
{
"jsonrpc":"2.0","id":4,
"result":{
"type":"nmea2000",
"messages":[
{"pgn":129025,"priority":2,"source":0,"name":"Position Rapid Update",
"valid":true,"hasPosition":true,"latitude":47.6097,"longitude":-122.3331}
],
"count":1,"capacity":200
}
}
{"jsonrpc":"2.0","method":"bus/history/read","id":5,"params":{"type":"obdii"}}
{
"jsonrpc":"2.0","id":5,
"result":{
"type":"obdii",
"records":[
{"service":1,"pid":12,"name":"Engine RPM","value":1500,"unit":"rpm","valid":true},
{"service":1,"pid":13,"name":"Vehicle Speed","value":65,"unit":"km/h","valid":true}
],
"count":2,"capacity":300
}
}
bus/history/config/get
{"jsonrpc":"2.0","method":"bus/history/config/get","id":6}
{
"jsonrpc":"2.0","id":6,
"result":{
"canFrameCount":500,
"nmeaLineCount":1000,
"nmea2000Count":200,
"obdiiCount":300,
"ramBudgetBytes":1048576,
"safetyMarginBytes":65536,
"freeHeapBytes":2097152
}
}
bus/history/config/set
All fields are optional; omitted fields retain their current values.
{
"jsonrpc":"2.0","method":"bus/history/config/set","id":7,
"params":{"canFrameCount":1000,"ramBudgetBytes":2097152}
}
{"jsonrpc":"2.0","id":7,"result":{"ok":true,"canFrameCount":1000,"nmeaLineCount":1000,"nmea2000Count":200,"obdiiCount":300,"ramBudgetBytes":2097152,"safetyMarginBytes":65536}}
> Set canFrameCount, nmeaLineCount, nmea2000Count, obdiiCount all to 0 to enable auto-sizing: the server splits ramBudgetBytes equally among the four buffer types based on the size of each record type.
Metrics
| Method | Params | Description | |---|---|---| | metrics/list | category? | List all registered metrics (name, type, unit, category) | | metrics/get | name | Current value of one metric | | metrics/history | name, seconds? | Historical samples for one metric (default: last 300 s) |
metrics/list
{"jsonrpc":"2.0","method":"metrics/list","id":8,"params":{"category":"system"}}
{
"jsonrpc":"2.0","id":8,
"result":{"metrics":[
{"name":"system.heap.free","type":"gauge","description":"Free heap","unit":"bytes","category":"system"},
{"name":"system.uptime","type":"counter","description":"Uptime","unit":"s","category":"system"},
{"name":"system.wifi.rssi","type":"gauge","description":"WiFi signal strength","unit":"dBm","category":"system"}
]}
}
Metric types: counter (cumulative integer), gauge (current value), histogram (min/max/mean/count object).
metrics/get
{"jsonrpc":"2.0","method":"metrics/get","id":9,"params":{"name":"system.heap.free"}}
{
"jsonrpc":"2.0","id":9,
"result":{"name":"system.heap.free","type":"gauge","value":214032,"unit":"bytes","category":"system","timestamp":1234567890}
}
For histogram metrics, value is an object: {"mean":…,"min":…,"max":…,"count":…}.
metrics/history
{"jsonrpc":"2.0","method":"metrics/history","id":10,"params":{"name":"system.heap.free","seconds":60}}
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [navado](https://github.com/navado)
- **Source:** [navado/ESP32MCPServer](https://github.com/navado/ESP32MCPServer)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.