Skip to content

speccy88/circuitpython-mcp-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CircuitPython MCP Server

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 status
  • POST /mcp: MCP JSON-RPC over HTTP
  • GET /mcp: method-not-allowed explanation
  • DELETE /mcp: method-not-allowed explanation
  • OPTIONS /mcp: permissive CORS
  • GET /sse and POST /messages: legacy SSE compatibility when the installed adafruit_httpserver has SSEResponse

Status

This repository is ready to clone and install from a local file path with circup.

The bundle command:

circup install circuitpython_mcp_server

will work after this library is accepted into a CircuitPython bundle that circup knows about.

Install From This GitHub Repo

Clone the repo:

git clone https://github.com/speccy88/circuitpython-mcp-server.git
cd circuitpython-mcp-server

Install the core dependency:

circup --path /Volumes/CIRCUITPY install adafruit_httpserver

Install this library from the local checkout:

circup --path /Volumes/CIRCUITPY install ./circuitpython_mcp_server.py

On Linux, replace /Volumes/CIRCUITPY with /media/$USER/CIRCUITPY or /run/media/$USER/CIRCUITPY. On Windows, use the CIRCUITPY drive letter.

Manual Install

Copy the module to the board:

cp circuitpython_mcp_server.py /Volumes/CIRCUITPY/lib/

Then install adafruit_httpserver with circup.

Basic Usage

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.

Hermes Agent Setup

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.

1. Confirm Hermes MCP support

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]"

2. Start the Fruit Jam MCP server

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/mcp

Do not continue until this works. Hermes cannot connect if the board IP is unreachable from the Hermes host.

3. Add the board to Hermes config

Edit Hermes config:

hermes config edit

Add 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: false

Use 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_fill
  • mcp_fruitjam_neopixels_neopixel_set
  • mcp_fruitjam_neopixels_neopixels_clear
  • mcp_fruitjam_neopixels_neopixels_brightness
  • mcp_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: false

4. Start or reload Hermes

Start Hermes:

hermes chat

If Hermes is already running, reload MCP servers inside the chat:

/reload-mcp

5. Verify Hermes loaded the tools

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 /sse if needed
  • ~/.hermes/config.yaml indentation is valid YAML
  • the server entry is not set to enabled: false
  • the tool names in tools.include are the unprefixed board tool names

6. Run a first Hermes command

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.

7. Keep the tool surface narrow

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.

Fruit Jam NeoPixel Demo

The demo in examples/fruitjam_neopixel_mcp_demo.py exposes the five onboard Adafruit Fruit Jam NeoPixels as MCP tools.

Tools:

  • neopixels_fill
  • neopixel_set
  • neopixels_clear
  • neopixels_brightness
  • neopixels_status

Safer Installer

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.py

Use an explicit drive if auto-detection is not correct:

python3 tools/install_fruitjam_demo.py --drive /Volumes/CIRCUITPY

If you already installed the CircuitPython dependencies:

python3 tools/install_fruitjam_demo.py --skip-deps

The installer rejects paths that escape the board root. It should not be used as a general local filesystem writer.

Manual Demo Install

Install demo dependencies:

circup --path /Volumes/CIRCUITPY install \
  adafruit_httpserver \
  adafruit_connection_manager \
  adafruit_esp32spi \
  adafruit_bus_device \
  neopixel \
  adafruit_ticks \
  adafruit_pixelbuf

Install this MCP library:

circup --path /Volumes/CIRCUITPY install ./circuitpython_mcp_server.py

Create /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
sync

Open 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/mcp

Development

Install development dependencies in a local environment:

python3 -m pip install -e ".[dev]"

Run tests:

pytest

Run lint:

ruff check .

Fruit Jam AirLift Firmware

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

API Reference

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_result

MCPServer handles:

  • MCP initialize
  • notifications/initialized
  • ping
  • tools/list
  • tools/call
  • empty resources/list
  • empty prompts/list
  • logging/setLevel
  • JSON-RPC error mapping
  • optional SSE compatibility

License

MIT.

Audit Notes

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.

About

Minimal MCP JSON-RPC server helpers for CircuitPython

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages