# 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 ```bash # 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, homed - `POST /move` -- absolute move to theta/phi - `POST /move/relative` -- relative offset move - `POST /home` -- StallGuard sensorless homing - `POST /stop` -- emergency stop - `GET /config` -- read motion parameters - `POST /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): 1. `positioner_move` (mcpositioner) -- position antenna at theta/phi 2. `scan` (mcnanovna) -- measure S21 transmission 3. Repeat across theta/phi grid 4. 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.02` in pyproject.toml - Python 3.11+, ruff at 120-char line length - `httpx` is a core dependency (not optional -- it IS the transport layer) - Single `PositionerTools` instance per server lifetime - No mixin pattern -- flat class with 5 methods (single-concern server) - Custom exception: `PositionerError` - Env var: `MCPOSITIONER_HOST` (default `positioner.local`)