MCP server
Connect a coding agent to espx mcp so it can build, flash and debug firmware on your board.
espx mcp serves the Model Context Protocol on stdio. A coding agent connected to it can create a project, build it, flash the board on your desk, read the boot log and fix what it sees, with nothing but espx. The agent works in a loop: build, flash (or run, which also watches the boot log), read the monitor, fix, repeat.
Add espx to a client
Run espx mcp install once per client:
| Client | What install does |
|---|---|
claude-code | Prints the claude mcp add command to run. espx never edits files that Claude Code's CLI owns |
codex | Prints the codex mcp add command to run |
cursor | Adds espx to ~/.cursor/mcp.json |
vscode | Adds espx to .vscode/mcp.json in the current directory |
claude-desktop | Adds espx to claude_desktop_config.json |
For the JSON files espx merges in its own entry and keeps every other key. --print shows the config instead of writing it. Restart the client afterwards so it loads espx. The entry starts the espx binary you ran install with:
The desktop app does the same for your whole user account, with Connect and Disconnect buttons per client.
Let the agent touch a board
An agent may flash, reset or erase only a board the project allows in espx.toml. By default that is the project's bound board, [board] id, and erasing is off. Create projects with a binding:
An agent does the same with project_new and board_id. A call outside the policy fails with policy_denied, and the message says which key to set. Commands you run yourself are never checked. Agent policy covers the [agent] table and the audit log.
Tools
espx serves 22 tools. Every tool carries readOnlyHint and destructiveHint annotations, and every result is a one-paragraph summary plus structured data.
| Tool | Effect | What it does |
|---|---|---|
doctor | read | Checks rustup, cargo, the tools and the Rust targets, offline |
setup | changes, job | Installs the tools and targets, and the Xtensa fork for Xtensa chips. Takes chip |
chips | read | The chip table |
board_catalog | read | Pin facts for known dev boards, or one with board |
devices | read | Attached boards: id, port, USB interface, name, open monitor session, lease holder. Touches no board |
board_info | changes | Chip, revision, crystal, flash size and MAC. Resets the board |
project_new | changes | Creates a project with esp-generate and AGENTS.md. Takes name, dir, chip, board, port, board_id, options |
project_info | read | Name, chip, target, toolchain, release binary and size, binding, resolved crates |
deps | read | The esp-hal family versions from Cargo.lock |
context | read | The AGENTS.md text plus attached boards and open monitor sessions |
build | changes, job | cargo build --release; compiler messages come back as diagnostics with file and line. Takes extra |
flash | destroys, job | Builds and flashes, then resets the board. Policy-checked. Takes port, baud, extra |
erase | destroys, job | Erases the whole flash. Needs erase = true in the policy |
reset | changes | Resets the board through the serial lines. Policy-checked |
run | destroys, job | Build, flash, open a monitor session and read the boot log. Policy-checked |
monitor_start | changes | Opens (or returns) a monitor session on a board, or plays a recording |
monitor_read | read | Reads a session, filtered and shaped |
monitor_write | changes | Sends text to the board's serial input |
monitor_stop | changes | Closes a session and releases the port |
job_status | read | A job's state, progress and last output lines |
job_wait | read | Waits for a job and returns its result |
job_cancel | changes | Stops a job; a build in progress is killed |
Most tools take dir, the project directory. It defaults to the directory espx mcp was started in, and relative paths resolve against it. Board tools take port: a serial port, a board id from devices, or a name from espx boards name. Leave it out when the project binds a board, pins a port in espflash_ports.toml, or exactly one board is attached.
Every call's arguments are checked strictly; an unknown argument is an error. A bad argument, a failed build or an espx error comes back as a tool result with isError: true, never as a JSON-RPC error, so the agent reads it. When an error caused it, the structured content holds error with a stable code (invalid_arguments for bad arguments) and a message that says what to do next. The codes are listed in Exit codes.
Builds
build (and the build step of flash and run) returns each compiler message as a diagnostic with its level, code, file, line and column. A failed build names the first error and its location in the summary, with rustc's rendered text as the body. A successful one reports the image size against the app partition, and an image that does not fit fails before anything reaches the board.
Monitoring
run and monitor_start open a monitor session, which espx reads itself: it decodes defmt and resolves panics against the project's release binary. monitor_start resets the board when the port opens, so the log starts at boot; pass reset: false to join without resetting. Pass replay with the path of an espx monitor --record file to play a recording through the same tools.
monitor_read arguments:
| Argument | Default | Meaning |
|---|---|---|
session | the only open one | Session id from monitor_start or run |
since | new | new: since the last read. boot: since the board last booted. start: everything still buffered |
cursor | Read from an earlier read's next instead | |
until | Keep reading until a line matches | |
expect | Regexes that must match in order; the read succeeds once the last one matches | |
reject | Regexes that fail the read on the first matching line, panic message or exception kind | |
wait_s | 25 with patterns, else 2 | Seconds to wait for output |
max_events | 200 | Most events to return; repeated lines count once |
max_bytes | 16384 | Most bytes of log text to return |
level | Drop lines below trace, debug, info, warn or error. Panics, resets and warnings always pass | |
module | Keep only lines whose defmt module contains this text | |
pattern | Keep only lines matching this regex |
The result is shaped for a model to read. Runs of identical lines collapse into one with ×N. Each boot shows its reason and the ROM's rst: line. Every panic or exception frame inside the project carries seven numbered source lines, the frame's line marked with >. The summary lists the boots and says so when the last one was a watchdog reset, a panic or a brownout, or when the same reason repeats, which is a reset loop.
run takes until, expect, reject and timeout_s: how long to watch after flashing, 30 seconds when there are patterns to match, else 5 seconds of boot log. A run that hits a rejected pattern, or whose expectations did not all match, is an error result. The session stays open for monitor_read.
After a flash or reset with a session open, the tool waits up to 3 seconds for the new boot and names its reason.
Jobs
setup, build, flash, erase and run can take longer than a client wants to wait. Each takes wait_s (default 25). If the work is done in time the call returns its result. Otherwise it returns {job, state: "running", tail, progress} and keeps going:
job_statusreturns the state (running,done,failedorcancelled), progress and the last output lines.job_waitwaits up to its ownwait_sand returns the same result the original call would have, orrunningagain.job_cancelstops the job.
Cancelling the starting call, or a job_wait, through MCP's notifications/cancelled cancels the job too. A cancelled build is killed with its whole process group. A flash cannot stop in the middle of a write, so a cancelled flash stops after it. A cancelled setup finishes its current download in the background.
When the request carries a progressToken, every step goes out as notifications/progress: cargo's Compiling lines, each block a flash writes, each setup step. Jobs live in memory, the newest 64, so restarting the server forgets them.
When the agent needs a person
When espx cannot get the bootloader to answer, also after one retry at 115200 baud, or the board is not on the USB bus, the result carries needs_human with an action and a message:
action | Ask the person to |
|---|---|
download_mode | Hold BOOT, tap RESET, release BOOT, then flash again |
replug | Plug the board in, or unplug and replug it, with a data cable |
Nothing else asks for a person. The server's own instructions tell the agent to ask for help only when a result carries needs_human. The desktop app reads it from the audit log and shows a banner.
Resources
| URI | Contents |
|---|---|
espx://boards | Attached boards with their id, port, lease holder and open monitor session |
espx://boards/{id}/log | The current boot's log of a board with an open session; id is the board id, its port, or the port's file name, with slashes URL-encoded |
espx://project/context | Chip, board pins, esp-hal version, the examples link and attached boards for the project espx mcp was started in |
espx://catalog/{board} | Pin facts for one known dev board |
Prompts
| Prompt | Arguments | What it asks the agent to do |
|---|---|---|
new-firmware | goal (required), board, name | Start from an empty folder: check the toolchain, create a bound project, build, take the API from the esp-hal examples, run with until set to a line that proves the goal, and fix until the board's log shows it working |
debug-crash | symptom | Read the board's own output since boot, find the panic, exception or watchdog reset, fix the cause and confirm it is gone |
port-example | example (required) | Bring an esp-hal example from the project's exact release onto this board, adapting the pins |
Sharing boards
espx mcp uses the same board hub as the CLI and the desktop app. When nothing else serves it, espx mcp serves it on ~/.espx/hub.sock, so two agents, or an agent and a terminal, share one board. Every flash, reset, erase and board-info read is leased under the client's name, espx mcp (claude-code) for example, and another holder's call fails with board_leased until it is done. If the process serving the hub quits, the server reconnects or opens the boards itself.
Every tool call is appended to ~/.espx/agent.log. espx log prints it; see Agent policy.