mcp server
lynx
Open-source pan/tilt camera for the XIAO ESP32-S3 Sense
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 25, 2026lynx
An open-source pan/tilt camera you can tell where to look. AI agents drive it over MCP, Home Assistant finds it on its own, and it tracks faces and learns your own objects without sending a frame off the device.
It runs on a Seeed XIAO ESP32-S3 Sense with two MG90S servos and eight 3D-printed parts. One USB-C cable powers it, and one screw type holds it together. About $43 in parts (cost breakdown).
Site: https://alexnodeland.github.io/lynx/ · Web flasher · Build guide (3D) · Docs
What it does
Agents: an MCP server on the camera
The firmware is an MCP server (Streamable HTTP) and a REST API. Give an agent the URL and the device token, and it can aim the head, take a snapshot with the detections drawn on it, find faces, save presets, run a patrol, and read the microSD recordings.
claude mcp add --transport http lynx http://lynx-xxxx.local/mcp --header "Authorization: Bearer <token>"
you: look at the desk and take a picture →
look(pan: -40, tilt: 30)→snapshot(annotate: true)agent: A laptop, a mug and a plant. One person, seated on the left.
Tools: look · center · sweep · snapshot · detect · follow · preset · patrol · recordings · set_camera · stream_url · status. Full list and the REST API: firmware/lynx.
Home Assistant: discovered, not configured
Add this repo to HACS as a custom integration and lynx shows up under Discovered (zeroconf). Paste the token and you get the camera with live view, pan and tilt sliders, occupancy and a person count, follow and detection switches, presets and patrol, microSD recording, and a firmware update entity that installs GitHub releases over Wi-Fi.
# look at the front door when it opens
triggers:
- trigger: state
entity_id: binary_sensor.front_door
to: "on"
actions:
- action: lynx.look
target: { entity_id: camera.lynx_42e0 }
data: { pan: 60, tilt: 50 }
Entities and more automations: custom_components/lynx.
Face tracking, on the device
ESP-DL finds human faces or cat faces at about 4 fps on a scaled copy of each frame, so the stream keeps its 20 fps. Follow mode keeps the nearest face centred with small no-overshoot moves, holds where it last saw one, then glides back to rest. It's detection, not recognition, and nothing leaves the camera unless you ask for it over the API.
Your own detector
Anything else (a package, your dog, a whole person) comes from Edge Impulse. lynx collects its own training set by scanning the room, and one command installs the export:
just capture mug # lynx pans the room and saves 30 shots to datasets/mug/
# label and train in Edge Impulse (FOMO boxes or a classifier), export as an Arduino library
just model-add export.zip # now `model: custom` in the API, MCP and Home Assistant
Walkthrough: docs/custom-models.md.
Also
Over-the-air updates that roll themselves back if a new build fails · up to 4 viewers at once and pushed status events · 8 named presets and a patrol tour · snapshots on detection and a timelapse on microSD · a first-boot Wi-Fi hotspot with a setup page · a per-device token.
Repository
| Path | Contents |
|---|---|
firmware/lynx | PlatformIO firmware: motion, camera and MJPEG stream, REST API, MCP server, detection and follow, OTA, setup hotspot |
custom_components/lynx | Home Assistant integration (HACS) |
hardware/production | Print-ready STL/STEP, assembly STEP, BOM |
hardware/electronics | Circuit, nets, parts, wiring harness, carrier PCB (KiCad) |
web | three.js landing page, build guide and web flasher (Vite + GSAP) |
tools/ml | Training-image capture for custom models |
media | Renders and screenshots |
Specs
| Controller | Seeed XIAO ESP32-S3 Sense (camera, microSD, Wi-Fi) |
| Actuators | 2 × MG90S micro servo — pan on D0 (GPIO1), tilt on D1 (GPIO2), 220 Ω series resistors |
| Travel | pan ±90°, tilt ±60° mechanical; firmware soft limits pan ±88°, tilt ±58° |
| Motion | eased "glance" moves, default max speed 300 °/s, PWM detached when idle |
| Power + data | one USB-C cable via a panel socket in the base (Adafruit 6069); supply ≥ 1 A |
| Decoupling | 470 µF 6.3 V bulk + 100 nF across the servo 5 V rail |
| Fasteners | 9 × M2 × 8 thread-forming, pan head — the only screw |
| Printed parts | 8 (base, lid, battery tray, yoke, pan base, pan cap, servo shroud, light pipe) |
| Control | on your network: REST API, MCP server for AI agents, MJPEG stream (port 81), dashboard; per-device token; serial console |
| Setup | first-boot Wi-Fi hotspot lynx-xxxx with a captive setup page; lynx-xxxx.local via mDNS |
| Detection | on-device faces or cat faces (ESP-DL), ~4 fps; custom Edge Impulse models; follow mode |
| Rest pose | pan 0°, tilt 45°: the lens leans toward the front (tilt 0° points it straight up) |
| Battery | optional 150 mAh LiPo keeps the camera up; servos run on USB power only |
System
flowchart LR
USB["USB-C supply (≥ 1 A)"] --> SOCK["Panel socket<br/>Adafruit 6069"]
SOCK --> XIAO["XIAO ESP32-S3 Sense"]
BAT["LiPo 150 mAh<br/>(optional)"] -.-> XIAO
XIAO -- "5V / GND" --> RAIL["Servo rail<br/>470 µF + 100 nF"]
XIAO -- "D0 → 220 Ω" --> PAN["Pan servo<br/>MG90S"]
XIAO -- "D1 → 220 Ω" --> TILT["Tilt servo<br/>MG90S"]
RAIL --> PAN
RAIL --> TILT
XIAO -. "Wi-Fi: MCP" .- AGENT["AI agent"]
XIAO -. "Wi-Fi: REST + events" .- HA["Home Assistant"]
XIAO -. "Wi-Fi: dashboard + MJPEG" .- CLIENT["Browser"]
Gallery
| Exploded: eight parts, one screw type | Assembled on the pan base |
| One USB-C cable into the base | microSD slot above the USB-C port |
| The landing page | Its motion section |
Parts
About $43 per camera, $55 with the battery and a microSD card, and about $80 for your first build because the small parts come in packs (breakdown). Full list with suppliers in hardware/production/BOM.md.
| Qty | Part |
|---|---|
| 8 | printed parts (hardware/production/*.stl) |
| 1 | Seeed XIAO ESP32-S3 Sense |
| 2 | MG90S micro servo (Waveshare 15024) or Adafruit SG92R |
| 1 | panel-mount USB-C extension, Adafruit 6069 |
| 1 | right-angle USB-C to USB-C, thin, data-capable, 15–20 cm |
| 1 | 470 µF 6.3 V electrolytic (Panasonic EEU-FR0J471) + 100 nF 0603 |
| 2 | 220 Ω resistor |
| 9 | M2 × 8 thread-forming screw, pan head |
| 1 | 12.7 × 3.17 mm N42 disc magnet (K&J D82) |
| — | optional: 150 mAh LiPo (Adafruit 1317), microSD card |
Quick start
1. Print
Print the three test-*.stl fit coupons first, then the eight parts in hardware/production. Orientation, supports and materials per part are in the BOM; defaults are 0.2 mm layers and 3 walls.
2. Assemble
The interactive build guide walks all 13 steps in 3D: parts fly in, the rest of the camera stays ghosted, and you can orbit the model at every step. The same steps as text:
Follow the ten steps in the BOM's assembly section. Wire the servos as in hardware/electronics — carrier PCB or soldered harness, same nets.
3. Flash
The easiest way is from a browser: open the web flasher in Chrome or Edge, plug in over USB-C, and click Install. Or with PlatformIO:
cd firmware/lynx
pio run -e xiao -t upload # build and flash over USB-C
pio device monitor -b 115200 # serial console: try `info`, `center`, `sweep`
Calibrate before fixing the horns: see firmware/lynx.
4. Connect
On first boot lynx opens a Wi-Fi hotspot, lynx-xxxx. Join it from a phone, pick your network, and save the token the setup page shows. Then open http://lynx-xxxx.local/ for the live view and controls, or hook up an AI agent:
claude mcp add --transport http lynx http://lynx-xxxx.local/mcp --header "Authorization: Bearer <token>"
The full API and MCP tool list are in firmware/lynx.
Home Assistant: add this repo to HACS as a custom integration, restart, and lynx shows up under Discovered. Paste the token and you're done. See custom_components/lynx.
5. Run the site locally
cd web
npm ci
npm run dev
Develop
just setup # once: PlatformIO + HA test harness in .venv, web deps, git hooks
just check # lint, 71 firmware tests, XIAO build, 35 Home Assistant tests, site build
just # every other recipe: fw-flash, fw-monitor, web-dev, hass-test, look, snap, mcp-add, release …
Contributing · Architecture · Troubleshooting · Changelog
Licences
- Hardware: CERN-OHL-S-2.0
- Firmware and web: MIT
- Docs and media: CC-BY-SA-4.0
The licence texts are in LICENSES/. The root LICENSE is the MIT text for the software, so GitHub and HACS can detect it; NOTICE maps each folder to its licence.