Minimal MCP JSON-RPC server helpers for CircuitPython projects using adafruit_httpserver.
This library lets a CircuitPython board run a real MCP server directly on the device. The host computer is only used for deployment, serial logs, and testing.
Implemented HTTP endpoints:
GET /: JSON statusPOST /mcp: MCP JSON-RPC over HTTPGET /mcp: method-not-allowed explanationDELETE /mcp: method-not-allowed explanationOPTIONS /mcp: permissive CORSGET /sseandPOST /messages: legacy SSE compatibility when the installedadafruit_httpserverhasSSEResponse
This repository is ready to clone and install from a local file path with circup.
The bundle command:
circup install circuitpython_mcp_serverwill work after this library is accepted into a CircuitPython bundle that circup knows about.
Clone the repo:
git clone https://github.com/speccy88/circuitpython-mcp-server.git
cd circuitpython-mcp-serverInstall the core dependency:
circup --path /Volumes/CIRCUITPY install adafruit_httpserverInstall this library from the local checkout:
circup --path /Volumes/CIRCUITPY install ./circuitpython_mcp_server.pyOn Linux, replace /Volumes/CIRCUITPY with /media/$USER/CIRCUITPY or /run/media/$USER/CIRCUITPY. On Windows, use the CIRCUITPY drive letter.
Copy the module to the board:
cp circuitpython_mcp_server.py /Volumes/CIRCUITPY/lib/Then install adafruit_httpserver with circup.
Create an adafruit_httpserver.Server, pass it to MCPServer, register tool callbacks, then call poll() in the main loop.
from adafruit_httpserver import Server
from circuitpython_mcp_server import MCPServer, schema_object, tool_result
http_server = Server(pool, debug=False)
mcp = MCPServer(
http_server,
name="example-board",
title="Example Board MCP Server",
version="0.1.0",
instructions="Controls hardware on this CircuitPython board.",
)
def hello(arguments):
return tool_result("Hello from CircuitPython.", {"ok": True, "action": "hello"})
mcp.add_tool("hello", "Return a greeting.", schema_object(), hello, title="Hello")
mcp.start(ip_address, port=5000)
while True:
mcp.poll()Tool callbacks receive the JSON object from params.arguments. Return a valid MCP tool result, usually built with tool_result(). Raise ValueError or ToolError for validation failures; the library maps them to JSON-RPC -32602.
This library is installed on the CircuitPython board, not in Hermes. Hermes connects to the board as an HTTP MCP server after the board prints its MCP URL on serial.
Reference: Use MCP with Hermes.
If Hermes was installed with the standard installer, MCP support is already included. If Hermes was installed without extras, add MCP support:
cd ~/.hermes/hermes-agent
uv pip install -e ".[mcp]"Install and run the Fruit Jam NeoPixel demo first. Watch serial at 115200 baud until the board prints:
MCP streamable HTTP URL: http://FRUITJAM_IP:5000/mcp
MCP legacy SSE URL: http://FRUITJAM_IP:5000/sse
Verify the URL from the same machine that runs Hermes:
curl http://FRUITJAM_IP:5000/
python3 tools/test_fruitjam_neopixels.py --url http://FRUITJAM_IP:5000/mcpDo not continue until this works. Hermes cannot connect if the board IP is unreachable from the Hermes host.
Edit Hermes config:
hermes config editAdd this under mcp_servers in ~/.hermes/config.yaml, replacing FRUITJAM_IP with the IP printed by the board:
mcp_servers:
fruitjam_neopixels:
url: "http://FRUITJAM_IP:5000/mcp"
connect_timeout: 20
timeout: 30
tools:
include:
- neopixels_fill
- neopixel_set
- neopixels_clear
- neopixels_brightness
- neopixels_status
resources: false
prompts: falseUse fruitjam_neopixels as the server name because Hermes prefixes MCP tools as mcp_<server>_<tool>. With this config, expected registered tool names include:
mcp_fruitjam_neopixels_neopixels_fillmcp_fruitjam_neopixels_neopixel_setmcp_fruitjam_neopixels_neopixels_clearmcp_fruitjam_neopixels_neopixels_brightnessmcp_fruitjam_neopixels_neopixels_status
The include list uses the original MCP tool names from the board, not the mcp_... prefixed Hermes names.
If your Hermes build expects legacy SSE for this server, use the serial fallback URL instead:
mcp_servers:
fruitjam_neopixels:
url: "http://FRUITJAM_IP:5000/sse"
connect_timeout: 20
timeout: 30
tools:
include:
- neopixels_fill
- neopixel_set
- neopixels_clear
- neopixels_brightness
- neopixels_status
resources: false
prompts: falseStart Hermes:
hermes chatIf Hermes is already running, reload MCP servers inside the chat:
/reload-mcp
Ask Hermes:
Tell me which MCP-backed tools are available right now.
You should see the mcp_fruitjam_neopixels_... tools listed. If no tools appear, check:
- the board still responds at
http://FRUITJAM_IP:5000/ - the configured URL is
/mcp, or/sseif needed ~/.hermes/config.yamlindentation is valid YAML- the server entry is not set to
enabled: false - the tool names in
tools.includeare the unprefixed board tool names
Use direct prompts first:
Use the Fruit Jam NeoPixel MCP server to set all five NeoPixels purple.
Then verify status:
Use the Fruit Jam NeoPixel MCP server to report the current pixel state and brightness.
Leave the board off when done:
Use the Fruit Jam NeoPixel MCP server to clear all NeoPixels.
This demo only exposes five hardware tools. Keep the tools.include allowlist anyway. Hermes supports per-server filtering, and using an allowlist is the safer default when an MCP server controls physical hardware.
The demo in examples/fruitjam_neopixel_mcp_demo.py exposes the five onboard Adafruit Fruit Jam NeoPixels as MCP tools.
Tools:
neopixels_fillneopixel_setneopixels_clearneopixels_brightnessneopixels_status
The recommended demo path is the installer script. It detects the CIRCUITPY drive, copies this library into CIRCUITPY/lib, backs up any existing code.py, preserves settings.toml, and optionally installs CircuitPython dependencies with circup.
python3 tools/install_fruitjam_demo.pyUse an explicit drive if auto-detection is not correct:
python3 tools/install_fruitjam_demo.py --drive /Volumes/CIRCUITPYIf you already installed the CircuitPython dependencies:
python3 tools/install_fruitjam_demo.py --skip-depsThe installer rejects paths that escape the board root. It should not be used as a general local filesystem writer.
Install demo dependencies:
circup --path /Volumes/CIRCUITPY install \
adafruit_httpserver \
adafruit_connection_manager \
adafruit_esp32spi \
adafruit_bus_device \
neopixel \
adafruit_ticks \
adafruit_pixelbufInstall this MCP library:
circup --path /Volumes/CIRCUITPY install ./circuitpython_mcp_server.pyCreate /Volumes/CIRCUITPY/settings.toml:
CIRCUITPY_WIFI_SSID = "your-wifi-ssid"
CIRCUITPY_WIFI_PASSWORD = "your-wifi-password"Back up any existing board code, then copy the demo:
if [ -f /Volumes/CIRCUITPY/code.py ]; then
cp /Volumes/CIRCUITPY/code.py /Volumes/CIRCUITPY/code.py.bak.$(date +%Y%m%d-%H%M%S)
fi
cp examples/fruitjam_neopixel_mcp_demo.py /Volumes/CIRCUITPY/code.py
syncOpen serial at 115200 baud and look for:
MCP streamable HTTP URL: http://FRUITJAM_IP:5000/mcp
MCP legacy SSE URL: http://FRUITJAM_IP:5000/sse
Smoke test from your computer:
python3 tools/test_fruitjam_neopixels.py --url http://FRUITJAM_IP:5000/mcpInstall development dependencies in a local environment:
python3 -m pip install -e ".[dev]"Run tests:
pytestRun lint:
ruff check .If the Fruit Jam demo repeatedly prints:
ESP32 timed out on SPI select
the ESP32-C6 AirLift/NINA firmware may be missing or stale. Use the latest non-debug Fruit Jam C6 firmware from:
https://github.com/adafruit/nina-fw/releases/latest
The validated firmware asset for the hardware demo was:
NINA_ADAFRUIT-fruitjam_c6-3.3.0.bin
Main imports:
from circuitpython_mcp_server import MCPServer
from circuitpython_mcp_server import ToolError, MCPError
from circuitpython_mcp_server import schema_int, schema_number, schema_object
from circuitpython_mcp_server import tool_resultMCPServer handles:
- MCP
initialize notifications/initializedpingtools/listtools/call- empty
resources/list - empty
prompts/list logging/setLevel- JSON-RPC error mapping
- optional SSE compatibility
MIT.
This repo is intentionally an on-board MCP server library. It is not a host-side MCP server for controlling board files or serial ports. The host-side project at neusse/Codex-Circuitpython-MCP was reviewed for operational patterns; the useful pieces adopted here are path-confined board writes, safer demo installation, clearer platform install documentation, and test coverage.