Skip to main content

MCP server

mcp/server.mjs exposes the emulator as Model Context Protocol tools, so an MCP host (Claude Code, Claude Desktop, or any MCP client) can boot firmware, step execution, read UART, poke pins, inject analog values, and inspect registers interactively — firmware debugging driven by an agent instead of a hand-written script.

Built on the official @modelcontextprotocol/sdk over a stdio transport.

Install

The SDK and zod are optional peer dependencies — using this package as an emulator library stays completely dependency-free (a plain npm i stm32f4-emu installs exactly one package), and only the MCP server itself imports @modelcontextprotocol/sdk and zod.

npm install stm32f4-emu @modelcontextprotocol/sdk zod

Running the server without them exits with a message telling you exactly what to install, rather than a module-resolution stack trace.

From a clone of this repo, npm install picks them up as devDependencies:

npm install
npm run mcp # or: node mcp/server.mjs
npm run test:mcp # protocol round-trip smoke test

Client configuration

Claude Desktop / Claude Code mcpServers entry:

{
"mcpServers": {
"stm32f4": {
"command": "node",
"args": ["/absolute/path/to/stm32 F4/mcp/server.mjs"]
}
}
}

Installed as a package, the stm32f4-mcp bin is on PATH:

{
"mcpServers": {
"stm32f4": { "command": "stm32f4-mcp" }
}
}

Tools

ToolWhat it does
list_firmwaresList the bundled firmware keys load_firmware accepts
load_firmwareBoot a firmware (firmware, optional enable_irqs), replacing any active session
stepRun up to N instructions; returns PC, instruction count, stopped flag
read_uartDrain UART output printed since the last call
send_uartSend text to the firmware over UART RX
read_pinRead a GPIO pin's output and input levels
write_pinDrive a GPIO input level into the guest
set_adc_channelForce an ADC channel to a 12-bit value (omit value to clear)
read_registersR0-R12, SP, LR, PC, XPSR as hex
read_memoryRead a 32-bit word at any address (flash, SRAM, MMIO)
attach_componentAttach a led/button/pwm/potentiometer; returns an id
read_componentRead an attached component's state
control_componentpress/release a button, or set a potentiometer value
resetClose the session and drop attached components

Components are the same classes documented in components.md — the MCP layer is a thin wrapper over the public library API, not a parallel implementation.

One session at a time

The server keeps a single active emulator. load_firmware closes any previous session first, and reset clears it.

Switching firmware within one server process is supported: load_firmware closes the previous instance and the wasm model installs a fresh peripheral tree and clears its globals (see components.md and AGENTS.md §9). The one hard requirement is that sessions are sequential, never concurrent — which is what "one session at a time" enforces.

Example session

list_firmwares -> ["eth_http", "blinky", ...]
load_firmware {firmware: "blinky"} -> loaded
attach_component {type: "led", port: "A", pin: 5} -> {id: "led1"}
step {instructions: 100000} -> {pc: "0x80001ee", ...}
read_uart -> "=== Blinky ===\r\nLED: GPIOA PA5..."
read_component {id: "led1"} -> {on: true}