flashgate
flashgate
flashgate answers one specific question: the firmware you just built — does it actually run on the board?
It automates the whole chain: compile, flash over ST-Link, wait for the board to report its own identity, then drive the actual feature over the console and assert on hardware register readbacks. Any broken step ends with a non-zero exit code and a reason.
build → flash over ST-Link → board boots → evidence → probes → exit codeA remote agent driving the bench over the network (Wi-Fi laptop → wired bench, OpenOCD backend) — real output:
$ flashgate --board apollo-openocd.yaml verify --all-probes
[verify] apollo-h743: build -> flash -> boot banner -> probes
[build] OK in 1.3s, 0 warning(s)
[flash] Apollo.bin @ 0x08000000 via openocd:port=SWD
[flash] OK (written, verified, started)
FLASHGATE-BOOT board=apollo-h743 git=e87efd8-dirty build=2026-09-16T02:48:14Z rtos=FreeRTOS
[probe] led-demo: LED state-machine set/readback + PWM CCR sanity
step 1: led-demo> led0 breath
board: OK led0 state=BREATH
step 2: led-demo> led0?
board: OK led0 state=BREATH ccr=691 <- hardware register readback
...
[probe] led-demo: PASS (5 steps)
[record] 20260916T024814701-0-035e45ea.json (succeeded exit=0 [6/6 checks])How the board testifies
Two evidence channels, picked per bench (evidence.mode: uart / swd / auto):
The firmware prints its identity over the console UART, and also publishes a 64-byte signature (magic + git sha + build time + CRC32) at a fixed RAM address that the host reads through the ST-Link alone — no serial cable needed for boot verification:
FLASHGATE-BOOT board=apollo-h743 git=2c58bd3 build=2026-08-28T07:26:35Z rtos=FreeRTOSBoth carry the git sha plus -dirty when the tree differs from HEAD, so a
passing verify proves the board is running a build of your current HEAD
(-dirty marks uncommitted changes; it does not fingerprint their
content — the Stop hook's tree fingerprint covers that side: tracked diffs
in full, untracked files and the board profile by path + size + first
4 MiB, plus the flashgate version itself, so tightening probe
expectations or upgrading the tool invalidates a cached PASS. Files
ignored by git and nested git repositories are outside the gate). Functional probes then send real commands and assert on the answers,
including register readbacks (TIM3 CCR), not firmware self-reports:
- send: "led0?"
expect: "OK led0 state={state} ccr={ccr:d}" # mirror of the firmware's printf
assert: "state == BREATH and ccr <= 1000"Related MCP server: firmforge
Quick start
pip install -e . # Python 3.11+
pip install -e "." # MCP server included since 0.10.0
pip install -e ".[bench]" # optional remote bench (device-connect)
flashgate doctor # ST-Link / serial / toolchain sanity
flashgate doctor --export checkup.md --redact # a sendable checkup page
flashgate verify --all-probes # build → flash → evidence → sha → probes
echo $? # 0 = the board confirms it worksRequires an ST-Link; a USB-TTL adapter on the console UART adds probes.
The repo ships a complete buildable example for the ALIENTEK Apollo
STM32H743 (examples/apollo-h743/) with the Stop hook pre-wired, so a
fresh clone verifies out of the box once wired up.
Exit codes
code | meaning |
0 | verified |
1 | build failed |
2 | flash failed |
3 | board stayed silent (no banner / no signature within timeout) |
4 | error string seen on serial (HardFault, assertion) |
5 | on-board identity ≠ repo state (git sha or board name) — or the identity evidence channel could not be reset: a failed pre-start signature wipe, or a readback that ANSWERED wrongly — nonzero, or a truncated rc-0 dump (the wipe is read back and verified; a backend silently lying about it is caught) — fails closed (exit 5, start withheld); a readback that cannot RUN at all is an environment failure (exit 6). A surviving old-boot signature must never count as a pass |
6 | environment error (no ST-Link / serial / tools) — including probes explicitly required via |
7 | functional probe failed |
Contributing
Board profiles, probes, fixes and docs are welcome — see CONTRIBUTING.md for the setup and contribution terms. For a release gate that does not depend on the Stop hook (the board signs off in CI before a tag publishes), see the official GitHub Actions recipes in docs/ci-recipes.md.
The Stop hook
hooks/flashgate_stop.py is a Claude Code Stop hook (any harness
implementing the same hook contract works). When the agent tries to finish after
touching watched firmware files, the hook fingerprints the tree (plus the
board profile — editing verification semantics must not reuse a cached
green) and allows instantly if that exact state already passed hardware
verify (~0.7 s cached). Otherwise it runs the full verify on the real
board and blocks the stop on failure, feeding the agent the board's
testimony:
[flashgate] BLOCKED (attempt 1/2): firmware changes are not verified on hardware (verify rc=7).
[flashgate] last verify output:
step 1: led-demo> led0 breath
board: OK led0 state=OFF ← readback exposes the silent no-opThe same broken tree is blocked at most twice, then released with a loud warning — the session can never wedge, and a failure is never silently swallowed.
One verify at a time per bench: concurrent verifies (a user-level and a
session-level Stop hook firing on the same stop, or a manual run racing
the hook) serialize on an OS-owned lock file under the firmware's
.flashgate/ instead of fighting over the console serial port — the
second one waits (default 300 s, FLASHGATE_VERIFY_LOCK_WAIT), and a
timed-out wait is a truthful exit 6 ("bench busy"), never a misleading
serial error. Register the hook at ONE level only: in the common case
stacking now just costs a redundant second verify; on a very slow bench
it can still exhaust the hook's own subprocess timeout, so it is a
crutch, not a supported setup.
Verification records
Every verify — passing or failing — leaves an evidence record at
<firmware>/.flashgate/records/<ts>-<exit>-<fingerprint8>.json: the
per-check verdicts (build/flash/boot/identity/probe:*, with
skipped meaning never executed, never a silent absence), the raw
evidence behind them (banner line, SWD signature fields, console tails,
failing probe transcripts), the artifact sha256, and the exact
tree+profile+tool-version identity the verdict applies to. Every record
also carries a coverage block — the "what this PASS proves" statement:
what was verified, what was NOT (physical effects, everything outside
the listed probes, environmental conditions), plus the board profile's
own caveats (coverage.notes) — a green record never reads as "all
functionality passed". The MCP
verify tool returns the same record inline as data.record. Format:
docs/record-schema.md. Keep .flashgate/ in your
firmware repo's .gitignore (the shipped example does), or every record
write churns the fingerprint. A record that cannot be written is a
warning, never a changed exit code — and a crashed run still leaves one.
The newest ~500 records are kept (best-effort pruning). flashgate verify --json prints this run's record as pure-ASCII JSON on stdout (human logs
move to stderr) for scripting — safe on any console codepage.
To hand a record to vendor support, export the archive as one sendable
page: flashgate records lists what is retained, flashgate records --export case.md [--all] [--redact] writes it (.json for JSON). The
export re-renders what the record already holds — coverage block and raw
evidence included — and --redact scrubs the workstation's identity
through the same util doctor --export --redact uses (paths keep only
their basename). A record that cannot be parsed is named, never silently
dropped; an empty selection or an unwritable target exits 6 rather than
leaving a file that was never written.
Remote bench (device-connect, Stage 2)
Expose this bench to any agent on the network over Arm's device-connect
protocol — four named RPCs over a BenchDriver core, nothing else (no
arbitrary shell, no path arguments; the board a bench serves is fixed by
its profile):
pip install "flashgate[bench]" # optional extra -> device-connect-edge
DEVICE_CONNECT_ALLOW_INSECURE=true DEVICE_CONNECT_DISCOVERY_MODE=d2d flashgate --board boards/apollo-h743.yaml bench-serveRemote callers discover device_type=flashgate-bench and use
describe_bench / start_verify / get_operation / cancel_operation,
plus a verify_completed event per operation at its terminal state (an
operation drained during server shutdown does not emit one — polling
remains the authoritative contract).
start_verify returns an op_id immediately (single verify at a time —
a client retrying over a flaky network can never trigger a second
flash); poll get_operation to the terminal snapshot, which carries the
exit code and the run's full evidence record. Stop the server without
hunting PIDs: flashgate --board <profile> bench-serve --stop (it
drains the in-flight operation, then exits).
The red line for every consumer: the mesh wraps any normally-delivered
reply as success: true — including a busy bench answering
{"error": "busy"}. Judge verification ONLY by the operation payload
(state / exit_code / the record's checks), never by transport
success. Acceptance on the Apollo bench: the same firmware input
produces identical local and remote domain results (tree fingerprint
and per-check verdicts equal; artifact_sha256 differs by design — it
identifies the BUILD, which embeds its timestamp, while the tree
fingerprint identifies the SOURCE).
Deployment notes (from the first real cross-host test, wired host
Wi-Fi laptop, 2026-09-15):
Multicast discovery does not cross all wired↔wireless routers. If discovery finds nothing across machines but
pingworks, switch to unicast: on the bench host setZENOH_LISTEN=tcp/0.0.0.0:7447beforebench-serve; on the client setMESSAGING_URLS=tcp/<host-ip>:7447.Open UDP 7446 (multicast scouting) or TCP 7447 (unicast) inbound on the host's firewall; a Wi-Fi client set to "Public network" blocks the announcements it needs to receive.
One bench-serve per bench: a second instance for the same firmware dir is refused at startup (a loopback lock socket, released automatically when the process dies). Before the lock existed, two same-named servers split-brained the mesh and raced each other for the board's serial port.
Third-party licensing: the bench extra depends on
device-connect-edge
(Apache-2.0) as a pip dependency only — flashgate stays MIT, and no
device-connect source is vendored into this repository (fixes go
upstream as PRs). Commercial distributions that bundle the dependency
must include its LICENSE and NOTICE files.
Security posture, stated plainly: D2D mode is zero-authentication —
anyone on the same LAN can discover the bench and start_verify, which
FLASHES THE BOARD. Descriptions and records also carry local paths.
Fine for a home bench; for anything shared, put the mesh behind
authenticated infrastructure (device-connect server mode) — Stage 4
work.
Install as a Claude Code plugin
The repo doubles as a Claude Code plugin: the Stop hook, an MCP server, a board-integration skill and /flashgate commands, all at once:
pip install git+https://github.com/Lion-1209/flashgate # CLI + hook runtime
pip install "flashgate @ git+https://github.com/Lion-1209/flashgate" # MCP includedThen inside Claude Code:
/plugin marketplace add Lion-1209/flashgate
/plugin install flashgate@flashgateThe hook needs to know your board profile: set FLASHGATE_BOARD to your
board yaml (or a repo boards/ default is used when present). Without a
profile the gate stays idle and says so.
MCP server
{ "mcpServers": { "flashgate": {
"command": "flashgate-mcp",
"args": ["--board", "/path/to/boards/apollo-h743.yaml"] } } }board_info, doctor, build, flash, verify, probe, console_send, console_read. Any MCP-capable agent can drive the board directly. Every tool returns a structured result envelope (status, stable code, summary, CLI exit_code, log) — never a bare log to parse. mcp 1.x and 2.x supported.
Demos
Real-hardware recordings, indexed in demo/: doctor, green-path verify, boot-timeout catch, probe catching a silent no-op, Stop-hook escalation — plus the full session of a real agent (Claude Code harness; recorded with GLM-5.2 behind the Anthropic-compatible endpoint) getting blocked, diagnosing the firmware↔profile contract, fixing both sides, and passing on hardware (24 MB GIF, release asset).
Debug backends (Phase 2)
The verify pipeline is backend-agnostic: flash.adapter: in the board
profile picks the probe tool — cubeprogrammer (default, the original
implementation), openocd (same ST-Link, no ST toolchain needed, and
the standard route to Linux/ARM64 bench hosts like a Raspberry Pi), or
fake (scripted, for tests). Validated on the same Apollo board:
swapping to openocd is a one-line profile change — verify stays green
with identical records, zero upper-layer edits, and runs slightly
faster. OpenOCD is discovered via PATH or $OPENOCD_BIN; the target
script maps from the MCU family (STM32H7* -> stm32h7x, override
with flash.openocd_target).
Board profiles
One yaml per board (boards/): build command, artifact, flash address
(flash.adapter: picks the debug backend), serial adapter hints,
banner template, probes, watch globs. The console-side
USB adapter is a property of your bench, not the board — port resolution
goes explicit serial.port / FLASHGATE_SERIAL_PORT, then VID/PID hint,
then the sole serial port, with the banner match as the final identity
proof. See the guide for the
firmware-side integration recipe (three levels, with code) and the
full profile field reference.
Status
Boot gate, probe gate, Stop hook, MCP server, SWD signature channel, verification records (per-check evidence with artifact hashes), the remote bench over device-connect (validated cross-host), and a second debug backend (OpenOCD, validated on the same board) — all on real hardware. Windows-first; Linux/macOS untested.
License
MIT
中文说明
flashgate 回答一个很具体的问题:刚编译出来的固件,烧到板子上到底能 不能跑。它把整条链路自动化:编译、ST-Link 烧录、等板子报告身份、经 串口实际调用功能并断言,任何一步断了就以非零退出码结束。
完整的使用说明在 docs/GUIDE.md:接线、安装、第一次 验证、给自己的板子写档案、探针写法、Stop hook 和 MCP 的配置、排错。
要点:
板子有两种方式自证:串口 banner,或者在固定 RAM 地址发布 64 字节 签名(后者只靠 ST-Link 就能验证,不用串口线)
版本身份带
-dirty语义,验证通过意味着板上跑的就是当前工作区探针下真命令、断言硬件寄存器读回值;响应报的是实际状态不是回声, 静默失效的设置第一步就会露馅
每次验证(无论成败)都留一份证据记录:逐项检查结论、板子的原话、 产物哈希——事后可审计"当时验证的是什么、板子说了什么"
bench-serve可以把整个台架挂上局域网:远程 agent 用 device-connect 协议发现它、请求真机验证、拿回带证据的结论(跨机实测通过)Stop hook 挂进 Claude Code:agent 改了固件没过真机验证就说"完成", 会被拦下并收到板子的失败证词;同一棵坏树最多拦两次,之后放行但 打警告,会话不会被卡死
仓库带完整的示例固件(examples/apollo-h743),接好线 clone 下来 就能跑通第一次验证
pip install -e "."
flashgate doctor # 体检
flashgate doctor --export checkup.md --redact # 可发送的体检单(脱敏)
flashgate verify --all-probes # 完整验证
echo $?六个真机演示 GIF 在 demo/,包括一段完整的 Claude Code 会话: agent 改固件、被拦、自己定位到固件与板卡档案的契约不一致、两侧改齐、 真机通过后放行。
MIT License
Available Tools
8 toolsboard_infoARead-only
Discover what hardware you are working with: the active board profile.
Call this FIRST in any session — it tells you the board identity
(name, MCU), where the firmware builds (firmware_dir, artifact), how
it flashes (flash connection), and — most importantly — which
functional probes exist (the "probes" list holds the names the probe
tool accepts). Read-only; touches neither host nor board.
Result: data carries the full profile; warnings flag an unloadable
probes section (fix the board YAML before relying on probes).| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only supply readOnlyHint, and the description reinforces that with 'Read-only; touches neither host nor board.' More valuably, it discloses failure semantics that annotations cannot express: warnings flag an unloadable probes section and the caller must fix the board YAML before trusting probes. That is real behavioral context beyond the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose and the call-first directive, and the result paragraph is tightly packed. The parenthetical emphasis ('most importantly') is slightly editorializing, but no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not enumerate return fields, yet it still summarizes the data/warnings split so the agent knows how to interpret results. Purpose, ordering, safety, and failure handling are all covered for a single-parameter read tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single 'board' parameter is fully documented there, including the omit-to-use-server-argument fallback. The description adds no syntax, format, or precedence detail for the parameter, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Discover what hardware you are working with: the active board profile') and enumerates the concrete payload (name, MCU, firmware_dir, artifact, flash connection, probes). An agent can distinguish it from siblings like doctor, build, and flash without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit ordering guidance ('Call this FIRST in any session') and cross-references the probe tool by noting the probes list holds the names probe accepts. It does not, however, state any condition under which you should skip it or name an alternative to use instead, so it stops short of full when/when-not coverage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
buildAIdempotent
Compile the board's firmware (incremental — recompiles only what changed). No hardware is touched.
Mostly useful on its own for a fast failure loop; verify runs the
same build internally, so you don't need this before verify. Result:
succeeded + exit_code 0 = clean build; failed + BUILD_FAILED = the
compiler output in data.log names the offending file and line. The
binary lands at the profile's artifact path, ready for flash.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With only idempotentHint available, the description carries the behavioral load fully: it discloses incrementality, that no hardware I/O occurs, the two result shapes (succeeded/exit_code 0 vs failed/BUILD_FAILED), where diagnostics surface (data.log naming file and line), and where the artifact lands. These are traits the annotations do not cover.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the operation and its incremental/non-hardware nature, then moves to routing and outcome semantics. Every sentence is functional — the result-code and artifact-path sentences directly support post-call interpretation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema existing, the description adds interpretive value (log file naming the offending line, artifact path readiness for flash) rather than repeating return structure, and the parameter gap is covered by the schema. Nothing needed to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single 'board' parameter is fully documented with default and fallback behavior, so the schema does the work. The description adds no parameter-level detail beyond what is already structured, which is the expected baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Compile the board's firmware') and immediately scopes it with 'incremental — recompiles only what changed' and 'No hardware is touched', which cleanly separates it from flash and probe. An agent can identify the operation without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly frames when to use it ('a fast failure loop') and when it is unnecessary ('verify runs the same build internally, so you don't need this before verify'), naming the sibling that subsumes it. This is precisely the kind of routing guidance that prevents redundant calls.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
console_readARead-only
Listen passively: capture everything the firmware prints on the console for the given number of seconds, sending nothing.
Use it to watch boot banners, self-test prints, or fault dumps
(HardFault traces appear here). Output lands in data.text; a fully
silent window is reported as "(console silent)" in the summary —
which for a just-reset board usually means wiring or baud-rate
trouble rather than a healthy quiet firmware. Read-only.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. | |
| seconds | No | Seconds to listen passively, sending nothing; below 0.1 it is clamped up (typical 2.0). Large values block for that long. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=true, and the description agrees ('Read-only') rather than contradicting. It adds real context beyond the annotation: large values block for that long, silent windows are reported as '(console silent)', and silence after reset usually indicates wiring or baud trouble. It stops short of describing the return shape in detail, relying on the output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in the first clause, and the diagostic aside on '(console silent)' earns its place. It is slightly verbose across three segments but no sentence is pure filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value documentation is not required, and the description covers timing, blocking, and failure interpretation adequately. The only minor gap is that it leans on the output schema for the exact data.text contract, which is acceptable here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented with defaults and clamping behavior. The description only loosely restates the 'seconds' semantics ('for the given number of seconds') and adds nothing new about 'board', so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('capture everything the firmware prints on the console') and pins the passive/silent scope ('sending nothing'). This clearly separates it from the sibling console_send, which an agent can distinguish without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete usage contexts: watch boot banners, self-test prints, or fault dumps. It implies the alternative ('sending nothing') but never names console_send explicitly, so the routing guidance is clear rather than exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
console_sendA
Talk to the firmware directly: send ONE command line on the console UART and collect its answer.
Example: line "led0?" — the firmware replies with its LED state and
the live PWM register value (a hardware readback, not a cached
variable). The command set is board-specific — board_info and the
board's probe definitions show the known commands; unknown commands
typically answer "ERR unknown-cmd". The reply lands in
data.response within wait_s seconds; silence is reported as
"(no response)" in the summary + a warning — check wiring before
concluding the firmware is dead.| Name | Required | Description | Default |
|---|---|---|---|
| line | Yes | One console command line to send, e.g. "led0?". Written verbatim — embedded newlines dispatch multiple commands, and over-long lines are dropped by the firmware console buffer. The command set is board-specific; board_info and the board's probe definitions list the known commands. | |
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. | |
| wait_s | No | Seconds to wait for the firmware's answer after sending; below 0.1 it is clamped up (typical 1.0). Large values block for that long. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations (readOnlyHint=false, openWorldHint=true) only signal that this mutates device state; the description goes much further by disclosing the response channel (data.response), the wait_s timing window, the '(no response)' plus warning behavior on silence, the 'ERR unknown-cmd' fallback, and that replies are live hardware readbacks rather than cached values. That is exactly the extra behavioral context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and example, and every sentence carries at least some operational value. The board-specific command guidance is however repeated in the description and again in the line parameter, which is mild redundancy rather than wasted content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the description still usefully names the response field, the timing window, and the failure signals, leaving nothing an agent needs in order to invoke and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so line, board, and wait_s are already fully documented including the verbatim-writing and buffer-overflow behavior. The description reinforces the 'led0?' example and board-specific command set but adds no parameter syntax or constraints beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('send ONE command line on the console UART and collect its answer') with an explicit cardinality constraint that separates it from passive read siblings like console_read. An agent can tell this is the active command-dispatch tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use — direct firmware interrogation, board-specific command set discoverable via board_info and probe definitions — and tells the agent how to interpret 'ERR unknown-cmd'. It does not explicitly say when to prefer console_read or another sibling over this tool, so it falls short of a full when/when-not/alternatives statement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
doctorARead-only
Diagnose the bench: is the ST-Link probe visible, is the console serial port resolvable, is the cross toolchain installed?
Run this before anything else when a board operation fails — and
ALWAYS when a tool returns status "incomplete" or code
CAPABILITY_UNAVAILABLE; the report names the exact missing link.
Read-only. Findings are listed in data.log under "issues:"; an empty
problems list with exit_code 0 means every prerequisite is healthy.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true, and the description adds useful context about what the report contains (data.log with 'issues:' list, empty problems + exit_code 0 means healthy). It doesn't cover whether findings are re-runnable, timing, or side effects, but for a read-only diagnostic the disclosure is strong.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences that are front-loaded with purpose, then usage conditions, then output interpretation. No filler, every clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, preconditions, and output semantics (issues list, exit code). With an output schema present and annotations covering safety, this is nearly complete. Could mention board profile precedence more clearly, but that's in the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the parameter itself has a detailed description in the schema. The tool description adds no parameter-level information beyond what's in the schema, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (diagnose) and a precise set of checks (ST-Link probe visibility, console serial resolvability, cross toolchain installed). This is clearly distinguishable from siblings like probe or board_info, which perform operations rather than environment diagnostics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to run it: 'before anything else when a board operation fails' and 'ALWAYS when a tool returns status "incomplete" or code CAPABILITY_UNAVAILABLE'. It routes the agent to this tool in specific failure conditions, which is exactly what usage guidance should do.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
flashADestructive
Write the built firmware to the board's flash over ST-Link, verify the write, then start the application. DESTRUCTIVE: whatever runs on the MCU is replaced.
Flashes the artifact from the last build — call build first if
sources changed since. Tries up to 3 times on transient probe
errors. Any failure — including a missing ST-Link or CubeProgrammer —
surfaces as failed + FLASH_FAILED, exit_code 2; data.log carries the
programmer output. Run doctor to tell an environment problem (no
probe / no tools) apart from a write failure.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true, and the description reinforces with concrete consequences ('whatever runs on the MCU is replaced'). Beyond that it discloses retry policy (up to 3 attempts on transient errors), failure semantics (FLASH_FAILED, exit_code 2), and where output lands (data.log), all of which go beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the destructive warning and primary action, followed by dependency, retry, and failure-handling detail. Every sentence carries distinct, actionable information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be explained, yet the description still covers dependencies, retries, and failure routing. Combined with the destructive annotation, an agent has everything needed to call it safely and interpret failures.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one optional parameter and schema description coverage is 100%, so the schema fully documents the board profile path including fallback behavior. The description adds no additional parameter meaning, which matches the baseline 3 when the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('write the built firmware to the board's flash over ST-Link'), plus the follow-on actions (verify, start application). It is clearly distinguishable from siblings like build, verify, and probe.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'call build first if sources changed since' and 'Run doctor to tell an environment problem apart from a write failure.' It names both the dependency and the diagnostic alternative with the condition that selects each.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
probeA
Exercise the firmware ALREADY RUNNING on the board with its defined probes: each probe sends console commands and asserts on the board's answers — including live register readbacks (e.g. the timer value actually driving an LED). No rebuild, no reflash.
names picks specific probes (valid names are in board_info's
"probes" list); omit it or pass [] to run all. Probes need the
console UART: without it the result is incomplete +
CAPABILITY_UNAVAILABLE — never a pass. failed + PROBE_FAILED means an
assertion did not hold; data.log shows the exact step, the board's
last response, and which value was off.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. | |
| names | No | Probe names to run, taken from board_info's "probes" list. Omit or pass [] to run every defined probe. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With only readOnlyHint=false to go on, the description carries the burden well: it discloses the console UART dependency, states that missing UART yields CAPABILITY_UNAVAILABLE and 'never a pass,' decodes failed + PROBE_FAILED as a failed assertion, and points to data.log for the exact step and board response. It even discloses that live register readbacks occur.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Densely front-loaded: capability first, parameter behavior second, failure semantics last. Every sentence is informative, though the names/omit-[] sentence partly duplicates the schema and could be trimmed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no prose, yet the description still covers the ambiguous outcome cases (CAPABILITY_UNAVAILABLE, PROBE_FAILED) that an agent must interpret. Nothing needed to call this tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema description for 'names' already says it draws from board_info's probes list and that omit/[] runs all, so the description largely restates structured data. The cross-reference to board_info is mildly useful but adds little beyond the schema baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('exercise the firmware ALREADY RUNNING ... with its defined probes') and immediately distinguishes itself from build/flash siblings via 'No rebuild, no reflash.' It also names the mechanism (sends console commands, asserts on answers) so an agent knows exactly what class of operation this is.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use: probes run against already-flashed firmware, names selects subsets from board_info's "probes" list, and omit/[] runs all. It does not explicitly contrast with the closest siblings (verify, console_send/console_read), leaving some routing to inference, but the when-to-use picture is solid.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
verifyADestructive
The hardware gate — the authoritative answer to "does this firmware actually work on the board?".
status=succeeded means the BOARD ITSELF booted the exact tree you are
on (its reported git sha matches the working tree) and every
functional probe passed. Trust no other signal; a clean build is not
a pass.
What it does: rebuild the tree, flash over ST-Link, start the app,
then require boot evidence proving WHICH build is running (UART
banner; or, without a serial cable, an SWD RAM signature — a stale
signature is wiped and read-back verified before start; a wipe that
fails, or whose readback answers wrong or cannot run at all, fails
closed), then run every defined probe, asserting on the board's
answers, including live register readbacks. DESTRUCTIVE: rewrites the
board's flash.
The failure code names the broken stage: BUILD_FAILED, FLASH_FAILED,
BOOT_EVIDENCE_TIMEOUT, BOOT_ERROR, IDENTITY_MISMATCH (the board runs
a different tree, or the signature wipe failed or its readback
answered wrongly — check the debug probe, not the build),
CAPABILITY_UNAVAILABLE (a required check could not run — most often
the console UART, or a readback that cannot run at all: environment
failure, check named, start withheld), PROBE_FAILED. Full transcript
in data.log; data.record is this run's persisted evidence record,
data.record_dir where the audit trail accumulates.
Report the status and code verbatim; never claim success from the
build step alone.| Name | Required | Description | Default |
|---|---|---|---|
| board | No | Path to the board profile YAML (e.g. boards/apollo-h743.yaml). Omit to use the server's --board argument, else the default search path. |
Output Schema
| Name | Required | Description |
|---|---|---|
| code | Yes | |
| data | No | |
| policy | No | |
| status | Yes | |
| summary | Yes | |
| evidence | No | |
| warnings | No | |
| exit_code | No | |
| schema_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the single destructiveHint annotation: discloses that it rebuilds, flashes over ST-Link, starts the app, requires boot evidence (UART banner or SWD RAM signature with fail-closed wipe/readback), and enumerates all failure codes with their meanings. Explicitly flags DESTRUCTIVE flash rewrite and the persisted output files.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core identity before the operational detail, and uses 'What it does:' style structure. Dense and long, but almost every clause carries behavioral meaning for a complex destructive operation, with minimal filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers the full lifecycle, the success/failure semantics, the destructive impact, and where transcript/record artifacts land (data.log, data.record, data.record_dir). With the output schema present, no return-value detail is missing for an agent to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one optional parameter (board) with full schema description coverage and a clear default/fallback stated in the schema. The description adds no board-path syntax or precedence detail beyond the schema, so the baseline 3 for high coverage applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('the hardware gate', 'does this firmware actually work on the board') and defines success concretely (board booted the exact tree, probes passed). Explicitly distinguishes itself from siblings: 'a clean build is not a pass' separates it from build/flash/probe.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Strongly routes the agent away from weaker signals ('Trust no other signal; a clean build is not a pass') and frames itself as the authoritative final gate. It stops short of an explicit when-not-to-use statement or naming a specific alternative tool to run first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.0- First observed
board_info - First observed
build - First observed
console_read - First observed
console_send - First observed
doctor - First observed
flash - First observed
probe - First observed
verify
TDQS
Scored across 8 tools
board_info, doctor, build, and flash each target a distinct stage of the embedded workflow, and the console tools split cleanly into active (console_send) vs passive (console_read). The one area of potential confusion is verify vs. build/flash/probe, since verify internally does all three, but the descriptions explicitly clarify the relationship and when to use each.
build, flash, verify, probe, and doctor form a coherent set of single-word action verbs, and the console_ prefix groups the two UART tools. board_info and doctor are the mild deviations (noun/noun-ish vs. pure verb), but the convention is largely readable and predictable.
Eight tools is well-scoped for an embedded build/flash/test server, with each tool earning its place across the discover-diagnose-build-flash-verify-probe lifecycle. Nothing is redundant or trivial.
The surface covers the full firmware lifecycle: discovery, environment diagnostics, build, flash, hardware verification, probe execution, and direct console interaction. A minor gap is the lack of an explicit reset/erase tool, though verify and console_read implicitly handle reset-adjacent scenarios.
Maintenance
Related MCP Connectors
Run, build, and validate firmware on virtual hardware from your AI agent. Hardware knowledge corpus.
Stop your agent before it runs rm -rf /etc or emails your .env. Deterministic preflight checks.
Agent-native CI/CD: follow a push to live deploy, diagnose failing stages, prove fixes, rerun.
A deterministic verification engine for agents. Proves a fix: fails on the old code, passes on new.
Related MCP Servers
- AlicenseAqualityAmaintenanceAgentic Hardware-in-the-Loop (Agentic HIL) lets coding agents develop firmware on real embedded boards: flash, reset, read UART and exchange CAN traffic, diagnose and fix. Local, policy-gated MCP tools support OpenOCD, pyOCD and STM32CubeProgrammer, with pytest for CI.44890 PyPI18Apache 2.0
- AlicenseNot gradedqualityBmaintenanceMCP server for firmware verification in MCU development, exposing a five-stage pipeline (Detect, Review, Build, Flash, Verify) as tools for AI agents to compile, flash, and verify firmware on Arduino boards.1MIT
- AlicenseAqualityBmaintenanceEnables MCP-capable agents to drive a real FPGA toolchain end to end—simulating, asserting, synthesizing, scanning JTAG, capturing on-chip waveforms, and configuring real silicon behind confirmation gates.45Apache 2.0
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to build, flash, and monitor ESP-IDF projects on real hardware, including serial sessions and pytest-embedded hardware tests.1MIT