README covers installation, hardware setup, tools, prompts, and cross-server integration with mcnanovna for 3D pattern measurement. CLAUDE.md provides project guidance for Claude Code.
4.2 KiB
CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
What This Is
mcpositioner is a FastMCP server that gives LLMs direct control of an ESP32 dual-axis antenna positioner over WiFi HTTP. It exposes 5 MCP tools and 3 guided workflow prompts for moving stepper motors, homing axes via StallGuard, and orchestrating 3D antenna radiation pattern measurement in coordination with a VNA MCP server (mcnanovna).
Commands
# Run the server
uv run mcpositioner # from source
uvx mcpositioner # from installed package
python -m mcpositioner # module invocation
# Lint and format
ruff check src/
ruff format src/
# Install dependencies
uv sync
# Build/verify ESP32 firmware (requires PlatformIO)
cd firmware && pio run
# Add to Claude Code for testing
claude mcp add mcpositioner-local --scope project -- uv run --directory /home/rpm/claude/ham/nanovna/mcpositioner mcpositioner
There are no tests yet. The project has no README.
Architecture
src/mcpositioner/
__init__.py Package docstring
__main__.py python -m mcpositioner
server.py FastMCP server -- tool registration, version banner, entry point
positioner.py Async HTTP client for the ESP32 (httpx)
tools.py 5 MCP tool methods on a simple PositionerTools class
prompts.py 3 guided workflow prompts (home, configure, measure_pattern_grid)
firmware/ PlatformIO ESP32 project for antenna positioner hardware
platformio.ini Build config: espressif32, AccelStepper, TMCStepper, ESPAsyncWebServer
include/config.h Pin assignments, motor constants, TMC2209 UART config
src/main.cpp Dual-axis stepper control, HTTP API, mDNS, StallGuard homing
hardware/ KiCad 9 schematic for positioner wiring
positioner.kicad_pro Project file (JSON)
positioner.kicad_sch Root schematic -- ESP32 + 2x TMC2209 + motors + power
positioner.kicad_sym Local symbol library (6 custom module symbols)
sym-lib-table Registers local symbol lib with project
generate_kicad.py Python generator for all KiCad files (dev tool)
How tools get registered
server.py holds a flat list _TOOL_METHODS of method name strings. create_server() instantiates one PositionerTools object, iterates the list, and wraps each bound method with FunctionTool.from_function(). To add a new tool: add a public method to tools.py then add its name to _TOOL_METHODS.
HTTP client (positioner.py)
The Positioner class wraps httpx async calls to the ESP32's HTTP API:
GET /status-- theta/phi position, moving, homedPOST /move-- absolute move to theta/phiPOST /move/relative-- relative offset movePOST /home-- StallGuard sensorless homingPOST /stop-- emergency stopGET /config-- read motion parametersPOST /config-- update speed, accel, microstepping
Default host: positioner.local (mDNS). Override with MCPOSITIONER_HOST env var.
Cross-server orchestration
This server controls physical positioning only. For 3D antenna pattern measurement, the LLM orchestrates both mcpositioner and mcnanovna (VNA MCP server):
positioner_move(mcpositioner) -- position antenna at theta/phiscan(mcnanovna) -- measure S21 transmission- Repeat across theta/phi grid
- Assemble pattern dict from collected measurements
The measure_pattern_grid prompt provides step-by-step guidance for this workflow.
ESP32 firmware (firmware/)
PlatformIO project targeting ESP32. Controls 2x NEMA 17 steppers via TMC2209 drivers in UART mode. Features:
- AccelStepper for smooth acceleration profiles
- TMCStepper for UART configuration and StallGuard sensorless homing
- ESPAsyncWebServer for the HTTP API
- mDNS advertisement as
positioner.local - Pin assignments and motor constants in
include/config.h
Key conventions
- Date-based versioning:
2026.02.02in pyproject.toml - Python 3.11+, ruff at 120-char line length
httpxis a core dependency (not optional -- it IS the transport layer)- Single
PositionerToolsinstance per server lifetime - No mixin pattern -- flat class with 5 methods (single-concern server)
- Custom exception:
PositionerError - Env var:
MCPOSITIONER_HOST(defaultpositioner.local)