---
title: Tools
description: Complete reference for all 17 mcjtag MCP 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()`.

---

## Connection Tools

Manage the lifecycle of the OpenOCD connection.

### connect

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `host` | `str` | `"localhost"` | OpenOCD TCL RPC hostname |
| `port` | `int` | `6666` | OpenOCD TCL RPC port |

**Returns:** `ConnectionStatus` -- `connected`, `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.

```python
# Connect to OpenOCD on the default port
connect()

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

### start_openocd

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `config` | `str` | *(required)* | Path to OpenOCD `.cfg` file |
| `tcl_port` | `int` | `6666` | TCL RPC port for the new OpenOCD process |
| `extra_args` | `str` | `""` | Additional CLI arguments (space-separated) |

**Returns:** `ConnectionStatus` -- `connected`, `host`, `port`, `process_pid`, `message`

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

```python
# 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](/reference/openocd-configs/) for the shipped config files.

### disconnect

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| *(none)* | | | |

**Returns:** `ConnectionStatus` -- `connected=false`, `message`

Safe to call even when not connected.

```python
disconnect()
```

---

## Diagnostics

### probe_diagnostics

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.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| *(none)* | | | |

**Returns:** `DiagnosticsResult` -- `connected`, `checks` (list of `DiagnosticCheck`), `summary`

Runs 9 diagnostic checks in sequence:

| Check | What it tests |
|-------|---------------|
| `connection` | Whether the OpenOCD session is active |
| `openocd_version` | OpenOCD version string |
| `adapter_speed` | Debug adapter clock speed |
| `transport` | Active transport (SWD or JTAG) |
| `target` | Whether any targets are defined |
| `target_state` | Whether the target state can be read |
| `memory_access` | Read the CPUID register at `0xE000ED00` |
| `flash` | Flash bank enumeration |
| `svd` | Whether 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.

```python
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"
# }
```

---

## Target Control

### target_state

Query the current execution state of the target.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| *(none)* | | | |

**Returns:** `TargetStateResult` -- `state`, `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.

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

### target_control

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `action` | `str` | *(required)* | One of: `"halt"`, `"resume"`, `"step"`, `"reset_halt"`, `"reset_run"` |
| `address` | `str \| None` | `None` | Hex address for resume/step (e.g. `"0x08001000"`) |

**Returns:** `TargetControlResult` -- `action`, `state`, `pc`, `halted`

**Actions:**

| Action | Effect |
|--------|--------|
| `halt` | Stop the CPU at its current instruction |
| `resume` | Continue execution (optionally from a specific address) |
| `step` | Execute one instruction, then halt again |
| `reset_halt` | Reset the chip and halt at the reset vector |
| `reset_run` | Reset 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.

```python
# 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")
```

---

## Memory

### read_memory

Read memory from the target at a given address.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `address` | `str` | *(required)* | Start address in hex (e.g. `"0x08000000"`) |
| `count` | `int` | `1` | Number of elements to read (1--4096) |
| `width` | `int` | `32` | Element width in bits: `8`, `16`, `32`, or `64` |

**Returns:** `MemoryDump` -- `address`, `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).

```python
# 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_memory

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `address` | `str` | *(required)* | Start address in hex (e.g. `"0x20000000"`) |
| `values` | `list[str]` | *(required)* | Hex values to write (e.g. `["0xDEADBEEF", "0x12345678"]`) |
| `width` | `int` | `32` | Element width in bits: `8`, `16`, or `32` |

**Returns:** `MemoryWriteResult` -- `address`, `width`, `count`, `written`

**Safety checks:**

- Address must fall within configured safe write ranges (default: `0x20000000`--`0x20100000`)
- 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](/reference/environment/) to configure `MCJTAG_SAFE_WRITE_RANGES`, and [Memory Layout](/concepts/memory-layout/) for why SRAM is the default safe zone.

```python
# 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_memory

Search for a byte pattern within a memory range.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `pattern` | `str` | *(required)* | Hex byte pattern (e.g. `"DEADBEEF"`) |
| `start` | `str` | *(required)* | Start address, inclusive (hex) |
| `end` | `str` | *(required)* | End address, exclusive (hex) |

**Returns:** `MemorySearchResult` -- `pattern`, `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.

```python
# 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")
```

---

## Registers

### read_registers

Read CPU registers from the halted target.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `names` | `list[str] \| None` | `None` | Register names to read, or `None` for all |

**Returns:** `RegisterSet` -- `registers` (dict of name to hex value), `count`

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

| Register | Purpose |
|----------|---------|
| `r0`--`r12` | General-purpose registers |
| `sp` (r13) | Stack pointer |
| `lr` (r14) | Link register (return address) |
| `pc` (r15) | Program counter |
| `xPSR` | Program status register |
| `msp` | Main stack pointer |
| `psp` | Process stack pointer |

```python
# Read all registers
read_registers()

# Read specific registers
read_registers(names=["pc", "sp", "lr", "xPSR"])
```

### write_register

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | *(required)* | Register name (e.g. `"pc"`, `"r0"`, `"sp"`) |
| `value` | `str` | *(required)* | Hex value to write (e.g. `"0x08001000"`) |

**Returns:** `RegisterWriteResult` -- `name`, `value`, `written`

```python
# Set the program counter
write_register(name="pc", value="0x08001000")

# Set r0 to zero
write_register(name="r0", value="0x00000000")
```

---

## Flash

### flash_info

List all flash banks on the target with metadata.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| *(none)* | | | |

**Returns:** `FlashBanksResult` -- `banks` (list of `FlashBankInfo`), `count`

Each `FlashBankInfo` contains:

| Field | Type | Description |
|-------|------|-------------|
| `index` | `int` | Bank index (0-based) |
| `name` | `str` | Flash driver name |
| `base` | `str` | Base address (hex) |
| `size` | `int` | Total size in bytes |
| `sectors` | `int` | Number of sectors (-1 if unavailable) |
| `target` | `str` | Associated target name |

```python
flash_info()
# {
#   "banks": [{
#     "index": 0,
#     "name": "stm32f1x",
#     "base": "0x08000000",
#     "size": 65536,
#     "sectors": 64,
#     "target": "stm32f1x.cpu"
#   }],
#   "count": 1
# }
```

### flash_program

Program a firmware image into flash memory.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `path` | `str` | *(required)* | Path to the firmware image file |
| `erase` | `bool` | `True` | Erase affected sectors before writing |
| `verify` | `bool` | `True` | Verify flash contents after writing |

**Returns:** `FlashProgramResult` -- `path`, `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.

```python
# 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)
```

---

## JTAG

### jtag_scan

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| *(none)* | | | |

**Returns:** `JTAGChain` -- `taps` (list of `TAPResult`), `count`

Each `TAPResult` contains:

| Field | Type | Description |
|-------|------|-------------|
| `name` | `str` | TAP name (e.g. `"stm32f1x.cpu"`) |
| `idcode` | `str` | JTAG IDCODE (hex, e.g. `"0x1ba01477"`) |
| `ir_length` | `int` | Instruction register length in bits |
| `enabled` | `bool` | Whether 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.

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

### jtag_shift

Perform a raw JTAG IR scan or DR scan operation.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `tap` | `str` | *(required)* | TAP name (e.g. `"stm32f1x.cpu"`) |
| `operation` | `str` | *(required)* | `"irscan"` or `"drscan"` |
| `value` | `str` | *(required)* | Value to shift in (hex, e.g. `"0x0E"`) |
| `bits` | `int \| None` | `None` | Number of bits (required for `drscan`) |

**Returns:** `JTAGShiftResult` -- `operation`, `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.

```python
# 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)
```

---

## SVD

### svd_inspect

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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `svd_path` | `str \| None` | `None` | Path to `.svd` file (loads it if provided) |
| `peripheral` | `str \| None` | `None` | Peripheral name to inspect (e.g. `"GPIOA"`) |
| `register` | `str \| None` | `None` | Specific register (e.g. `"CR1"`). Requires `peripheral`. |

**Returns:** varies by arguments:

| Arguments | Return Type | Content |
|-----------|-------------|---------|
| `svd_path` only | `SVDListResult` | List of all peripherals |
| neither (SVD already loaded) | `SVDListResult` | List of all peripherals |
| `peripheral` | `SVDPeripheralResult` | All registers for that peripheral, decoded |
| `peripheral` + `register` | `SVDPeripheralResult` | Single 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](https://github.com/cmsis-svd/cmsis-svd-data).

**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.

```python
# 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:

```json
{
  "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"}
      ]
    }
  }
}
```

---

## Raw Command

### raw_command

Send a raw OpenOCD TCL command and return the response.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `command` | `str` | *(required)* | OpenOCD TCL command string |

**Returns:** `RawCommandResult` -- `command`, `response`

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

| Blocked Pattern | Use Instead |
|----------------|-------------|
| `flash erase`, `flash write`, `flash protect`, `flash fill` | `flash_program()` |
| `mww`, `mwh`, `mwb`, `write_memory` | `write_memory()` |
| `reset` | `target_control(action="reset_halt")` or `target_control(action="reset_run")` |
| `shutdown`, `exit` | `disconnect()` |
| `reg` | `read_registers()` / `write_register()` |
| `eval`, `subst`, `uplevel` | Blocked to prevent deny-list bypass via TCL metaprogramming |

Set `MCJTAG_ALLOW_RAW=true` to disable the deny-list entirely. See [Environment Variables](/reference/environment/) for details.

```python
# 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")
```
