CMSIS-DAP MCP
An open-source debug tool suite for CMSIS-DAP probes and Cortex-M chips, providing an MCP server (for AI assistants) and a standalone CLI, both built on the same engine over SWD or JTAG.
New here? Start with the Getting Started guide for a step-by-step walkthrough from environment setup to your first connection.
Two tools
- cmsis-dap-mcp — an MCP (Model Context Protocol) server that lets AI assistants (Codex, Claude Code, opencode, etc.) drive your probe and target chip directly.
- cmsis-dap-cli — a standalone command-line tool for humans, scripts and automation, no AI client needed.
Core features
Probe and session
| Tool | What it does |
|---|---|
list_probes | Enumerate all connected CMSIS-DAP probes |
get_probe_info | View probe details (product, serial, protocols, speeds) |
connect | Connect to a target via SWD or JTAG; supports under-reset connect |
disconnect | End the current session |
get_target_info | View target info (core type, CPUID, memory regions) |
Memory access
| Tool | What it does |
|---|---|
read_memory | Read memory (u8/u16/u32/u64); export a range as bin/hex file |
write_memory | Write memory |
verify_memory | Read back and compare against expected data; report mismatches |
Core control
| Tool | What it does |
|---|---|
read_core_register / write_core_register | Read/write core registers (pc, sp, lr, r0-r15, …) |
list_core_registers | List all registers available on the target |
get_core_status | Query core state (running/halted/sleeping/locked up) |
halt / resume / step | Pause / resume / single-step execution |
reset | Reset the target; continue or halt after reset |
Breakpoints and watchpoints
| Tool | What it does |
|---|---|
set_breakpoint / clear_breakpoints / list_breakpoints | Hardware breakpoint management |
set_watchpoint / clear_watchpoints / list_watchpoints | DWT data watchpoints (read/write/rw trigger) |
DAP raw access
| Tool | What it does |
|---|---|
read_dap / write_dap | Direct DP/AP register read/write (advanced debugging) |
SVD named peripherals
| Tool | What it does |
|---|---|
load_svd | Load any CMSIS-SVD file at runtime |
list_peripherals | List all loaded peripherals |
read_peripheral / write_peripheral | Read/write peripheral registers and bitfields by name (read-modify-write) |
Flash programming
| Tool | What it does |
|---|---|
erase_flash | Erase flash by sector (only sectors overlapping the requested range) |
program_flash | Program firmware from elf/axf/bin/hex files; optional read-back verify |
Chip definition
| Tool | What it does |
|---|---|
define_chip (MCP) | Register an unknown chip at runtime from a Keil FLM file |
chip generate (CLI) | Generate a probe-rs target YAML from an FLM file |
chip list / chip search | List or search the built-in chip library |
Script engine
| Tool | What it does |
|---|---|
run_script (MCP) / script (CLI) | Execute J-Link Commander / OpenOCD style debug scripts |
Non-invasive debugging
| Tool | What it does |
|---|---|
dump_cpu_state (MCP) / dump (CLI) | Take a CPU snapshot without resetting: registers, fault status, stacks, memory |
Remote access
| Feature | Description |
|---|---|
| TCP JSON-RPC server | --tcp PORT (MCP) or tcp-server (CLI): line-delimited remote protocol |
| GDB server | --gdb-port PORT (MCP) or gdb-server (CLI): GDB Remote Serial Protocol stub |
Runtime configuration
| Tool | What it does |
|---|---|
get_config | View current runtime configuration |
update_config | Update config at runtime (destructive gate, TCP/GDB ports) without restart |
reload_config | Re-apply the config file given at startup |
Security
Three-tier security: read-only tools are always available; write tools are governed by the MCP client approval policy; destructive tools (flash erase/program) are disabled by default and must be explicitly enabled.
CLI live debugging (CLI-only)
| Feature | Description |
|---|---|
watch | Poll variables by address or ELF symbol with configurable refresh; timestamped log export |
rtt monitor | Read SEGGER RTT up-channel logs over SWD/JTAG — no UART needed |
evr monitor | Decode CMSIS-View Event Recorder events — no trace hardware needed |
repl | Interactive shell that keeps one session open |
Highlights
- Generic Cortex-M support: standard cores work without chip-specific adaptation
- Runtime chip definition: register unknown chips from FLM files; no pre-built YAML needed
- Zero-argument startup: server starts with no flags and is fully configurable at runtime
- Zero dependencies for end users:
npx -y cmsis-dap-mcpor a single native binary - Cross-platform: Windows / Linux / macOS
Documentation
- Getting Started — complete setup and first-connection tutorial
- Quickstart — MCP server quick setup
- AI client configuration — Codex / Claude Code / opencode setup
- Tools — full MCP tool reference
- CLI — full CLI command reference
- Scripting — J-Link / OpenOCD style scripts
- SWD and JTAG — protocol selection
- SVD and Flash — peripheral access and flash workflows
- Security — security model and configuration
- Troubleshooting — common issues
Chinese documentation: https://guohj2021.github.io/CMSIS-DAP-MCP/zh/
Getting Started
This guide walks you through installation, hardware setup, and your first debug session — from absolute zero. Every step includes a concrete command and the expected output.
What you get
CMSIS-DAP MCP ships two tools built on the same engine:
- cmsis-dap-mcp — a Model Context Protocol server that lets AI assistants (Codex, Claude Code, etc.) drive your debug probe and target chip directly.
- cmsis-dap-cli — a standalone command-line tool for humans, scripts and automation, no AI client needed.
Both support:
- Enumerating debug probes, connecting over SWD or JTAG to any Cortex-M chip
- Reading/writing memory and core registers, halt/resume/step execution
- Loading SVD files at runtime for named peripheral access
- Programming flash from firmware files (elf/axf/bin/hex)
- Running J-Link / OpenOCD style debug scripts
- Non-invasive CPU snapshots (without resetting the target)
- Remote TCP server and GDB debug server
The CLI additionally provides live debugging: watch (variable polling),
rtt monitor (SEGGER RTT logs), evr monitor (CMSIS-View Event
Recorder) — all over SWD/JTAG, no UART needed.
Hardware you need
Required
-
CMSIS-DAP debug probe
- Supports CMSIS-DAP v1 (HID) or v2 (WinUSB) protocol
- Most commercial CMSIS-DAP compatible probes work
- Connects to your PC via USB
-
Cortex-M development board
- Any ARM Cortex-M board with an SWD debug port
- Supports M0, M0+, M3, M4, M7 — all core variants
- Connects to the probe via SWD wires
-
SWD wires
- Minimum 3 wires: SWDIO, SWCLK, GND
- Connect probe SWD pins to the matching debug port on your board
Optional
- nRST reset wire
- Used for
under_resetmode (locked or non-responsive targets) - Connect the probe’s nRST pin to the board’s reset pin
- Used for
Wiring diagram
Probe (CMSIS-DAP) Board (Cortex-M)
┌─────────────┐ ┌─────────────┐
│ SWDIO ──────┼───────────┤ SWDIO │
│ SWCLK ──────┼───────────┤ SWCLK │
│ GND ──────┼───────────┤ GND │
│ nRST ──────┼── (opt) ─┤ NRST │
└──────┬──────┘ └─────────────┘
│ USB
┌──┴──┐
│ PC │
└─────┘
Tip: Pin layouts vary by probe and board. Always check your hardware’s pinout diagram to match SWDIO/SWCLK/GND correctly.
Files you need
Features work in layers — some need no extra files, others do:
| Feature | File needed | Where to get it |
|---|---|---|
| Basic debug (memory, registers, execution) | None — works out of the box | — |
| Named peripheral access | SVD file | Chip vendor SDK or CMSIS-Pack |
| Flash programming | Keil FLM flash algorithm file | IDE installation directory or chip vendor |
| Symbol-level debug (watch/RTT/EVR) | Firmware ELF or AXF file | Your compiler output |
FLM files are typically found under the Keil MDK Flash/ directory,
named like TargetChip_64.FLM.
SVD files describe the chip’s peripheral register layout, usually shipped
with the chip’s SDK or CMSIS-Pack, named like TargetChip.svd.
Note: This repository never bundles chip-specific data. All files are provided by you at runtime.
Environment setup
Option A: npm (recommended)
npm is the Node.js package manager. Both tools are published as npm packages.
Windows
# Install Node.js (includes npm) with winget
winget install OpenJS.NodeJS.LTS
# Or with scoop
scoop install nodejs-lts
Open a new terminal and verify:
node --version # Should show v18.x or later
npm --version # Should show 9.x or later
Linux (Debian/Ubuntu)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
Linux (Fedora/RHEL)
sudo dnf install -y nodejs npm
macOS
brew install node
Option B: Native binary (offline)
If Node.js is not available, download the platform binary from GitHub Releases. Zero runtime dependencies.
Option C: Build from source (developers)
Install the Rust toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo build --release --workspace
Install the tools
MCP server (for AI assistants)
# Verify it runs (zero-install; downloads automatically on first run)
npx -y cmsis-dap-mcp --help
CLI (for humans)
# Zero-install quick trial
npx -y cmsis-dap-cli --help
# Or install globally for direct command access
npm install -g cmsis-dap-cli
cmsis-dap-cli --help
If you downloaded a native binary, add it to your PATH or call it by full path.
Driver setup
Windows
- CMSIS-DAP v1 (HID): usually driverless; plug in and it works.
- CMSIS-DAP v2 (WinUSB): requires a WinUSB driver.
- Download Zadig from https://zadig.akeo.ie/
- Plug in the probe and open Zadig
- Go to
Options -> List All Devices - Select your CMSIS-DAP device
- Replace the driver with WinUSB, click
Replace Driver
Linux
Add a udev rule to allow non-root USB access:
# Create a rule (replace xxxx/yyyy with your probe's VID/PID)
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="yyyy", MODE="0666"' \
| sudo tee /etc/udev/rules.d/99-cmsis-dap.rules
# Reload rules
sudo udevadm control --reload-rules
sudo udevadm trigger
# Re-plug the probe
Tip: Find your probe’s VID/PID in Windows Device Manager, or run
lsusbon Linux.
macOS
Usually works out of the box. If the probe is not recognized, check System Settings > Privacy & Security for any USB permission prompts.
Step 1: Connect your hardware
1. Verify detection
Plug in the CMSIS-DAP probe and open a terminal:
cmsis-dap-cli list
Expected output (probe id and product name vary by hardware):
CMSIS-DAP probes found:
id : 0123456789AB
product : CMSIS-DAP
serial : (none)
protocols : SWD, JTAG
If the list is empty, check driver setup and the USB connection.
2. Connect to the target chip
cmsis-dap-cli connect
This auto-detects the target. For more detailed memory mapping, specify the chip name:
cmsis-dap-cli --target STM32F030C8 connect
Expected output:
target: {"ap_count":1, "core_count":1, "core_type":"Armv6m", ...,
"memory_regions":[FLASH 0x08000000-0x08010000, SRAM 0x20000000-0x20002000]}
3. Read memory to verify
cmsis-dap-cli read --address 0x20000000 --width u32 --count 4
Expected output (values depend on the target’s current memory contents):
address: 0x20000000, width: u32, count: 4
0x20000000: 0x00000040
0x20000004: 0x00000001
0x20000008: 0x00000003
0x2000000C: 0x00000000
4. Halt, read a register, resume
cmsis-dap-cli halt
cmsis-dap-cli reg get pc
cmsis-dap-cli resume
Expected output:
halted: true
pc = 0x0800122A
running: true
Congratulations! You have connected to the target chip and performed basic memory and register operations.
Tip: Use
replto stay in an interactive session:cmsis-dap-cli repl # At the prompt: connect, halt, reg pc, resume
Next: MCP server setup
If you use an AI assistant (Codex, Claude Code, or opencode), you can let it drive the probe directly. Add the MCP server configuration:
Codex
codex mcp add cmsis-dap -- npx -y cmsis-dap-mcp
Claude Code
claude mcp add --scope local cmsis-dap -- npx -y cmsis-dap-mcp
opencode
opencode mcp add cmsis-dap -- npx -y cmsis-dap-mcp
After adding the server, restart the client. Then you can say in a chat:
List connected debug probes and connect to the target chip.
The AI will call list_probes, connect, and other tools automatically.
Next: Flash programming
Prerequisites
- Obtain the FLM flash algorithm file for your chip
- Know the chip’s Flash and SRAM address ranges (check the datasheet)
CLI workflow
# Step 1: Generate a target YAML from the FLM (one-time setup)
cmsis-dap-cli chip generate \
--flm /path/to/TargetChip.FLM \
--flash-start 0x08000000 --flash-size 0x10000 \
--sram-start 0x20000000 --sram-size 0x2000 \
--name TargetChip --output TargetChip.yaml
# Step 2: Connect with the generated YAML and program
cmsis-dap-cli --target-yaml TargetChip.yaml connect
cmsis-dap-cli flash erase --address 0x08000000 --size 0x10000
cmsis-dap-cli flash program --address 0x08000000 --file firmware.hex --verify
MCP workflow
define_chip {
"flm": "/path/to/TargetChip.FLM",
"flash_start": 0x08000000, "flash_size": 0x10000,
"sram_start": 0x20000000, "sram_size": 0x2000,
"core": "armv6m", "name": "TargetChip"
}
connect { "target": "TargetChip", "protocol": "swd" }
program_flash { "address": 0x08000000, "path": "firmware.hex", "format": "hex", "verify": true }
Enabling destructive mode
Flash erase and program are destructive operations, disabled by default. Two ways to enable:
- At startup: pass
--allow-destructive - At runtime: call
update_config {"allow_destructive": true}(no restart)
Next: Named peripherals (SVD)
SVD files describe the chip’s peripheral register layout, letting you work with register names instead of raw addresses.
# CLI
cmsis-dap-cli --svd TargetChip.svd svd list
cmsis-dap-cli --svd TargetChip.svd svd read GPIOA.ODR.ODR0
cmsis-dap-cli --svd TargetChip.svd svd write GPIOA.ODR.ODR0 1
# MCP
load_svd { "path": "/path/to/TargetChip.svd" }
list_peripherals {}
read_peripheral { "peripheral": "GPIOA", "register": "ODR", "field": "ODR0" }
write_peripheral { "peripheral": "GPIOA", "register": "ODR", "field": "ODR0", "value": 1 }
Next: Live debugging
The CLI provides three live debugging features, all over SWD/JTAG — no UART needed:
Variable polling (watch)
cmsis-dap-cli --elf firmware.axf watch counter --interval-ms 200 --count 0
RTT logging
cmsis-dap-cli --elf firmware.axf rtt monitor --channel 0 --count 0
Event Recorder
cmsis-dap-cli --elf firmware.axf evr monitor --count 0
Note: Live debugging requires the firmware ELF file (
--elf), and the target firmware must have initialized the corresponding component (SEGGER RTT or CMSIS-View Event Recorder).
Next steps
- Quickstart — MCP server quick setup
- AI client configuration — detailed config for each AI client
- Tools — full MCP tool reference
- CLI — full CLI command reference
- Scripting — J-Link / OpenOCD style scripts
- SWD and JTAG — protocol selection guide
- SVD and Flash — peripheral access and flash workflows
- Troubleshooting — common issues and solutions
Quickstart
npm (recommended)
Install nothing — let your MCP client launch the server with npx:
codex mcp add cmsis-dap -- npx -y cmsis-dap-mcp
The npm package cmsis-dap-mcp downloads the correct platform binary
automatically on first launch and caches it afterwards.
To pin a version:
codex mcp add cmsis-dap -- npx -y cmsis-dap-mcp@0.5.0
Native binary
Download the binary for your platform from the GitHub Releases page, then point the client at it:
codex mcp add cmsis-dap -- /path/to/cmsis-dap-mcp --log-level warn
This is the standard way to run an unpublished or locally built server, or when you need an exact, offline-pinned binary.
Configuration styles
MCP clients can start a stdio server in three equivalent ways. The
npx form is the standard for published packages; the local-binary form is
equivalent and used for local builds.
| Style | Example | Best for |
|---|---|---|
npx package | command = "npx", args = ["-y", "cmsis-dap-mcp"] | Published releases; updates with npm |
| Local binary | command = "/path/to/cmsis-dap-mcp" | Local builds, offline, exact version |
| Remote URL | url = "https://..." | Streamable-HTTP servers (not supported by this project) |
All three clients covered on the AI client configuration
page accept both the npx form and a local binary path; the server behaves
identically either way.
First session
The server can be started with zero arguments — it enters a to-be-configured state where all read/write tools work and destructive tools stay gated until enabled (see step 7).
list_probesto find your probe id.connectwith{"protocol": "swd", "speed_khz": 1000}.read_memory/write_memoryfor raw access.halt, thenread_core_register(e.g.pc,sp,lr,r0).resumewhen done.load_svdwith your own SVD path for named peripheral access.program_flash/erase_flashrequire destructive mode: start the server with--allow-destructive, or callupdate_config {"allow_destructive": true}at runtime (no restart needed).- For a chip not built into probe-rs, call
define_chipwith a Keil FLM file beforeconnect(see Tools).
Example (verified output on a CMSIS-DAP probe + Cortex-M0+ board):
list_probes -> {"probes": [{"id": "0123456789AB", "product": "CMSIS-DAP", ...}]}
connect {protocol: swd, speed_khz: 1000}
-> {"target": {"core_type": "Armv6m", "core_count": 1, "ap_count": 1, "cpu_id": ..., "dp_id": ...}}
read_memory {address: 0x20000000, width: u32, count: 4}
-> {"values": [64000000, 1, 3, 0]}
halt -> {"halted": true}
read_core_register {name: pc} -> {"value": 134228884}
resume -> {"running": true}
CLI quick start
The standalone cmsis-dap-cli shares the same engine and auto-connects with
the global options (--probe-id, --target, --target-yaml, …):
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 connect
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 read --address 0x20000000 --width u32 --count 4
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 --elf fw.axf watch counter --interval-ms 200 --count 0
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 --elf fw.axf rtt monitor --count 0
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 --elf fw.axf evr monitor --count 0
Use repl to keep one session open (halt/read/resume across lines, or run the
watch/RTT/Event Recorder monitors after reset run). See the
CLI reference for the full command set.
Logs go to stderr only; the MCP protocol runs over stdout.
AI client configuration
The server speaks MCP over stdio. The standard way to configure it is the
npx form, which runs the published npm package. To run a locally built
binary instead, replace npx -y cmsis-dap-mcp with your binary path — the
server behaves identically.
Configuration styles
There are three ways to point an MCP client at a server:
| Style | Example | When to use |
|---|---|---|
npx package (standard) | command = "npx", args = ["-y", "cmsis-dap-mcp"] | Published releases; first launch downloads and caches the package |
| Local binary | command = "/path/to/cmsis-dap-mcp" | Unpublished or locally built servers, offline use, exact version pinning |
| Remote URL | url = "https://..." | Streamable-HTTP MCP servers (not supported by this project yet) |
To pin a version with npx: npx -y cmsis-dap-mcp@0.5.0. If you are
developing this repository, point the client at target/release/cmsis-dap-mcp
so the freshly built binary is used without publishing.
Server command-line options
All options are optional — the server starts fine with no arguments and
enters a to-be-configured state. Everything below (except logging) can
also be changed at runtime via the update_config / reload_config /
get_config MCP tools without a restart.
| Option | Meaning |
|---|---|
--allow-destructive | enable erase_flash / program_flash and destructive script commands at startup |
--tcp PORT | also serve the remote JSON-RPC TCP server on 127.0.0.1:PORT |
--gdb-port PORT | also start a GDB server on 127.0.0.1:PORT |
--config-file FILE | JSON config file (allow_destructive, tcp_port, gdb_port keys); loaded at startup, watchable for changes |
--probe-id ID | default probe id for connect |
--protocol swd|jtag | default debug protocol (default swd) |
--speed-khz N | default SWD/JTAG clock speed |
--target NAME | default target chip name |
--svd FILE | SVD file to load at startup |
--target-yaml FILE | target YAML to pre-load into the chip registry |
--log-level LEVEL | tracing filter; logs go to stderr (default info) |
--log-file FILE | write logs to a file instead of stderr |
Startup-only (not changeable at runtime): --log-level, --log-file,
the --config-file path itself (its contents can be reloaded), and the
backend registry seed (--target-yaml; define_chip adds to it at
runtime). A GDB server port cannot be changed once the server is running.
Precedence: CLI flags > config file > defaults; runtime update_config
overrides both.
Codex
codex mcp add cmsis-dap -- npx -y cmsis-dap-mcp
Or add to ~/.codex/config.toml:
[mcp_servers.cmsis-dap]
command = "npx"
args = ["-y", "cmsis-dap-mcp"]
For a local build, use command = "/path/to/cmsis-dap-mcp". Verify with
codex mcp list. The Codex desktop app loads the server when a new session
starts.
Claude Code
claude mcp add --scope local cmsis-dap -- npx -y cmsis-dap-mcp
For a local build, replace npx -y cmsis-dap-mcp with the binary path.
Verify with claude mcp list (shows √ Connected).
opencode
opencode mcp add cmsis-dap -- npx -y cmsis-dap-mcp
Or add to ~/.config/opencode/opencode.jsonc:
"cmsis-dap": {
"type": "local",
"command": ["npx", "-y", "cmsis-dap-mcp"],
"enabled": true
}
For a local build, replace the command array with
["/path/to/cmsis-dap-mcp", "--log-level", "warn"]. Verify with
opencode mcp list.
Other MCP clients
{
"mcpServers": {
"cmsis-dap": {
"command": "npx",
"args": ["-y", "cmsis-dap-mcp"]
}
}
}
End-to-end example (verified)
The same task below was executed successfully by Claude Code and opencode against a real CMSIS-DAP probe:
1. list_probes
2. connect {protocol: swd, speed_khz: 1000}
3. read_memory {address: 0x20000000, width: u32, count: 4}
4. halt
5. read_core_register {name: pc}
6. resume
Observed results:
probe id : 0123456789AB (CMSIS-DAP, vendor 0x0416)
memory : [64000000, 1, 3, 0]
pc : 134228884 (0x08002B94)
Notes:
- When passing arguments from a model, use decimal integers or strings; some
clients reject hex literals in JSON arguments (e.g.
0x20000000). Decimal536870912is equivalent. - Write tools such as
connect,haltandresumemay be governed by the client approval policy. - If the tools do not appear, restart the client after adding the server.
Tools
Levels: Read (always available), Write (governed by client approval),
Destructive (requires --allow-destructive at startup or
update_config with allow_destructive: true at runtime).
Probe and session
| Tool | Params | Level |
|---|---|---|
list_probes | - | Read |
get_probe_info | probe_id (optional) | Read |
connect | probe_id, protocol (swd/jtag, default swd), speed_khz, target, under_reset | Write |
disconnect | - | Write |
get_target_info | - | Read |
list_probes returns the probe id, vendor/product, serial, product id,
interface, HID flag, supported protocols, speed and target voltage (when the
probe reports it).
get_target_info returns the core type and count, the real AP count, CPUID,
DPIDR and a memory map summary (RAM/NVM regions).
Memory
| Tool | Params | Level |
|---|---|---|
read_memory | address, width (u8/u16/u32/u64), count (default 1) | Read |
write_memory | address, width, values | Write |
verify_memory | address, width, data | Read |
verify_memory reads back the given range and compares it with data,
returning verified and a list of mismatches.
read_memory can also export a range to a file: pass path plus
format (bin default or hex) and count becomes the number of bytes
to read. Example:
read_memory { "address": 0x08000000, "width": "u8", "count": 0x1000, "path": "firmware.bin", "format": "bin" }
Core
| Tool | Params | Level |
|---|---|---|
read_core_register | name or number | Read |
write_core_register | name or number, value | Write |
list_core_registers | - | Read |
get_core_status | - | Read |
halt | - | Write |
resume | - | Write |
step | - | Write |
reset | mode (run default / halt) | Write |
Register names are resolved case-insensitively. Special roles (pc, sp,
fp, lr/ra, psr/xpsr, msp, psp, fpsr) and general registers
(r0-r15) are supported; any other name is looked up in the architecture
register file. list_core_registers returns all available names.
get_core_status returns state (running/halted/sleeping/locked_up/
unknown), the halt_reason when halted, and the program counter when
halted.
Non-invasive debugging
| Tool | Params | Level |
|---|---|---|
dump_cpu_state | address (repeatable, 0xADDR or ELF symbol), stack_words (optional), no_restore (optional) | Read |
dump_cpu_state takes a CPU snapshot without ever resetting the target:
core registers (read during a short halt), Cortex-M fault status registers
(CFSR/HFSR/DFSR/MMFAR/BFAR, read without halting), the top words of the
MSP/PSP stacks and optional memory samples at the given addresses. By default
the previous run state is restored afterwards; pass no_restore: true to
leave the core halted. Addresses accept 0xADDR or ELF symbol names (when
the server is started with an --elf file).
Breakpoints and watchpoints
| Tool | Params | Level |
|---|---|---|
set_breakpoint | address | Write |
clear_breakpoints | - | Write |
list_breakpoints | - | Read |
set_watchpoint | address, access (read/write/rw) | Write |
clear_watchpoints | - | Write |
list_watchpoints | - | Read |
Watchpoints use the core’s DWT comparators. They trigger on core load/store
accesses, not on debugger writes. If the target has no DWT comparators, the
server returns UnsupportedFeature.
DAP
| Tool | Params | Level |
|---|---|---|
read_dap | address | Read |
write_dap | address, value | Write |
DAP addresses use APSEL in bits 24-31 for AP access (e.g. 0x010000FC);
otherwise bits 0-7 are the DP register address (bits 4-7 select the DP bank).
SVD
| Tool | Params | Level |
|---|---|---|
load_svd | path | Write |
list_peripherals | - | Read |
read_peripheral | peripheral, register, field (optional) | Read |
write_peripheral | peripheral, register, field (optional), value | Write |
Field writes are read-modify-write.
Flash
| Tool | Params | Level |
|---|---|---|
erase_flash | address, size | Destructive |
program_flash | address, data or path, format (optional), verify (optional) | Destructive |
erase_flash erases only the sectors overlapping [address, address+size);
pass the full flash range to erase the whole chip. program_flash with
verify: true reads the data back after programming. Instead of raw data
you can pass a firmware file via path:
program_flash { "address": 0x08004000, "path": "/path/to/fw.hex", "format": "hex", "verify": true }
Supported formats: elf, axf (same container as ELF), bin (requires
address), hex/ihex/intelhex, or auto (default, inferred from the
file extension .elf/.axf/.bin/.hex/.ihx).
Chip definition
| Tool | Params | Level |
|---|---|---|
define_chip | flm, flash_start, flash_size, sram_start, sram_size, core (optional, default armv6m), name (optional, default FLM file stem) | Write |
define_chip registers a custom/unknown chip at runtime from a Keil FLM
flash algorithm file — no standalone probe-rs CLI or pre-built target YAML
is needed. The FLM is parsed to extract the flash algorithm (code, entry
points, page size, sector layout, erased value, timeouts), and a probe-rs
target YAML is generated and registered in the running server’s backend
registry. After registration, call connect with target set to the chip
name (or omit it when only one variant is defined) to attach.
Parameters:
flm— path to a Keil FLM file (ARM ELF containing the vendor flash algorithm and aFlashDevicedescriptor).flash_start/flash_size— Flash memory address range (e.g.0x08000000/0x10000for 64 KB). The FLM descriptor’s own values are unreliable, so you must supply these explicitly.sram_start/sram_size— SRAM address range (e.g.0x20000000/0x2000for 8 KB). The FLM does not contain this information.core— ARM architecture profile:armv6m(Cortex-M0/M0+, default),armv7m(Cortex-M3), orarmv7em(Cortex-M4/M7).name— chip/variant name used withconnect. Defaults to the FLM file stem.
Example:
define_chip {
"flm": "C:/SDK/Libraries/Flash/MyChip_64.FLM",
"flash_start": 0x08000000, "flash_size": 0x10000,
"sram_start": 0x20000000, "sram_size": 0x2000,
"core": "armv6m", "name": "MyChip"
}
connect { "target": "MyChip", "protocol": "swd" }
load_svd { "path": "C:/SDK/SVD/MyChip.svd" }
erase_flash { "address": 0x0800FC00, "size": 0x400 }
program_flash { "address": 0x0800FC00, "data": [0xDE, 0xAD, 0xBE, 0xEF], "verify": true }
Runtime configuration
| Tool | Params | Level |
|---|---|---|
get_config | - | Read |
update_config | allow_destructive (optional), tcp_port (optional), gdb_port (optional) | Write |
reload_config | - | Write |
These tools manage the server’s runtime configuration. The server can be started with zero arguments (to-be-configured state) and fully configured at runtime — no restart needed.
get_config returns the current configuration as JSON:
allow_destructive, tcp_port, gdb_port, config_file.
update_config applies a partial update: omit any field to keep its
current value. The candidate config is validated before anything is
written, so an invalid value rejects the whole update atomically (no
partial apply). After a successful update, the server reconciles its
running TCP/GDB tasks to match the new config (idempotent).
allow_destructive—trueenableserase_flash/program_flashand destructive script commands;falsedisables them.tcp_port— set to a port number (1–65535) to start or move the remote JSON-RPC TCP server on127.0.0.1; set tonullto stop it.gdb_port— set to a port number to start the GDB server. A running GDB server cannot be moved at runtime; restart the server to change its port.
reload_config re-reads the config file supplied at startup via
--config-file and applies it. Fails with a clear error when no file was
provided, the file is missing, or the contents are invalid.
Example:
get_config
-> {"allow_destructive": false, "tcp_port": null, "gdb_port": null, "config_file": null}
update_config { "allow_destructive": true, "tcp_port": 4000 }
-> {"allow_destructive": true, "tcp_port": 4000, "gdb_port": null, "config_file": null}
Scripts
| Tool | Params | Level |
|---|---|---|
run_script | path or script | Write |
run_script executes a linear debug script using a J-Link Commander /
OpenOCD style command subset. See Scripting for the full
command reference and examples.
Error codes
Errors return structured JSON with code and message:
ProbeNotFound, ConnectFailed, NotConnected, ProtocolError, Timeout,
MemoryFault, SvdNotLoaded, FileError, UnsupportedFeature,
DestructiveDisabled, InvalidArgument, InternalError.
CLI
Introduction
cmsis-dap-cli is a standalone command-line tool for humans, scripts and
automation. It shares the same cmsis-dap-core engine as the MCP server
(probe enumeration, memory, core control, SVD, flash and scripting), but talks
to you directly instead of over MCP.
The repository is a Cargo workspace with three crates:
cmsis-dap-core— the shared engine (backend, session, SVD, script engine);cmsis-dap-mcp— the MCP server binary;cmsis-dap-cli— this CLI, which only depends oncmsis-dap-core.
Install
The npm package is published; zero-install works via npx, or install globally
and use the cmsis-dap-cli command directly:
# zero-install (recommended for quick use and scripts)
npx -y cmsis-dap-cli --help
# or install globally
npm install -g cmsis-dap-cli
cmsis-dap-cli --help
For offline use, download a native binary for Windows / Linux / macOS from GitHub Releases, or build locally:
cargo build --release --workspace
./target/release/cmsis-dap-cli --help # target\release\cmsis-dap-cli.exe on Windows
To call it as plain cmsis-dap-cli, add the directory to PATH.
Quick start
cmsis-dap-cli list # enumerate probes
cmsis-dap-cli --probe-id 0123456789AB connect # connect (auto-selects chip)
cmsis-dap-cli read --address 0x20000000 --width u32 --count 4
cmsis-dap-cli halt
cmsis-dap-cli reg get pc
cmsis-dap-cli resume
Commands that need a target auto-connect using the global connection options, so a typical one-shot session looks like:
$ cmsis-dap-cli --target STM32F030C8 connect
target: {"ap_count":1,"core_count":1,"core_type":"Armv6m",...,
"memory_regions":[FLASH 0x08000000-0x08010000, SRAM 0x20000000-0x20002000]}
Global options
All options are global and can appear before or after the subcommand.
| Option | Meaning |
|---|---|
--probe-id ID | probe id or serial to select when several probes are connected |
--protocol swd|jtag | debug wire protocol (default swd) |
--speed-khz N | SWD/JTAG clock speed in kHz |
--target NAME | target chip name (probe-rs built-in or a variant from --target-yaml) |
--under-reset | connect while holding reset (locked / unresponsive targets) |
--target-yaml FILE | load a target YAML (chip + flash algorithm definitions) |
--svd FILE | SVD file for named peripheral access (svd subcommands) |
--elf FILE | firmware ELF for symbol resolution (symbols, watch, rtt, evr) |
--json | machine-readable JSON output instead of human text |
--log-level LEVEL | tracing filter; logs always go to stderr (default warn) |
--log-file FILE | write logs to a file instead of stderr |
Numbers (addresses, sizes, values) accept decimal or hex (0x...).
Command reference
Probe and session
| Command | Purpose |
|---|---|
list | enumerate connected probes |
info | show probe information (id, vendor, product, serial, capabilities) |
connect | connect to the target and show target info |
disconnect | disconnect the session |
target | show target info (auto-connects) |
Memory
| Command | Purpose |
|---|---|
read --address A --width W --count N [--output FILE --format bin|hex] | read memory, or export a range to a file (then count is bytes) |
write --address A --width W --values V1,V2,... | write memory |
verify --address A --width W --values ... | compare memory against expected values |
width is u8, u16, u32 or u64.
cmsis-dap-cli read --address 0x20000000 --width u32 --count 4
cmsis-dap-cli read --address 0x08000000 --width u8 --count 0x1000 --output fw.bin --format bin
cmsis-dap-cli write --address 0x20000000 --width u32 --values 0xDEADBEEF,1,2
Core
| Command | Purpose |
|---|---|
regs | list core register names |
reg get NAME|NUM | read a register (name or number) |
reg set NAME|NUM VALUE | write a register |
status | show core state, halt reason and PC |
halt / resume / step | control execution |
reset [--mode run|halt] | reset and continue, or reset and halt |
Register reads on a running core fail — halt first (each one-shot command
opens a new session, so use script/repl to halt and read in one session):
cmsis-dap-cli script --text "connect\nhalt\nreg pc\nresume"
Breakpoints and watchpoints
bp set ADDR | bp list | bp clear
wp set ADDR --access read|write|rw | wp list | wp clear
DAP
dap read ADDR
dap write ADDR VALUE
Raw DP/AP register access (ADDR bit 24..31 selects the AP, low bits the
register).
SVD (named peripheral access)
svd list
svd read PERIPH.REG[.FIELD]
svd write PERIPH.REG[.FIELD] VALUE
Requires --svd FILE. Target syntax: GPIOA.ODR or GPIOA.ODR.ODR0. Field
writes are read-modify-write.
cmsis-dap-cli --svd target.svd svd list
cmsis-dap-cli --svd target.svd svd read GPIOA.ODR.ODR0
cmsis-dap-cli --svd target.svd svd write GPIOA.ODR.ODR0 1
Flash
flash erase --address A --size N
flash program --address A --file FILE [--format elf|axf|bin|hex] [--verify]
Flash erase/program run directly (no confirmation). They require a target that
defines flash — otherwise the command fails with a clear error instead of
silently doing nothing. --format defaults to the file extension. --verify
reads the programmed data back.
cmsis-dap-cli flash erase --address 0x08000000 --size 0x1000
cmsis-dap-cli flash program --address 0x08000000 --file fw.hex --verify
Scripts
script --file FILE
script --text TEXT
Runs a J-Link Commander / OpenOCD style script (see Scripting).
The script command inherits the global connection options, so connect
inside the script uses them.
Chip tooling
chip generate --flm FILE --flash-start A --flash-size N --sram-start A --sram-size N [--name NAME] [--output FILE]
chip list
chip search KEYWORD
chip generate builds a probe-rs target YAML from a Keil FLM (see below).
chip list / chip search list or search the built-in chip database (plus
--target-yaml custom chips); results include flash/RAM ranges so you can tell
at a glance whether a chip can be programmed.
Symbols
symbols list [PATTERN]
symbols resolve NAME
Inspect the symbol table of a firmware ELF passed with --elf. list prints
every symbol (optionally filtered by a case-insensitive substring) with its
virtual address; resolve looks up one name. These are the same symbols used
by watch, rtt and evr to find variables and control blocks.
cmsis-dap-cli --elf firmware.axf symbols resolve counter
cmsis-dap-cli --elf firmware.axf symbols list counter
Live watch
watch [--interval-ms N] [--count N] [--width u8|u16|u32|u64]
[--log-dir DIR | --log-file FILE] TARGET...
Polls one or more variables and prints a timestamped sample line on every
interval. TARGET is a symbol name (resolved via --elf) or a 0xADDR
address. Defaults: --interval-ms 500, --count 1 (one sample), --width u32. --count 0 runs until Ctrl-C; after a clean Ctrl-C stop the command
exits 0 with stopped (Ctrl-C) on stderr.
cmsis-dap-cli --target STM32F030C8 --elf firmware.axf \
watch counter 0x20000004 --interval-ms 200 --count 0
Example output (verified on a Cortex-M0+ target with a CMSIS-DAP probe):
[2026-08-16 19:16:13.302] watch_var = 0x00001007
[2026-08-16 19:16:13.520] watch_var = 0x0000100E
[2026-08-16 19:16:13.736] watch_var = 0x00001015
RTT (J-Link RTT logs)
rtt info
rtt monitor --channel 0,1 [--interval-ms N] [--count N]
[--address A] [--log-dir DIR | --log-file FILE]
rtt info attaches to the target RTT control block and lists the up channels.
rtt monitor polls the selected up channels (comma list, default 0) and
prints every received chunk with a host timestamp and channel prefix
([RTT0 "Channel 0"] ...). The control block address is taken from the
_SEGGER_RTT symbol of --elf, from --address, or found by scanning the
target RAM — scanning needs a chip target that defines RAM (built-in chip or
--target-yaml). Defaults: --interval-ms 200, --count 0 (until Ctrl-C),
--max-bytes 1024 per channel per poll.
The firmware must run SEGGER RTT (for example rtt_target or the SEGGER RTT
implementation) and initialize the control block before the host attaches.
cmsis-dap-cli --target STM32F030C8 --elf firmware.axf \
rtt monitor --channel 0 --count 0 --log-dir logs
Event Recorder (CMSIS-View)
evr info
evr monitor [--interval-ms N] [--count N]
[--ctx 0..7] [--address A]
[--log-dir DIR | --log-file FILE]
evr info attaches to the on-chip Event Recorder and reports its protocol
version, record count, timestamp frequency and counters. evr monitor polls
the circular buffer over plain SWD/JTAG memory reads (no trace hardware, no
UART) and prints every new event, decoded from the official 16-byte record
layout: host timestamp, target tick count and seconds (via ts_freq), event
context (record info bits 16..18, 0..7), component and message numbers,
sequence and the two 32-bit values. --ctx filters by context (repeatable or
comma list). Note that the on-chip record stores a 16-bit event id
(component + message); the API level is used for filtering inside the target
and is not part of the stored record.
The firmware must include the CMSIS-View Event Recorder component (symbol
EventRecorderInfo) and initialize it before the host attaches. The info
address comes from the EventRecorderInfo symbol of --elf or from
--address.
cmsis-dap-cli --target STM32F030C8 --elf firmware.axf \
evr monitor --ctx 0,2 --count 0 --log-dir logs
Monitor output, timestamps and log export
Every watch, rtt monitor and evr monitor line carries a host capture
timestamp [YYYY-MM-DD HH:MM:SS.mmm]. With --json each sample/event is one
NDJSON object on stdout with a host_ts field (RFC 3339 with milliseconds and
time zone); EVR events keep their target timestamp_ticks / timestamp_secs.
Monitor output is also written to a log file by default. The location is the
current directory with an auto-generated name (watch-<unix>.log,
rtt-<unix>.log, evr-<unix>.log); --log-dir DIR selects another directory
(created if missing) and --log-file FILE appends to an exact file instead.
The file contains exactly what stdout prints, one line per sample/event,
flushed immediately. Monitor start prints logging to <path> on stderr.
Interactive shell
repl
Non-invasive debugging
dump [--address A]... [--stack-words N] [--no-restore]
Takes a snapshot of the target CPU without resetting it: registers,
Cortex-M fault status registers (CFSR/HFSR/DFSR/MMFAR/BFAR, read without
halting), the top words of the MSP/PSP stacks and optional memory samples.
Core registers require a short halt; by default the previous run state is
restored afterwards (--no-restore leaves the core halted). --address
accepts 0xADDR or ELF symbol names (via --elf).
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 dump \
--address 0x20000000 --stack-words 16
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 --elf fw.axf dump \
--address counter --no-restore --json
Remote TCP server
tcp-server [--port 4000]
Serves a line-delimited JSON-RPC protocol over TCP on 127.0.0.1. Requests
mirror the MCP tool names (list_probes, connect, read_memory,
write_memory, read_core_register, halt, resume, step, reset,
status, dump_cpu_state, …), one JSON object per line, with
{"id":N,"result":...} / {"id":N,"error":{...}} responses. A follow-up
request reuses the same session — no reconnect needed. cmsis-dap-mcp --tcp PORT serves the same protocol alongside the MCP stdio server.
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 tcp-server --port 4000
echo '{"id":1,"method":"read_memory","params":{"address":536870912,"width":"u32","count":4}}' \
| nc 127.0.0.1 4000
GDB server
gdb-server [--port 1337] [--reset-halt]
Exposes a GDB Remote Serial Protocol stub (ported from
probe-rs-tools via
gdbstub, MIT OR Apache-2.0): connect
any GDB (target remote :1337) to read/write registers and memory, run,
single-step, halt and use hardware breakpoints. Attach is non-invasive (no
reset; --reset-halt opts in). cmsis-dap-mcp --gdb-port 1337 starts the
same server inside the MCP process.
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 gdb-server --port 1337
arm-none-eabi-gdb fw.elf -ex 'target remote :1337' -ex 'info registers'
Related references: the GDB Remote Serial Protocol and the MCP specification (modelcontextprotocol.io); Cortex-M fault status registers are part of the ARM System Control Block (see the ARMv6-M Architecture Reference Manual); Event Recorder details are in the CMSIS-View documentation.
Generating a target YAML from an FLM
For chips that are not built into probe-rs, flashing needs a target YAML that
describes the chip and embeds the vendor flash algorithm. You do not have to
hand-write it — chip generate reads a Keil FLM and only needs the Flash and
SRAM address ranges from you:
cmsis-dap-cli chip generate \
--flm MyChip_64.FLM \
--flash-start 0x08000000 --flash-size 0x10000 \
--sram-start 0x20000000 --sram-size 0x2000 \
--name MYCHIP --output MYCHIP.yaml
Everything else is extracted from the FLM automatically: the algorithm
instructions, entry-point offsets (Init/ProgramPage/EraseSector/
EraseChip), the static data base, the FlashDevice descriptor (page size,
erased value, sector size, timeouts) and the device name. --name defaults to
the FLM file stem; use --output - to print the YAML to stdout.
Then connect with it:
cmsis-dap-cli --target-yaml MYCHIP.yaml connect
When the target YAML defines exactly one chip variant, --target can be
omitted — the CLI auto-selects it. With several variants, --target NAME is
required (the command lists the available names).
The generated YAML places the algorithm at SRAM start + 0x20; make sure the
SRAM range is large enough (the command refuses to emit a YAML that would not
fit).
Listing and searching chips
cmsis-dap-cli chip list
cmsis-dap-cli chip search STM32F103
cmsis-dap-cli chip search stm32f103c8
cmsis-dap-cli --target-yaml MYCHIP.yaml chip search MYCHIP
Search is case-insensitive and matches substrings. With --json the full
details (family, cores, flash and RAM ranges) are returned for scripting.
Examples
End-to-end debug session
cmsis-dap-cli list
cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 connect
cmsis-dap-cli --target STM32F030C8 read --address 0x20000000 --width u32 --count 4
cmsis-dap-cli --target STM32F030C8 halt
cmsis-dap-cli --target STM32F030C8 reg get pc
cmsis-dap-cli --target STM32F030C8 step
cmsis-dap-cli --target STM32F030C8 resume
Program firmware and verify
cmsis-dap-cli --target STM32F030C8 flash erase --address 0x08000000 --size 0x10000
cmsis-dap-cli --target STM32F030C8 flash program --address 0x08000000 --file fw.hex --verify
cmsis-dap-cli --target STM32F030C8 read --address 0x08000000 --width u8 --count 0x100 --output dump.bin --format bin
Script file
flash.jlink:
connect
halt
reg pc
savebin C:/dump.bin 0x20000000 0x100
resume
q
cmsis-dap-cli --target STM32F030C8 script --file flash.jlink
Machine-readable output
cmsis-dap-cli --json connect
cmsis-dap-cli --json read --address 0x20000000 --width u32 --count 2
{"target":{"core_type":"Armv6m","core_count":1,"ap_count":1, ...}}
{"address":536870912,"width":"u32","values":[64000000,1]}
Output and exit codes
- Default output is human-readable;
--jsonprints the same structured payloads the MCP tools return. Logs always go to stderr. - Exit codes:
0success,1runtime error (probe/connect/flash failures),2usage error (unknown option, invalid value, missing argument). - Monitor commands (
watch,rtt monitor,evr monitor) print one line per sample/event (NDJSON in--jsonmode) and exit0after a clean Ctrl-C stop;--count Nbounds the run for scripts and CI.
REPL
repl starts an interactive shell that keeps one session open, so
halt/read/resume sequences work across lines:
$ cmsis-dap-cli --probe-id 0123456789AB --target STM32F030C8 repl
cmsis-dap-cli> connect
target: {"ap_count":1,"core_count":1,"core_type":"Armv6m", ...}
cmsis-dap-cli> halt
halted: true
cmsis-dap-cli> reg pc
pc = 0x800122A
cmsis-dap-cli> resume
running: true
cmsis-dap-cli> q
?/help shows the supported commands; q/exit quits. The REPL inherits
the global connection options, so connect uses them (no need to retype
--target). Flash erase/program run directly in the REPL too.
The REPL also exposes the live debugging commands with persistent watch state:
watch add <name|0xADDR> [--width u8|u16|u32|u64] [--label TEXT]
watch list | watch remove <idx|name> | watch clear
watch interval <ms>
watch run [--count N] [--log-dir DIR | --log-file FILE]
rtt [info] [--channel 0,1] [--count N] [--interval-ms N] [--log-dir DIR | --log-file FILE]
evr [info] [--ctx 0..7] [--count N] [--log-dir DIR | --log-file FILE]
Monitors run until Ctrl-C (or --count N) and return to the prompt.
Script commands
The script engine (used by script and the REPL) supports:
connect | disconnect | init session management
si swd|jtag interface
speed <khz> clock speed
device <name> target chip
adapter serial <id> probe selection
halt | go | step execution
reset [run|halt] reset
reg <name> [<value>] | regs core registers
mem8/16/32 <addr> [<n>] | mdb/mdh/mdw read memory
w8/16/32 <addr> <value> | mwb/mwh/mww write memory
savebin <file> <addr> <size> export memory to a binary file
dump_image <file> <addr> <size> alias of savebin
loadbin <file> <addr> program a binary file
loadfile <file> [<addr>] program axf/elf/bin/hex
flash write_image <file> [<addr>] alias of loadfile
flash erase_sector <addr> <size> erase a flash range
erase erase all flash
verifybin <file> [<addr>] verify a binary file against memory
verify_image <file> [<addr>] alias of verifybin
sleep <ms> | echo <text> helpers
targets show connected target
? | help | q | exit help and quit
Tips and troubleshooting
- Picking a chip: built-in chips (
chip search NAME) work with just--target NAME. For other chips, generate a target YAML once withchip generateand load it with--target-yaml(single-variant YAMLs auto-select; multi-variant YAMLs require--target). - Flash needs a chip definition: without one, erase/program fail with a clear error instead of silently doing nothing.
- Register reads need a halted core: in one-shot mode, use
script/replsohaltandregshare a session. - Flash cannot be written with
write: raw memory writes to flash are rejected; useflash program. - Numbers: decimal or hex (
0x...) everywhere.
Scripting
run_script executes a linear debug script with a J-Link Commander / OpenOCD
style command subset. It is useful for repeatable workflows such as connect,
dump memory, program a file and reset, without issuing each tool call
separately.
Running a script
Provide a script file path, or inline text:
run_script { "path": "/path/to/demo.jlink" }
run_script { "script": "halt\nreg pc\nresume" }
Exactly one of path and script is required. Scripts run sequentially and
stop on the first failing command. The result contains ok, the number of
commands, and one result per command:
{
"ok": true,
"commands": 3,
"results": [
{ "command": "halt", "status": "ok", "output": { "halted": true } },
{ "command": "reg pc", "status": "ok", "output": { "register": "pc", "value": 134228884 } },
{ "command": "resume", "status": "ok", "output": { "running": true } }
]
}
Syntax
- One command per line;
;is also accepted as a separator (OpenOCD style). - Comments start with
//or#. - Arguments can be quoted with
"..."or'...'(paths with spaces). - Numbers accept decimal or
0xhexadecimal. sleep <ms>pauses;echo <text>prints;q/exitstops the script.
Command reference
J-Link Commander names are primary; OpenOCD aliases map to the same operations.
| Area | J-Link | OpenOCD alias | Meaning |
|---|---|---|---|
| Session | connect, si SWD|JTAG, speed <khz>, device <name>, disconnect | init, adapter speed <khz>, adapter serial <serial>, targets | Connect / configure session |
| Core | halt, go, step, reset [halt|run], reg <name> [value], regs | resume, reset, reg <name> [value] | Execution control |
| Memory | mem8/16/32 <addr> [count], w8/16/32 <addr> <value> | mdb/mdh/mdw, mwb/mwh/mww | Read/write memory |
| Files | savebin <path> <addr> <size>, loadbin <path> <addr>, loadfile <path> [addr], verifybin <path> <addr> | dump_image <path> <addr> <size>, flash write_image <path> [offset], verify_image <path> [offset] | Export / program / verify files |
| Flash | erase | flash erase_sector <addr> <size> | Erase flash |
| Misc | sleep <ms>, echo <text>, q / exit | - | Utility commands |
savebin / dump_image export raw binary. loadbin programs a raw binary
at the given address. loadfile / flash write_image program a file whose
format is inferred from the extension (elf/axf/bin/hex). verifybin /
verify_image compare a file with target memory.
Examples
Save the first 16 KB of flash, then program a binary and verify it:
connect
savebin C:/dump/fw.bin 0x08000000 0x4000
loadbin C:/fw/new.bin 0x08000000
verifybin C:/fw/new.bin 0x08000000
reset halt
go
q
OpenOCD style inline script:
halt; mdw 0x20000000 4; reg pc; resume
Program a HEX file:
connect
flash write_image C:/fw/out.hex
reset
Security
run_script is a write-level tool. Destructive commands inside a script
(erase, loadbin, loadfile, flash write_image, flash erase_sector)
additionally require the server to be started with --allow-destructive;
otherwise they fail with DestructiveDisabled.
SWD and JTAG
Both protocols are supported. Select one at connect time, or set the
default with the --protocol server option.
connect { "protocol": "swd" } # default
connect { "protocol": "jtag" }
list_probes reports which protocols the connected probe supports. Most
CMSIS-DAP probes support both.
Which one to use
- SWD is the default and works on any Cortex-M with a debug port. It uses two wires (SWDIO, SWCLK) plus reset.
- JTAG requires the target to expose a JTAG TAP and the four/five JTAG pins. Many small Cortex-M0/M0+ devices do not bring out JTAG.
The server was verified on hardware over SWD. If a target does not support
JTAG, connect returns ConnectFailed with a protocol error; the toolset
still fully supports JTAG for targets that expose it.
Speed
Pass speed_khz in connect, or set --speed-khz at startup. The probe
selects the highest supported speed at or below the request.
Connect under reset
For locked or non-responsive targets, hold the reset line during attach:
connect { "protocol": "swd", "under_reset": true }
This requires the probe’s reset pin to be wired to the target’s reset.
SVD and Flash
SVD files
SVD files describe a chip’s peripherals and registers. Provide your own file at runtime:
load_svd { "path": "/path/to/your-chip.svd" }
list_peripherals {}
read_peripheral { "peripheral": "GPIOA", "register": "ODR" }
write_peripheral { "peripheral": "GPIOA", "register": "ODR", "field": "ODR0", "value": 1 }
Field writes are read-modify-write. This repository never bundles chip-specific data.
Flash programming
Flash tools need a target description with a flash algorithm. Generate a probe-rs target YAML from your chip’s CMSIS-Pack (or write one by hand) and start the server with it:
cmsis-dap-mcp --target-yaml /path/to/your-target.yaml --allow-destructive
Connect with the target name defined in the YAML, then erase and program:
connect { "protocol": "swd", "target": "YourChip" }
erase_flash { "address": 0x08000000, "size": 0x1000 }
program_flash { "address": 0x08000000, "data": [0x00, 0x11, ...], "verify": true }
verify: true reads the data back after programming. erase_flash erases
only the sectors overlapping the requested range; pass the full flash range
to erase the whole chip.
Recommended flash workflow (verified)
- Read out the current firmware first and keep it as a backup.
- Erase only the sectors you intend to write.
- Program with
verify: true. - Read back and
verify_memorythe result. - Restore the backup if the target must keep its original firmware.
Security
- Read-only tools are always available.
- Write and debug-control tools are marked as writes; your MCP client governs approval.
erase_flashandprogram_flashare destructive and disabled by default. Enable them either at startup with--allow-destructiveor at runtime viaupdate_configwithallow_destructive: true. Calling them while disabled returnsDestructiveDisabled.
Flash erasing, option-byte changes, read-protection and debug unlock can permanently damage a device or make it unrecoverable. Only enable destructive mode when you explicitly intend to reprogram the target.
Logs are written to stderr (or --log-file) only, never to stdout, so they
cannot corrupt the MCP protocol stream.
read_memory with a path argument writes an export file (bin/hex) on the
host at the path you provide; run_script may read and write files on the
host too. This uses the same trust model as load_svd: the paths come from
the user and are executed on the machine running the server.
Troubleshooting
Probe not listed
- Windows: CMSIS-DAP v2 probes need a WinUSB driver. Use Zadig to replace the driver with WinUSB if the probe does not appear. CMSIS-DAP v1 (HID) probes usually work without a driver.
- Linux: install a udev rule granting access to the USB device (see README), then replug the probe.
- macOS: usually works out of the box; check System Settings > Privacy & Security if the probe is blocked.
Connect fails
- Check the wiring: SWDIO/SWCLK (and nRST when using
under_reset). - Lower the speed:
connect { "speed_khz": 100 }. - Try
under_reset: truefor locked targets. - JTAG fails with
ConnectFailedon targets that do not expose a JTAG TAP; use SWD instead.
Register name errors
Names are case-insensitive and role-aware (pc, sp, fp, lr, ra,
psr, xpsr, msp, psp, fpsr, r0-r15). Other names must match the
architecture register file; use list_core_registers to see what is
available.
Flash tools return DestructiveDisabled
Start the server with --allow-destructive.
Flash algorithm fails to load
- The target YAML must define a RAM region large enough for the algorithm, the header and the stack.
load_addressmust leave room for the 4-byte algorithm header, e.g.0x20000020for a RAM region starting at0x20000000.pc_init,pc_uninit,pc_erase_sector,pc_program_pageandpc_erase_allin the YAML are offsets from the code start address.
File formats and scripts
binfiles have no address information: always passaddress(or the scriptloadbinaddress).axffiles are ELF containers: use formataxforauto; they are parsed with the ELF loader.hexfiles are standard Intel HEX (type 00/04/01); invalid checksums or records returnFileError.- A valid ELF/AXF must contain loadable sections; an ELF with no sections
fails with
FileError(“no loadable segments”). - Scripts stop at the first failing command; check the per-command
statusandoutputin the result.
AI client does not show the tools
- Restart the client after adding the server.
- For Codex,
codex mcp listmust show the server as enabled; the desktop app loads it when a new session starts. - Verify the binary path in the client configuration is correct and executable.
RTT / Event Recorder
RTT attach failed: control block not found— the target firmware must initialize SEGGER RTT (SEGGER_RTT_Init()) and the host must attach after that, not while the core is halted beforemain. Pass--elf(the_SEGGER_RTTsymbol) or--address, and run the monitor fromreplafterreset runso the core is actually executing.evrrequires an address — the firmware must include the CMSIS-View Event Recorder component (symbolEventRecorderInfo). Pass--elfor--address, and initialize the recorder withEventRecorderInitializebefore attaching.- One-shot commands read stale values — every one-shot invocation opens a
new session and probe-rs attaches with the core halted. Use
replwithconnect+reset run, thenwatch run/rtt monitor/evr monitor. - EVR timestamps look scaled — seconds are derived from the firmware’s
ts_freq(EVENT_TIMESTAMP_FREQ); set it to the actual timestamp clock (e.g.SystemCoreClock) for accurate wall time. Ticks themselves are always monotonic.
Architecture
cmsis-dap-mcp is a single Rust process that speaks MCP over stdio. It is a
pure server: an MCP client (Codex, Claude Code, opencode, or any MCP-compatible
host) drives it, and it never renders its own UI.
The repository is a Cargo workspace with three crates: cmsis-dap-core (the
MCP-independent engine shared by both tools), cmsis-dap-mcp (this server)
and cmsis-dap-cli (a standalone CLI over the same engine). The diagram below
shows the server side of that workspace.
System overview
MCP client (Codex / Claude Code / opencode / any MCP host)
|
| MCP stdio: JSON-RPC 2.0, newline-delimited, on stdout
v
+--------------------------------------------------------------+
| cmsis-dap-mcp (single Rust process, logs only to stderr) |
| |
| +--------------------------------------------------------+ |
| | MCP tool layer (rmcp) | |
| | probe | memory | core | dap | svd | flash | file | script | |
| +--------------------------------------------------------+ |
| | Security policy: read-only / write / destructive | |
| +--------------------------------------------------------+ |
| | SessionManager: probe selection, session & SVD state | |
| +--------------------------------------------------------+ |
| | Backend trait | |
| | ProbeRsBackend (real) MockBackend (tests) | |
| +--------------------------------------------------------+ |
| | probe-rs library (SWD/JTAG, flash, ELF/HEX/BIN parsing) | |
| +--------------------------------------------------------+ |
+--------------------------------------------------------------+
|
| USB (HID / WinUSB)
v
CMSIS-DAP probe ---- SWD / JTAG ----> Cortex-M target
Module responsibilities
| Module | Responsibility |
|---|---|
cli | Parse startup arguments, configure logging, start the stdio server |
config | ServerConfig runtime-mutable fields (allow_destructive, tcp_port, gdb_port); JSON config-file loading |
runtime | ServerRuntime: owns the shared config, session and running TCP/GDB tasks; reconcile() is the single idempotent convergence point for every config change; optional config-file watcher |
mcp | Register tools with rmcp, MCP annotations, server instructions |
mcp/tools_* | Per-area parameters and handlers (probe, memory, core, dap, svd, flash, script, chip, config) |
script | Linear J-Link Commander / OpenOCD style script parser and executor |
hex | Intel HEX encoder used for memory export |
security | Three-tier policy; destructive tools require --allow-destructive or a runtime update_config enable |
session | Single active session; owns probe/session and SVD state |
backend | Backend trait with ProbeRsBackend and MockBackend implementations, including RTT attach/read and Event Recorder attach/poll |
backend/chip | Keil FLM parsing (algorithm, entry points, FlashDevice descriptor) and probe-rs target YAML generation; powers the define_chip MCP tool and the CLI chip generate command |
gdb | GDB Remote Serial Protocol stub (ported from probe-rs-tools via gdbstub); non-invasive attach, registers/memory/run/step/hardware breakpoints |
remote | Remote TCP JSON-RPC server reusing one session; methods mirror MCP tool names (read_memory, write_memory, halt, resume, step, reset, status, dump_cpu_state, …) |
evr | CMSIS-View Event Recorder decoding (official 16-byte record layout), used by the CLI’s evr command |
svd | SVD parsing and named peripheral/register/field resolution |
error | Error codes and structured McpError |
Tool call flow
MCP client server backend target
| tools/call | | |
|----------------->| security check | |
| | lock session | |
| | backend.read_memory() |-- SWD/JTAG read ---->|
| |<-----------------------| |
|<-----------------| structured JSON | |
Every tool call goes through the same path: parse and validate parameters, check the security tier, acquire the session, run the operation on the backend, and return structured JSON (or a classified error).
File and script paths
program_flash {data: [...]} -> backend.program_flash -> FlashLoader (raw data)
program_flash {path, format} -> backend.program_file -> BIN: read + add_data
ELF/AXF/HEX: probe-rs build_loader
read_memory {path, format} -> backend.export_memory -> BIN: raw bytes
HEX: hex::encode_ihex
run_script {path | script} -> script::run -> per-command dispatch to backend
(destructive commands gated by policy)
Build and release pipeline
feature branch -> develop -> main -> tag vX.Y.Z
|
v
CI: fmt / clippy / test / build on 3 OSes
|
+-----------------------+-----------------------+
| |
v v
GitHub Release binaries npm platform packages
(win32/linux/darwin × x64/arm64) (meta cmsis-dap-mcp + platform packages)
|
v
GitHub Pages docs (English at /, Chinese at /zh/)
Development
Build and test
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --release --workspace
This builds both binaries: target/release/cmsis-dap-mcp (MCP server) and
target/release/cmsis-dap-cli (CLI).
Code style
cargo fmt --checkmust pass; format withcargo fmtbefore committing.cargo clippy --workspace --all-targets -- -D warningsmust pass with no warnings.- Commit messages follow Conventional Commits:
type(scope): subject, where type is one offeat,fix,docs,refactor,test,chore,perf. Reference the CHANGELOG style for examples. - The repository must stay free of vendor-specific terms; run
scripts/check-no-vendor.ps1(Windows PowerShell) before pushing. The CI enforces this check.
Contributing
- Branch strategy: feature branches merge into
develop, thendevelopmerges intomain. Pushing avX.Y.Ztag triggers the release workflow. - Pull requests must pass the full CI suite (fmt / clippy / test / build on Windows, Linux and macOS) and the vendor-content scan.
- Hardware-verified changes are preferred: when a feature touches probe or target behavior, validate it on a real CMSIS-DAP probe + Cortex-M board before opening the PR.
- Keep the English and Chinese documentation in sync: any user-visible
change in
docs/src/must be mirrored indocs/zh/src/.
Testing strategy
- Unit tests: per-crate tests under
crates/*/tests/cover backend behavior (mock and probe-rs), SVD parsing, hex encoding, register hints, security policy, session management and scripting. - Integration tests:
crates/cmsis-dap-cli/tests/exercise the CLI end to end (args, commands, live monitors, non-invasive dump);crates/cmsis-dap-mcp/tests/cover MCP handlers and feature flags. - Hardware verification: before each release, run a full end-to-end session on a real CMSIS-DAP probe + Cortex-M target (list probes, connect, read/write memory, halt/resume, register access, flash program with verify, live watch / RTT / Event Recorder monitors, non-invasive dump). This is manual and not part of CI.
Documentation maintenance
Before each release, run this checklist to keep documentation in sync with code:
- Compare
CHANGELOG.mdagainst the actual diff since the last release; add missing entries under the## [vX.Y.Z] - unreleasedsection. - Audit every README (
README.md,npm/README.md,npm-cli/README.md) against the current feature set; update tool tables and configuration examples. - Verify
docs/src/SUMMARY.mdanddocs/zh/src/SUMMARY.mdreflect the current chapter list and the user/developer grouping. - Check
docs/src/tools.mdagainst the MCP tool implementations incrates/cmsis-dap-mcp/src/mcp/; add any new tool and its parameters. - Check
docs/src/architecture.mdmodule table againstcrates/cmsis-dap-core/src/; add any new module. - Sync the Chinese mirror in
docs/zh/src/— structure, examples and command output must match the English version. - Build both books locally:
mdbook build docs # English
mdbook build docs/zh # Chinese
- Run the vendor-content scan:
powershell -File scripts/check-no-vendor.ps1
If any step surfaces a discrepancy, fix it before tagging the release.
Documentation
mdbook build docs # English
mdbook build docs/zh # Chinese
Release process
The repository follows GitFlow: feature branches merge into develop, then
develop into main. Pushing a vX.Y.Z tag triggers the release workflow,
which builds the three platform binaries for both tools, publishes the
cmsis-dap-mcp and cmsis-dap-cli npm meta packages plus their platform
packages, uploads GitHub Release assets, and rebuilds the GitHub Pages
documentation.
Before releasing, run the full verification suite on real hardware and the vendor-content scan:
powershell -File scripts/check-no-vendor.ps1