Content
# M5Camera MCP Firmware
Standalone ESP32 firmware that turns an M5Stack M5Camera/OV2640 module into a Wi-Fi camera, Web UI, OTA target, and MCP server for Codex or other MCP clients.
```text
MCP client
└── m5camera_mcp # camera status, captures, stream URL, OV2640 settings
```
The device exposes camera control and diagnostics over HTTP and MCP. It is a focused camera server, not a general automation controller.
## What You Get
- Dark mobile Web UI with live status, snapshot stream, capture, settings, Wi-Fi, and OTA pages.
- JSON diagnostics at `/status.json`.
- JPEG capture at `/capture.jpg`.
- Stream page at `/stream.html` using periodic snapshots.
- MCP endpoint at `/mcp` with tools for status, capture, stream URL, camera settings, and reboot.
- First-run setup AP: `m5camera-setup`.
- Optional bearer-token protection through `MCP_AUTH_TOKEN`.
## Hardware
- M5Stack M5Camera or compatible ESP32 camera board.
- OV2640 sensor.
- USB serial adapter/cable for first flash.
- 2.4 GHz Wi-Fi network for normal operation.
## Repository Layout
```text
src/ firmware modules
include/ board config, pins, optional token template
test/ PlatformIO Unity tests
skills/m5camera-mcp/ Codex skill for using this MCP server
docs/ implementation plans and source prompt
platformio.ini PlatformIO environment
partitions.csv OTA-capable flash layout
```
## Quick Start
Build the firmware:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio run
```
Flash over USB:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio run -t upload --upload-port /dev/ttyUSB1
```
Open the serial monitor:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio device monitor -p /dev/ttyUSB1 -b 115200
```
Common Linux ports are `/dev/ttyUSB0`, `/dev/ttyUSB1`, and `/dev/ttyACM0`. If upload does not auto-reset, hold `BOOT`/`IO0`, reset or reconnect power, then release after upload starts.
## First Wi-Fi Setup
1. Power on the camera.
2. If no Wi-Fi is configured, connect to AP `m5camera-setup`.
3. Open `http://192.168.4.1/`.
4. Select a Wi-Fi network, enter the password, and save.
5. Wait for reboot.
6. Open `http://m5camera.local/` or the IP printed in serial logs.
Current tested device address during development: `http://192.168.14.42/`.
## Web UI
Open:
```text
http://m5camera.local/
```
The shared header shows camera status, Wi-Fi/IP, and active frame settings. Navigation is available on every HTML page:
- `Camera`: live preview, capture, download, status summary.
- `Stream`: periodic snapshot stream with capture and download controls.
- `Settings`: frame size, orientation, JPEG quality, brightness, contrast, saturation, white balance, exposure, and gain.
- `Wi-Fi`: scan networks and save credentials.
- `OTA`: upload new firmware over Wi-Fi.
## OTA Updates
After the first USB flash, build and upload firmware from the Web UI:
```text
http://m5camera.local/ota
```
Or use curl:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio run
curl -F firmware=@.pio/build/m5camera/firmware.bin http://m5camera.local/ota
```
With bearer auth:
```bash
curl -H 'Authorization: Bearer <token>' \
-F firmware=@.pio/build/m5camera/firmware.bin \
http://m5camera.local/ota
```
## Add the MCP Server to Codex
Register the camera as a streamable HTTP MCP server:
```bash
codex mcp add m5camera_mcp --url http://192.168.14.42/mcp
```
Or, if mDNS works on your network:
```bash
codex mcp add m5camera_mcp --url http://m5camera.local/mcp
```
Check registration:
```bash
codex mcp list
codex mcp get m5camera_mcp
```
Restart Codex after adding the server so the tools appear in the session.
If `MCP_AUTH_TOKEN` is enabled:
```bash
export M5CAMERA_MCP_TOKEN='<token>'
codex mcp add m5camera_mcp \
--url http://192.168.14.42/mcp \
--bearer-token-env-var M5CAMERA_MCP_TOKEN
```
## MCP Tools
| Tool | Purpose |
| --- | --- |
| `camera_status` | Return diagnostics, IP, heap, PSRAM, frame settings, orientation, and last error. |
| `camera_capture` | Capture JPEG and return a URL or base64 payload. |
| `camera_set_params` | Apply OV2640 parameters: frame size, orientation, quality, brightness, contrast, saturation, white balance, exposure, gain. |
| `camera_stream_url` | Return MJPEG URL or periodic snapshot URL. |
| `camera_reboot` | Schedule a controller reboot. |
Example through raw JSON-RPC:
```bash
curl -s http://m5camera.local/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
Capture:
```bash
curl -s http://m5camera.local/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"camera_capture","arguments":{"return_mode":"url"}}}'
```
Tune image settings temporarily:
```bash
curl -s http://m5camera.local/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"camera_set_params","arguments":{"brightness":2,"contrast":1,"saturation":-1,"whitebal":true,"awb_gain":true,"exposure_ctrl":true,"gain_ctrl":true,"save":false}}}'
```
Use `save:true` only when you want settings to persist after reboot.
## HTTP Reference
| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/` | Main Web UI or Wi-Fi setup page. |
| `GET` | `/status` | HTML status page. |
| `GET` | `/status.json` | Machine-readable diagnostics. |
| `GET` | `/capture.html` | Capture page. |
| `GET` | `/capture.jpg` | Latest JPEG capture. |
| `GET` | `/stream.html` | Browser stream page. |
| `GET` | `/stream` | Reserved MJPEG endpoint; currently returns not implemented unless enabled. |
| `GET/POST` | `/settings` | Camera settings UI and JSON update endpoint. |
| `GET/POST` | `/ota` | OTA form and firmware upload endpoint. |
| `GET/POST` | `/wifi` | Wi-Fi setup UI and credential save endpoint. |
| `GET` | `/wifi/scan` | Wi-Fi scan JSON. |
| `POST` | `/wifi/forget` | Clear Wi-Fi credentials and reboot. |
| `POST` | `/reboot` | Schedule reboot. |
| `POST` | `/factory-reset` | Clear Wi-Fi and camera settings, then reboot. |
| `POST` | `/mcp` | MCP JSON-RPC endpoint. |
## Tests
Run PlatformIO commands sequentially. They share `.pio/build`, and parallel runs can corrupt object files during linking.
Compile tests:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio test -e m5camera --without-uploading --without-testing
```
Run the Web UI Unity test on a connected device:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio test -e m5camera --filter test_web_ui --upload-port /dev/ttyUSB1
```
After running device tests, flash the normal firmware again:
```bash
PLATFORMIO_CORE_DIR=.pio-core pio run -t upload --upload-port /dev/ttyUSB1
```
## Optional Codex Skill
This repository includes a Codex skill for operating the camera MCP server:
```text
skills/m5camera-mcp/
```
To make it auto-discoverable by local Codex sessions, symlink it into your personal skills directory:
```bash
ln -s "$(pwd)/skills/m5camera-mcp" ~/.agents/skills/m5camera-mcp
```
Then restart Codex.
## Configuration and Secrets
Default settings live in `include/config.h`. Bearer-token protection is controlled by the `MCP_AUTH_TOKEN` preprocessor macro. For a local protected build, add a build flag such as:
```ini
build_flags =
-DCORE_DEBUG_LEVEL=3
-DMCP_AUTH_TOKEN=\"change-me\"
```
Keep real tokens out of commits. `include/secrets.example.h` is only a template for local experiments unless you wire it into your own build configuration.
## Troubleshooting
| Symptom | What to try |
| --- | --- |
| Cannot find the device by `m5camera.local` | Use the serial monitor to read the IP, then open `http://<ip>/`. |
| No Wi-Fi connection | Connect to `m5camera-setup` and configure Wi-Fi again. |
| Upload fails over USB | Pass `--upload-port`, lower upload speed if needed, or manually use `BOOT`/`IO0` during reset. |
| Web UI works but Codex cannot see tools | Run `codex mcp get m5camera_mcp`, verify the URL, then restart Codex. |
| Image is upside down | Open Settings or call `camera_set_params` with `orientation:"up"`, `"down"`, `"right"`, or `"left"`. |
| Face is too dark | Try temporary settings: `brightness:2`, `contrast:1`, `saturation:-1`, with auto white balance/exposure/gain enabled. |
## License
This project is licensed under the GNU General Public License v3.0 or later. See [LICENSE](LICENSE).
SPDX identifier:
```text
GPL-3.0-or-later
```
Third-party components used by the firmware and tests allow this choice:
| Component | License | Notes |
| --- | --- | --- |
| ArduinoJson | MIT | Permissive, GPL-compatible. |
| ThrowTheSwitch Unity | MIT | Test framework only; permissive, GPL-compatible. |
| Arduino-ESP32 framework | LGPL-2.1-or-later | GNU LGPL is compatible with GPLv3. |
| PlatformIO Espressif32 platform/tooling | Apache-2.0 and tool-specific licenses | Build tooling/platform code is not part of this project's source; Apache-2.0 is GPLv3-compatible, but not GPLv2-only-compatible. |
Because Apache-2.0 is not compatible with GPLv2-only, this repository uses GPLv3-or-later rather than GPLv2-only.