Skip to content

Tools

mcjtag exposes 17 tools organized across 9 mixin categories. Each tool is a callable MCP function that an LLM client (or any MCP-compatible client) invokes over JSON-RPC.

All tools that interact with the target require an active OpenOCD connection. If you haven’t connected yet, start with connect() or start_openocd().


Manage the lifecycle of the OpenOCD connection.

Connect to a running OpenOCD instance over its TCL RPC interface.

ParameterTypeDefaultDescription
hoststr"localhost"OpenOCD TCL RPC hostname
portint6666OpenOCD TCL RPC port

Returns: ConnectionStatusconnected, host, port, message

Use this when OpenOCD is already running (started manually or by another process). For auto-starting OpenOCD, use start_openocd instead.

If already connected, returns immediately with a message to disconnect first.

# Connect to OpenOCD on the default port
connect()
# Connect to a remote OpenOCD instance
connect(host="192.168.1.50", port=6666)

Spawn an OpenOCD subprocess with the given config file, then connect to it.

ParameterTypeDefaultDescription
configstr(required)Path to OpenOCD .cfg file
tcl_portint6666TCL RPC port for the new OpenOCD process
extra_argsstr""Additional CLI arguments (space-separated)

Returns: ConnectionStatusconnected, host, port, process_pid, message

The spawned process is tracked internally. When you call disconnect(), the process is stopped automatically.

# Start OpenOCD with SWD config
start_openocd(config="/path/to/openocd-daplink-swd.cfg")
# Start on a non-default port with extra flags
start_openocd(
config="/path/to/openocd-daplink-swd.cfg",
tcl_port=6667,
extra_args="-d2"
)

See OpenOCD Configurations for the shipped config files.

Close the OpenOCD connection and stop the managed process if one was started via start_openocd.

ParameterTypeDefaultDescription
(none)

Returns: ConnectionStatusconnected=false, message

Safe to call even when not connected.

disconnect()

Run a comprehensive health check on the probe, target, and server capabilities. Call this first after connecting to understand what is working and what is not.

ParameterTypeDefaultDescription
(none)

Returns: DiagnosticsResultconnected, checks (list of DiagnosticCheck), summary

Runs 9 diagnostic checks in sequence:

CheckWhat it tests
connectionWhether the OpenOCD session is active
openocd_versionOpenOCD version string
adapter_speedDebug adapter clock speed
transportActive transport (SWD or JTAG)
targetWhether any targets are defined
target_stateWhether the target state can be read
memory_accessRead the CPUID register at 0xE000ED00
flashFlash bank enumeration
svdWhether an SVD file is loaded

Each check returns a name, pass/fail boolean, and a detail string with guidance on what to try if it failed.

probe_diagnostics()
# Returns something like:
# {
# "connected": true,
# "checks": [
# {"name": "connection", "ok": true, "detail": "Connected to OpenOCD"},
# {"name": "openocd_version", "ok": true, "detail": "Open On-Chip Debugger 0.12.0"},
# ...
# ],
# "summary": "connected, target found, 8/9 checks passed"
# }

Query the current execution state of the target.

ParameterTypeDefaultDescription
(none)

Returns: TargetStateResultstate, pc (hex string or null), halted (bool), name

The state field can be one of: halted, running, reset, debug-running.

The pc (program counter) is only available when the target is halted.

target_state()
# {"state": "halted", "pc": "0x08001234", "halted": true, "name": "stm32f1x.cpu"}

Control target execution: halt, resume, single-step, or reset.

ParameterTypeDefaultDescription
actionstr(required)One of: "halt", "resume", "step", "reset_halt", "reset_run"
addressstr | NoneNoneHex address for resume/step (e.g. "0x08001000")

Returns: TargetControlResultaction, state, pc, halted

Actions:

ActionEffect
haltStop the CPU at its current instruction
resumeContinue execution (optionally from a specific address)
stepExecute one instruction, then halt again
reset_haltReset the chip and halt at the reset vector
reset_runReset the chip and let it run freely

The address parameter is only used with resume and step. If provided, it must be 2-byte aligned (ARM Thumb requirement). Odd addresses are rejected.

# Stop the CPU
target_control(action="halt")
# Single-step one instruction
target_control(action="step")
# Resume execution from a specific address
target_control(action="resume", address="0x08001000")
# Reset and halt at the reset vector
target_control(action="reset_halt")

Read memory from the target at a given address.

ParameterTypeDefaultDescription
addressstr(required)Start address in hex (e.g. "0x08000000")
countint1Number of elements to read (1—4096)
widthint32Element width in bits: 8, 16, 32, or 64

Returns: MemoryDumpaddress, width, count, values (list of hex strings), hexdump (formatted text)

The hexdump field provides a formatted view with hex bytes and ASCII representation, similar to xxd:

08000000: 00 50 00 20 A1 01 00 08 AB 01 00 08 AD 01 00 08 |.P. ............|

Unaligned reads produce a warning (they can cause bus faults on Cortex-M0 targets).

# Read 16 words from flash
read_memory(address="0x08000000", count=16, width=32)
# Read a byte array from SRAM
read_memory(address="0x20000100", count=64, width=8)
# Read a single 32-bit word (the default)
read_memory(address="0xE000ED00")

Write values to target memory. By default, writes are restricted to SRAM regions.

ParameterTypeDefaultDescription
addressstr(required)Start address in hex (e.g. "0x20000000")
valueslist[str](required)Hex values to write (e.g. ["0xDEADBEEF", "0x12345678"])
widthint32Element width in bits: 8, 16, or 32

Returns: MemoryWriteResultaddress, width, count, written

Safety checks:

  • Address must fall within configured safe write ranges (default: 0x200000000x20100000)
  • Writes must be aligned to the element width (e.g., 32-bit writes to 4-byte aligned addresses)
  • Each value must fit within the specified width

See Environment Variables to configure MCJTAG_SAFE_WRITE_RANGES, and Memory Layout for why SRAM is the default safe zone.

# Write two 32-bit words to SRAM
write_memory(address="0x20000000", values=["0xDEADBEEF", "0x12345678"])
# Write bytes
write_memory(address="0x20000100", values=["0x41", "0x42", "0x43"], width=8)

Search for a byte pattern within a memory range.

ParameterTypeDefaultDescription
patternstr(required)Hex byte pattern (e.g. "DEADBEEF")
startstr(required)Start address, inclusive (hex)
endstr(required)End address, exclusive (hex)

Returns: MemorySearchResultpattern, start, end, matches (list of hex addresses), count

The search reads memory in 4 KB chunks and reports progress via MCP progress notifications. The maximum search range is 1 MB by default, configurable via the MCJTAG_MAX_SEARCH_RANGE environment variable.

# Search for 0xDEADBEEF in SRAM
search_memory(pattern="DEADBEEF", start="0x20000000", end="0x20010000")
# Search for an ASCII string ("Hello")
search_memory(pattern="48656C6C6F", start="0x20000000", end="0x20008000")

Read CPU registers from the halted target.

ParameterTypeDefaultDescription
nameslist[str] | NoneNoneRegister names to read, or None for all

Returns: RegisterSetregisters (dict of name to hex value), count

The target must be halted for register access. Common ARM Cortex-M registers:

RegisterPurpose
r0r12General-purpose registers
sp (r13)Stack pointer
lr (r14)Link register (return address)
pc (r15)Program counter
xPSRProgram status register
mspMain stack pointer
pspProcess stack pointer
# Read all registers
read_registers()
# Read specific registers
read_registers(names=["pc", "sp", "lr", "xPSR"])

Write a value to a single CPU register. The target must be halted.

ParameterTypeDefaultDescription
namestr(required)Register name (e.g. "pc", "r0", "sp")
valuestr(required)Hex value to write (e.g. "0x08001000")

Returns: RegisterWriteResultname, value, written

# Set the program counter
write_register(name="pc", value="0x08001000")
# Set r0 to zero
write_register(name="r0", value="0x00000000")

List all flash banks on the target with metadata.

ParameterTypeDefaultDescription
(none)

Returns: FlashBanksResultbanks (list of FlashBankInfo), count

Each FlashBankInfo contains:

FieldTypeDescription
indexintBank index (0-based)
namestrFlash driver name
basestrBase address (hex)
sizeintTotal size in bytes
sectorsintNumber of sectors (-1 if unavailable)
targetstrAssociated target name
flash_info()
# {
# "banks": [{
# "index": 0,
# "name": "stm32f1x",
# "base": "0x08000000",
# "size": 65536,
# "sectors": 64,
# "target": "stm32f1x.cpu"
# }],
# "count": 1
# }

Program a firmware image into flash memory.

ParameterTypeDefaultDescription
pathstr(required)Path to the firmware image file
eraseboolTrueErase affected sectors before writing
verifyboolTrueVerify flash contents after writing

Returns: FlashProgramResultpath, erased, verified, success, message

This is a destructive operation — it overwrites flash contents.

Validation checks before programming:

  • File must exist
  • Extension must be one of: .bin, .hex, .elf, .ihex, .srec, .s19
  • File must not be empty
  • File must not exceed 16 MB

Progress is reported in phases: erase, write, verify.

# Program with default erase + verify
flash_program(path="/home/user/firmware.bin")
# Program without erasing (overwrite in-place)
flash_program(path="/home/user/firmware.hex", erase=False)

Enumerate all TAPs (Test Access Ports) on the JTAG scan chain.

ParameterTypeDefaultDescription
(none)

Returns: JTAGChaintaps (list of TAPResult), count

Each TAPResult contains:

FieldTypeDescription
namestrTAP name (e.g. "stm32f1x.cpu")
idcodestrJTAG IDCODE (hex, e.g. "0x1ba01477")
ir_lengthintInstruction register length in bits
enabledboolWhether the TAP is enabled

The IDCODE uniquely identifies the silicon. Bits 1—11 contain the manufacturer ID (JEDEC), and bits 12—27 contain the part number.

jtag_scan()
# {
# "taps": [{
# "name": "stm32f1x.cpu",
# "idcode": "0x1ba01477",
# "ir_length": 4,
# "enabled": true
# }],
# "count": 1
# }

Perform a raw JTAG IR scan or DR scan operation.

ParameterTypeDefaultDescription
tapstr(required)TAP name (e.g. "stm32f1x.cpu")
operationstr(required)"irscan" or "drscan"
valuestr(required)Value to shift in (hex, e.g. "0x0E")
bitsint | NoneNoneNumber of bits (required for drscan)

Returns: JTAGShiftResultoperation, tap, value_in, value_out, bits

This is a low-level tool for protocols not covered by the higher-level tools. Most users should use the target, memory, and register tools instead.

# Read the IDCODE register (IR = 0x0E on ARM DAP)
jtag_shift(tap="stm32f1x.cpu", operation="irscan", value="0x0E")
# Scan a 32-bit data register
jtag_shift(tap="stm32f1x.cpu", operation="drscan", value="0x00000000", bits=32)

Load an SVD file and/or inspect peripherals and registers with decoded bitfields.

ParameterTypeDefaultDescription
svd_pathstr | NoneNonePath to .svd file (loads it if provided)
peripheralstr | NoneNonePeripheral name to inspect (e.g. "GPIOA")
registerstr | NoneNoneSpecific register (e.g. "CR1"). Requires peripheral.

Returns: varies by arguments:

ArgumentsReturn TypeContent
svd_path onlySVDListResultList of all peripherals
neither (SVD already loaded)SVDListResultList of all peripherals
peripheralSVDPeripheralResultAll registers for that peripheral, decoded
peripheral + registerSVDPeripheralResultSingle register, decoded

SVD (System View Description) files describe a chip’s memory-mapped peripheral registers and their bitfields. They are provided by silicon vendors and available from the CMSIS-SVD repository.

Validation checks:

  • SVD file must exist, have a .svd extension, and be under 50 MB

Progress reporting: When decoding a full peripheral, progress is reported per register.

# Load an SVD file and list peripherals
svd_inspect(svd_path="/path/to/STM32F103.svd")
# List peripherals (SVD already loaded)
svd_inspect()
# Decode all GPIOA registers
svd_inspect(peripheral="GPIOA")
# Decode a single register
svd_inspect(peripheral="USART1", register="CR1")

Example decoded register output:

{
"peripheral": "GPIOA",
"registers": {
"CRL": {
"name": "CRL",
"address": "0x40010800",
"raw_value": "0x44444444",
"fields": [
{"name": "MODE0", "value": 0, "width": 2, "offset": 0, "description": "Port mode bits"},
{"name": "CNF0", "value": 1, "width": 2, "offset": 2, "description": "Port configuration bits"}
]
}
}
}

Send a raw OpenOCD TCL command and return the response.

ParameterTypeDefaultDescription
commandstr(required)OpenOCD TCL command string

Returns: RawCommandResultcommand, response

This is an escape hatch for OpenOCD functionality not covered by the typed tools. A deny-list blocks destructive commands by default:

Blocked PatternUse Instead
flash erase, flash write, flash protect, flash fillflash_program()
mww, mwh, mwb, write_memorywrite_memory()
resettarget_control(action="reset_halt") or target_control(action="reset_run")
shutdown, exitdisconnect()
regread_registers() / write_register()
eval, subst, uplevelBlocked to prevent deny-list bypass via TCL metaprogramming

Set MCJTAG_ALLOW_RAW=true to disable the deny-list entirely. See Environment Variables for details.

# Query OpenOCD version
raw_command(command="version")
# Check adapter speed
raw_command(command="adapter speed")
# List configured targets
raw_command(command="targets")
# Read a chip-specific register via OpenOCD commands
raw_command(command="flash banks")