Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

ToolWhat it does
list_probesEnumerate all connected CMSIS-DAP probes
get_probe_infoView probe details (product, serial, protocols, speeds)
connectConnect to a target via SWD or JTAG; supports under-reset connect
disconnectEnd the current session
get_target_infoView target info (core type, CPUID, memory regions)

Memory access

ToolWhat it does
read_memoryRead memory (u8/u16/u32/u64); export a range as bin/hex file
write_memoryWrite memory
verify_memoryRead back and compare against expected data; report mismatches

Core control

ToolWhat it does
read_core_register / write_core_registerRead/write core registers (pc, sp, lr, r0-r15, …)
list_core_registersList all registers available on the target
get_core_statusQuery core state (running/halted/sleeping/locked up)
halt / resume / stepPause / resume / single-step execution
resetReset the target; continue or halt after reset

Breakpoints and watchpoints

ToolWhat it does
set_breakpoint / clear_breakpoints / list_breakpointsHardware breakpoint management
set_watchpoint / clear_watchpoints / list_watchpointsDWT data watchpoints (read/write/rw trigger)

DAP raw access

ToolWhat it does
read_dap / write_dapDirect DP/AP register read/write (advanced debugging)

SVD named peripherals

ToolWhat it does
load_svdLoad any CMSIS-SVD file at runtime
list_peripheralsList all loaded peripherals
read_peripheral / write_peripheralRead/write peripheral registers and bitfields by name (read-modify-write)

Flash programming

ToolWhat it does
erase_flashErase flash by sector (only sectors overlapping the requested range)
program_flashProgram firmware from elf/axf/bin/hex files; optional read-back verify

Chip definition

ToolWhat 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 searchList or search the built-in chip library

Script engine

ToolWhat it does
run_script (MCP) / script (CLI)Execute J-Link Commander / OpenOCD style debug scripts

Non-invasive debugging

ToolWhat it does
dump_cpu_state (MCP) / dump (CLI)Take a CPU snapshot without resetting: registers, fault status, stacks, memory

Remote access

FeatureDescription
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

ToolWhat it does
get_configView current runtime configuration
update_configUpdate config at runtime (destructive gate, TCP/GDB ports) without restart
reload_configRe-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)

FeatureDescription
watchPoll variables by address or ELF symbol with configurable refresh; timestamped log export
rtt monitorRead SEGGER RTT up-channel logs over SWD/JTAG — no UART needed
evr monitorDecode CMSIS-View Event Recorder events — no trace hardware needed
replInteractive 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-mcp or a single native binary
  • Cross-platform: Windows / Linux / macOS

Documentation

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

  1. 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
  2. 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
  3. SWD wires

    • Minimum 3 wires: SWDIO, SWCLK, GND
    • Connect probe SWD pins to the matching debug port on your board

Optional

  1. nRST reset wire
    • Used for under_reset mode (locked or non-responsive targets)
    • Connect the probe’s nRST pin to the board’s reset pin

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:

FeatureFile neededWhere to get it
Basic debug (memory, registers, execution)None — works out of the box
Named peripheral accessSVD fileChip vendor SDK or CMSIS-Pack
Flash programmingKeil FLM flash algorithm fileIDE installation directory or chip vendor
Symbol-level debug (watch/RTT/EVR)Firmware ELF or AXF fileYour 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

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.
    1. Download Zadig from https://zadig.akeo.ie/
    2. Plug in the probe and open Zadig
    3. Go to Options -> List All Devices
    4. Select your CMSIS-DAP device
    5. 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 lsusb on 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 repl to 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

  1. Obtain the FLM flash algorithm file for your chip
  2. 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

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.

StyleExampleBest for
npx packagecommand = "npx", args = ["-y", "cmsis-dap-mcp"]Published releases; updates with npm
Local binarycommand = "/path/to/cmsis-dap-mcp"Local builds, offline, exact version
Remote URLurl = "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).

  1. list_probes to find your probe id.
  2. connect with {"protocol": "swd", "speed_khz": 1000}.
  3. read_memory / write_memory for raw access.
  4. halt, then read_core_register (e.g. pc, sp, lr, r0).
  5. resume when done.
  6. load_svd with your own SVD path for named peripheral access.
  7. program_flash / erase_flash require destructive mode: start the server with --allow-destructive, or call update_config {"allow_destructive": true} at runtime (no restart needed).
  8. For a chip not built into probe-rs, call define_chip with a Keil FLM file before connect (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:

StyleExampleWhen to use
npx package (standard)command = "npx", args = ["-y", "cmsis-dap-mcp"]Published releases; first launch downloads and caches the package
Local binarycommand = "/path/to/cmsis-dap-mcp"Unpublished or locally built servers, offline use, exact version pinning
Remote URLurl = "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.

OptionMeaning
--allow-destructiveenable erase_flash / program_flash and destructive script commands at startup
--tcp PORTalso serve the remote JSON-RPC TCP server on 127.0.0.1:PORT
--gdb-port PORTalso start a GDB server on 127.0.0.1:PORT
--config-file FILEJSON config file (allow_destructive, tcp_port, gdb_port keys); loaded at startup, watchable for changes
--probe-id IDdefault probe id for connect
--protocol swd|jtagdefault debug protocol (default swd)
--speed-khz Ndefault SWD/JTAG clock speed
--target NAMEdefault target chip name
--svd FILESVD file to load at startup
--target-yaml FILEtarget YAML to pre-load into the chip registry
--log-level LEVELtracing filter; logs go to stderr (default info)
--log-file FILEwrite 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). Decimal 536870912 is equivalent.
  • Write tools such as connect, halt and resume may 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

ToolParamsLevel
list_probes-Read
get_probe_infoprobe_id (optional)Read
connectprobe_id, protocol (swd/jtag, default swd), speed_khz, target, under_resetWrite
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

ToolParamsLevel
read_memoryaddress, width (u8/u16/u32/u64), count (default 1)Read
write_memoryaddress, width, valuesWrite
verify_memoryaddress, width, dataRead

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

ToolParamsLevel
read_core_registername or numberRead
write_core_registername or number, valueWrite
list_core_registers-Read
get_core_status-Read
halt-Write
resume-Write
step-Write
resetmode (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

ToolParamsLevel
dump_cpu_stateaddress (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

ToolParamsLevel
set_breakpointaddressWrite
clear_breakpoints-Write
list_breakpoints-Read
set_watchpointaddress, 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

ToolParamsLevel
read_dapaddressRead
write_dapaddress, valueWrite

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

ToolParamsLevel
load_svdpathWrite
list_peripherals-Read
read_peripheralperipheral, register, field (optional)Read
write_peripheralperipheral, register, field (optional), valueWrite

Field writes are read-modify-write.

Flash

ToolParamsLevel
erase_flashaddress, sizeDestructive
program_flashaddress, 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

ToolParamsLevel
define_chipflm, 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 a FlashDevice descriptor).
  • flash_start / flash_size — Flash memory address range (e.g. 0x08000000 / 0x10000 for 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 / 0x2000 for 8 KB). The FLM does not contain this information.
  • core — ARM architecture profile: armv6m (Cortex-M0/M0+, default), armv7m (Cortex-M3), or armv7em (Cortex-M4/M7).
  • name — chip/variant name used with connect. 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

ToolParamsLevel
get_config-Read
update_configallow_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_destructivetrue enables erase_flash / program_flash and destructive script commands; false disables them.
  • tcp_port — set to a port number (1–65535) to start or move the remote JSON-RPC TCP server on 127.0.0.1; set to null to 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

ToolParamsLevel
run_scriptpath or scriptWrite

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 on cmsis-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.

OptionMeaning
--probe-id IDprobe id or serial to select when several probes are connected
--protocol swd|jtagdebug wire protocol (default swd)
--speed-khz NSWD/JTAG clock speed in kHz
--target NAMEtarget chip name (probe-rs built-in or a variant from --target-yaml)
--under-resetconnect while holding reset (locked / unresponsive targets)
--target-yaml FILEload a target YAML (chip + flash algorithm definitions)
--svd FILESVD file for named peripheral access (svd subcommands)
--elf FILEfirmware ELF for symbol resolution (symbols, watch, rtt, evr)
--jsonmachine-readable JSON output instead of human text
--log-level LEVELtracing filter; logs always go to stderr (default warn)
--log-file FILEwrite logs to a file instead of stderr

Numbers (addresses, sizes, values) accept decimal or hex (0x...).

Command reference

Probe and session

CommandPurpose
listenumerate connected probes
infoshow probe information (id, vendor, product, serial, capabilities)
connectconnect to the target and show target info
disconnectdisconnect the session
targetshow target info (auto-connects)

Memory

CommandPurpose
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

CommandPurpose
regslist core register names
reg get NAME|NUMread a register (name or number)
reg set NAME|NUM VALUEwrite a register
statusshow core state, halt reason and PC
halt / resume / stepcontrol 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 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; --json prints the same structured payloads the MCP tools return. Logs always go to stderr.
  • Exit codes: 0 success, 1 runtime error (probe/connect/flash failures), 2 usage error (unknown option, invalid value, missing argument).
  • Monitor commands (watch, rtt monitor, evr monitor) print one line per sample/event (NDJSON in --json mode) and exit 0 after a clean Ctrl-C stop; --count N bounds 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 with chip generate and 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/repl so halt and reg share a session.
  • Flash cannot be written with write: raw memory writes to flash are rejected; use flash 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 0x hexadecimal.
  • sleep <ms> pauses; echo <text> prints; q / exit stops the script.

Command reference

J-Link Commander names are primary; OpenOCD aliases map to the same operations.

AreaJ-LinkOpenOCD aliasMeaning
Sessionconnect, si SWD|JTAG, speed <khz>, device <name>, disconnectinit, adapter speed <khz>, adapter serial <serial>, targetsConnect / configure session
Corehalt, go, step, reset [halt|run], reg <name> [value], regsresume, reset, reg <name> [value]Execution control
Memorymem8/16/32 <addr> [count], w8/16/32 <addr> <value>mdb/mdh/mdw, mwb/mwh/mwwRead/write memory
Filessavebin <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
Flasheraseflash erase_sector <addr> <size>Erase flash
Miscsleep <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.

  1. Read out the current firmware first and keep it as a backup.
  2. Erase only the sectors you intend to write.
  3. Program with verify: true.
  4. Read back and verify_memory the result.
  5. 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_flash and program_flash are destructive and disabled by default. Enable them either at startup with --allow-destructive or at runtime via update_config with allow_destructive: true. Calling them while disabled returns DestructiveDisabled.

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: true for locked targets.
  • JTAG fails with ConnectFailed on 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_address must leave room for the 4-byte algorithm header, e.g. 0x20000020 for a RAM region starting at 0x20000000.
  • pc_init, pc_uninit, pc_erase_sector, pc_program_page and pc_erase_all in the YAML are offsets from the code start address.

File formats and scripts

  • bin files have no address information: always pass address (or the script loadbin address).
  • axf files are ELF containers: use format axf or auto; they are parsed with the ELF loader.
  • hex files are standard Intel HEX (type 00/04/01); invalid checksums or records return FileError.
  • 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 status and output in the result.

AI client does not show the tools

  • Restart the client after adding the server.
  • For Codex, codex mcp list must 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 before main. Pass --elf (the _SEGGER_RTT symbol) or --address, and run the monitor from repl after reset run so the core is actually executing.
  • evr requires an address — the firmware must include the CMSIS-View Event Recorder component (symbol EventRecorderInfo). Pass --elf or --address, and initialize the recorder with EventRecorderInitialize before attaching.
  • One-shot commands read stale values — every one-shot invocation opens a new session and probe-rs attaches with the core halted. Use repl with connect + reset run, then watch 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

ModuleResponsibility
cliParse startup arguments, configure logging, start the stdio server
configServerConfig runtime-mutable fields (allow_destructive, tcp_port, gdb_port); JSON config-file loading
runtimeServerRuntime: 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
mcpRegister tools with rmcp, MCP annotations, server instructions
mcp/tools_*Per-area parameters and handlers (probe, memory, core, dap, svd, flash, script, chip, config)
scriptLinear J-Link Commander / OpenOCD style script parser and executor
hexIntel HEX encoder used for memory export
securityThree-tier policy; destructive tools require --allow-destructive or a runtime update_config enable
sessionSingle active session; owns probe/session and SVD state
backendBackend trait with ProbeRsBackend and MockBackend implementations, including RTT attach/read and Event Recorder attach/poll
backend/chipKeil 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
gdbGDB Remote Serial Protocol stub (ported from probe-rs-tools via gdbstub); non-invasive attach, registers/memory/run/step/hardware breakpoints
remoteRemote TCP JSON-RPC server reusing one session; methods mirror MCP tool names (read_memory, write_memory, halt, resume, step, reset, status, dump_cpu_state, …)
evrCMSIS-View Event Recorder decoding (official 16-byte record layout), used by the CLI’s evr command
svdSVD parsing and named peripheral/register/field resolution
errorError 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 --check must pass; format with cargo fmt before committing.
  • cargo clippy --workspace --all-targets -- -D warnings must pass with no warnings.
  • Commit messages follow Conventional Commits: type(scope): subject, where type is one of feat, 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, then develop merges into main. Pushing a vX.Y.Z tag 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 in docs/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:

  1. Compare CHANGELOG.md against the actual diff since the last release; add missing entries under the ## [vX.Y.Z] - unreleased section.
  2. Audit every README (README.md, npm/README.md, npm-cli/README.md) against the current feature set; update tool tables and configuration examples.
  3. Verify docs/src/SUMMARY.md and docs/zh/src/SUMMARY.md reflect the current chapter list and the user/developer grouping.
  4. Check docs/src/tools.md against the MCP tool implementations in crates/cmsis-dap-mcp/src/mcp/; add any new tool and its parameters.
  5. Check docs/src/architecture.md module table against crates/cmsis-dap-core/src/; add any new module.
  6. Sync the Chinese mirror in docs/zh/src/ — structure, examples and command output must match the English version.
  7. Build both books locally:
mdbook build docs        # English
mdbook build docs/zh     # Chinese
  1. 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