Skip to main content
Glama
dbgprobe-mcp-server

dbgprobe-mcp-server

by es617

Debug Probe MCP Server

MCP License: MIT Python Debug Probe

A stateful debug probe Model Context Protocol (MCP) server for developer tooling and AI agents. Works out of the box with Claude Code, VS Code with Copilot, and any MCP-compatible runtime. Communicates over stdio and drives on-chip debug probes (J-Link first, OpenOCD and pyOCD planned) to flash, debug, and inspect embedded targets.

Example: Let Claude Code list attached J-Link probes, connect to your nRF52840, flash a new firmware, read memory, and reset the target — all conversationally.

Demo

Video walkthrough — connecting to a J-Link probe, flashing firmware, loading ELF and SVD for symbol-aware debugging, RTT logging, and breakpoints.


Why this exists

If you've ever typed J-Link Commander commands by hand, copy-pasted memory addresses between a datasheet and a terminal, re-flashed the same firmware 20 times during a debug session, and juggled multiple tool windows — this is for you.

You have a microcontroller on a debug probe. You want an AI agent to interact with it — connect, flash firmware, read/write memory, reset, halt, resume. This server makes that possible.

It gives any MCP-compatible agent a full set of debug probe tools. The agent calls these tools, gets structured JSON back, and reasons about what to do next — without you manually driving JLinkExe for every operation.

What agents can do with it:

  • Flash and iterate — build firmware, flash it, reset, check behavior — all in one conversation

  • Inspect memory — read peripheral registers, check RAM contents, verify flash writes

  • Debug interactively — halt, step, set breakpoints, inspect state, resume

  • Automate test flows — flash → reset → read output → validate

  • Multi-probe setups — connect to multiple probes simultaneously, each with its own session


Related MCP server: io.github.microhenrio/openocd-mcp

Who is this for?

  • Embedded engineers — faster iteration: flash, debug, inspect memory conversationally

  • Hobbyists and makers — interact with microcontrollers without learning JLinkExe command syntax

  • QA and test engineers — automated flash-and-test sequences across multiple boards

  • Researchers — systematic exploration of embedded systems, register inspection


Quickstart (Claude Code)

pip install dbgprobe-mcp-server

# Register the MCP server with Claude Code
claude mcp add dbgprobe -- dbgprobe_mcp

# Or with explicit J-Link path
claude mcp add dbgprobe \
  -e DBGPROBE_JLINK_PATH=/Applications/SEGGER/JLink/JLinkExe \
  -- dbgprobe_mcp

Then in Claude Code, try:

"List attached debug probes, connect to the J-Link, and read 16 bytes from address 0x20000000."


Supported backends

Backend

Status

Probe hardware

J-Link

Working (v0)

SEGGER J-Link (EDU, EDU Mini, PLUS, PRO, etc.)

OpenOCD

Planned

ST-Link, CMSIS-DAP, and many others

pyOCD

Planned

CMSIS-DAP, ST-Link, J-Link (via pyOCD)

The server is backend-agnostic — tool names (dbgprobe.*) stay the same regardless of which probe you use.

Install the SEGGER J-Link Software Pack. The server auto-detects JLinkExe on PATH or in common install locations:

  • macOS: /Applications/SEGGER/JLink/

  • Linux: /opt/SEGGER/JLink/, /usr/bin/

  • Windows: C:\Program Files\SEGGER\JLink\

Or set DBGPROBE_JLINK_PATH to point to the executable directly.

This project is not affiliated with or sponsored by SEGGER. J-Link is used because it's widely available and well-supported.


Tools

Category

Tools

Probe

dbgprobe.probes.list, dbgprobe.connect, dbgprobe.erase, dbgprobe.disconnect, dbgprobe.reset, dbgprobe.halt, dbgprobe.go, dbgprobe.step, dbgprobe.status, dbgprobe.flash, dbgprobe.mem.read, dbgprobe.mem.write, dbgprobe.breakpoint.set, dbgprobe.breakpoint.clear, dbgprobe.breakpoint.list

Introspection

dbgprobe.connections.list

ELF

dbgprobe.elf.attach, dbgprobe.elf.info, dbgprobe.elf.lookup, dbgprobe.elf.symbols

SVD

dbgprobe.svd.attach, dbgprobe.svd.info, dbgprobe.svd.read, dbgprobe.svd.write, dbgprobe.svd.set_field, dbgprobe.svd.update_fields, dbgprobe.svd.list_peripherals, dbgprobe.svd.list_registers, dbgprobe.svd.list_fields, dbgprobe.svd.describe

RTT

dbgprobe.rtt.start, dbgprobe.rtt.stop, dbgprobe.rtt.read, dbgprobe.rtt.write, dbgprobe.rtt.status

Plugins

dbgprobe.plugin.list, dbgprobe.plugin.template, dbgprobe.plugin.load, dbgprobe.plugin.reload

Tracing

dbgprobe.trace.status, dbgprobe.trace.tail

See docs/tools.md for full schemas and examples.


Install (development)

# Editable install from repo root
pip install -e ".[test]"

# Or with uv
uv pip install -e ".[test]"

MCP is a protocol — this server works with any MCP-compatible client. Below are setup instructions for the most common ones.

Add to Claude Code

# Standard setup
claude mcp add dbgprobe -- dbgprobe_mcp

# With default target device
claude mcp add dbgprobe \
  -e DBGPROBE_JLINK_DEVICE=nRF52840_xxAA \
  -- dbgprobe_mcp

# Debug logging
claude mcp add dbgprobe -e DBGPROBE_MCP_LOG_LEVEL=DEBUG -- dbgprobe_mcp

Add to VS Code / Copilot

Add to your project's .vscode/mcp.json (or create it):

{
  "servers": {
    "dbgprobe": {
      "type": "stdio",
      "command": "dbgprobe_mcp",
      "args": [],
      "env": {
        "DBGPROBE_JLINK_DEVICE": "nRF52840_xxAA"
      }
    }
  }
}

Adjust env to match your target — set DBGPROBE_JLINK_DEVICE to your chip, or remove it to specify the device at connect time.

Add to Cursor

Add to your project's .cursor/mcp.json (or create it). Cursor does not support dots in tool names, so DBGPROBE_MCP_TOOL_SEPARATOR must be set to _:

{
  "mcpServers": {
    "dbgprobe": {
      "command": "dbgprobe_mcp",
      "args": [],
      "env": {
        "DBGPROBE_JLINK_DEVICE": "nRF52840_xxAA",
        "DBGPROBE_MCP_TOOL_SEPARATOR": "_"
      }
    }
  }
}

Environment variables

Server

Variable

Default

Description

DBGPROBE_BACKEND

jlink

Debug probe backend. Future: openocd, pyocd.

DBGPROBE_MCP_LOG_LEVEL

WARNING

Python log level (DEBUG, INFO, WARNING, ERROR). Logs go to stderr.

DBGPROBE_MCP_TRACE

enabled

JSONL tracing of every tool call. Set to 0, false, or no to disable.

DBGPROBE_MCP_TRACE_PAYLOADS

disabled

Include memory data payloads in traced args (stripped by default).

DBGPROBE_MCP_TRACE_MAX_BYTES

16384

Max payload chars before truncation (only when TRACE_PAYLOADS is on).

DBGPROBE_MCP_TOOL_SEPARATOR

.

Character used to separate tool name segments. Set to _ for MCP clients that reject dots in tool names (e.g. Cursor).

DBGPROBE_MCP_PLUGINS

disabled

Plugin policy: all or comma-separated plugin names (e.g. nrf52,stm32).

Variable

Default

Description

DBGPROBE_JLINK_PATH

auto-detect

Explicit path to JLinkExe (or JLink.exe on Windows).

DBGPROBE_JLINK_GDBSERVER_PATH

auto-detect

Explicit path to JLinkGDBServerCLExe.

DBGPROBE_JLINK_DEVICE

(none)

Default target device string (e.g. nRF52840_xxAA). Can be overridden per-session.

DBGPROBE_INTERFACE

SWD

Debug interface: SWD or JTAG.

DBGPROBE_SPEED_KHZ

4000

Interface clock speed in kHz.

DBGPROBE_GDB_TRACE

disabled

Log all GDB RSP packets to file. Set to 1, true, or yes to enable.

DBGPROBE_GDB_TRACE_FILE

/tmp/gdb_trace.log

Path for GDB RSP trace log (only when GDB_TRACE is on).


ELF Support

Attach an ELF file to a session to enable symbol-aware debugging:

  • Symbol lookup — resolve function names to addresses and vice versa

  • Breakpoints by name — breakpoint.set(symbol="main") instead of raw addresses

  • Auto-enriched responses — status, step, and halt include symbol + symbol_offset when an ELF is attached

  • Flash integration — flashing an .elf auto-attaches it; flashing .hex/.bin auto-reloads a previously attached ELF; sibling .elf files are suggested via hints

> "Attach the ELF, set a breakpoint on main, run, and show me where it halted."

The agent calls elf.attach, breakpoint.set(symbol="main"), go, then status — and gets back "halted at main+0" instead of a raw hex address.


SVD Support

Attach an SVD (System View Description) file to a session to enable register-level peripheral access:

  • Named register reads — svd.read("GPIO.OUT") returns the raw value and all decoded fields with enum names

  • Field-level reads — svd.read("GPIO.PIN_CNF[3].PULL") returns the field value and enum name ("PullUp")

  • Safe field writes — svd.set_field("GPIO.PIN_CNF[3].PULL", "PullUp") does read-modify-write

  • Batch field updates — svd.update_fields("GPIO.PIN_CNF[3]", {"DIR": "Output", "PULL": "PullUp"}) — one read, one write

  • Raw register writes — svd.write("GPIO.OUT", 0x01) — full register, no RMW

  • Discovery — list peripherals, registers, fields; describe with enums

  • Auto-decode on mem.read — when an SVD is attached and mem.read hits a known register address, the response includes decoded fields

> "Attach the SVD, read GPIO.PIN_CNF[3], and set PULL to PullUp."

The agent calls svd.attach, svd.read("GPIO.PIN_CNF[3]"), then svd.set_field("GPIO.PIN_CNF[3].PULL", "PullUp") — and gets back decoded field values instead of raw hex.


RTT (Real-Time Transfer)

Start, stop, read, and write to SEGGER RTT channels. The agent can stream target log output and send data to the device — useful for debugging firmware that prints over RTT instead of UART.

> "Start RTT and show me the output."

For repetitive flows — read device ID, run a self-test sequence, validate calibration — you can package them as plugins: Python modules that expose custom tools the agent can call directly. Enable with DBGPROBE_MCP_PLUGINS=all. See the nrf52_info example.


Tracing

Every tool call is traced to .dbgprobe_mcp/traces/trace.jsonl and an in-memory ring buffer (last 2000 events). Tracing is on by default — set DBGPROBE_MCP_TRACE=0 to disable.

Use dbgprobe.trace.status and dbgprobe.trace.tail to inspect the trace without reading the file directly.


Try without an agent

You can test the server interactively using the MCP Inspector:

npx @modelcontextprotocol/inspector python -m dbgprobe_mcp_server

Roadmap / TODO

  • OpenOCD backend — support ST-Link, CMSIS-DAP, and other probes via OpenOCD subprocess

  • pyOCD backend — native Python probe access via pyOCD library

  • Multi-core support — target specific cores on multi-core SoCs

  • Cortex-A/R support — ARM-mode breakpoints (kind=4); currently Thumb-only (Cortex-M)


Known limitations

  • Single-client only. The server handles one MCP session at a time (stdio transport).

  • RTT channel 0 only. RTT support is limited to channel 0 (terminal). Multi-channel RTT is a future enhancement.

  • Flash clears breakpoints. Flashing new firmware invalidates breakpoints (the code at those addresses may have changed). The session stays alive but breakpoints are cleared.

  • Cortex-M only. Breakpoints use Thumb-mode (kind=2). Cortex-A/R targets (ARM-mode, kind=4) are not yet supported.

  • Instruction-level step only. dbgprobe.step single-steps one CPU instruction. Source-level stepping (step into/over/out) is not supported.


Safety

This server connects an AI agent to real debug hardware. That's the point — and it means the stakes are higher than pure-software tools.

Plugins execute arbitrary code. When plugins are enabled, the agent can create and run Python code on your machine with full server privileges. Review agent-generated plugins before loading them.

Writes affect real hardware. A bad memory write or flash operation can brick a device, wipe calibration data, or trigger unintended behavior. Consider what the agent can reach.

Use tool approval deliberately. When your MCP client prompts you to approve a tool call, consider whether you want to allow it once or always.

This software is provided as-is under the MIT License. You are responsible for what the agent does with your hardware.


License

This project is licensed under the MIT License — see LICENSE for details.

Available Tools

41 tools
dbgprobe.breakpoint.clearC

Clear a breakpoint at a target address.

Inputs: address (required), session_id (required).

dbgprobe.breakpoint.listB

List all active breakpoints for a session.

Inputs: session_id (required).

dbgprobe.breakpoint.setA

Set a breakpoint at a target address or symbol name. Software breakpoints (default) are handled by the debug probe and work on both flash and RAM. Hardware breakpoints use the CPU's FPB and are limited in number (typically 4-6). If 'symbol' is provided and an ELF is attached, resolves the symbol to an address.

Inputs: session_id (required), symbol, address, bp_type.

dbgprobe.connectA

Establish a debug probe session. Returns a session_id and the resolved configuration (backend, executable paths, defaults applied). The target is halted after connecting — this is inherent to the debug probe connection. Use dbgprobe.go to resume execution if needed. For symbol-aware debugging, attach an ELF file with dbgprobe.elf.attach after connecting — this enables breakpoints by function name and address-to-symbol resolution in status/step/halt responses. For register-level peripheral access, attach an SVD file with dbgprobe.svd.attach.

Inputs: device, backend, probe_id, interface, +1 more.

dbgprobe.connections.listA

List all open probe sessions with their status, backend, device, and config. Useful for recovering session IDs after context loss.

Inputs: no named parameters.

dbgprobe.disconnectC

Close a debug probe session and release resources.

Inputs: session_id (required).

dbgprobe.elf.attachA

Parse an ELF file and attach it to a session. Enables symbol-based breakpoints (by function name), address-to-symbol resolution in status/step/halt responses, and symbol search. Re-attaching replaces the previous ELF.

Inputs: path (required), session_id (required).

dbgprobe.elf.infoA

Get ELF metadata for a session: file path, symbol count, entry point, sections. Returns null if no ELF is attached.

Inputs: session_id (required).

dbgprobe.elf.lookupA

Bidirectional symbol lookup. Provide 'symbol' for name-to-address, or 'address' for address-to-name+offset. Exactly one is required.

Inputs: session_id (required), symbol, address.

dbgprobe.elf.symbolsB

Search or list ELF symbols. Optional substring filter, optional type filter (FUNC, OBJECT, NOTYPE, etc.), default limit 50.

Inputs: session_id (required), type, limit, filter.

dbgprobe.eraseA

Erase target flash. With no address params: full chip erase (unlocks secured/read-protected devices like Nordic APPROTECT). With start_addr and end_addr: erase only that range. If session_id is provided, erases through the active GDB session (preferred — no USB contention). Without session_id, uses JLinkExe directly (for session-less erase, e.g. unlocking a secured device before connect).

Inputs: device, backend, end_addr, probe_id, +4 more.

dbgprobe.flashA

Program a firmware image to the target. Supports .hex, .elf (address auto-detected) and raw .bin (requires explicit addr). Optionally verify and reset after flashing. If session_id is provided, tears down GDB, flashes, and restarts (preferred). Without session_id, uses JLinkExe directly (session-less, no debug session needed).

Inputs: path (required), addr, device, verify, +6 more.

dbgprobe.goC

Resume target execution (run/go).

Inputs: session_id (required).

dbgprobe.haltC

Halt the target CPU.

Inputs: session_id (required).

dbgprobe.mem.readA

Read memory from the target. Returns data in the requested format: 'hex' (hex string), 'base64', or 'u32' (array of 32-bit words, little-endian). If an SVD file is attached and the read is exactly one register wide, the response includes decoded SVD field names and values.

Inputs: length (required), address (required), session_id (required), format.

dbgprobe.mem.writeB

Write data to target memory. Provide data in one of: 'hex' (hex string), 'base64', or 'u32' (array of 32-bit words, little-endian).

Inputs: address (required), session_id (required), data, format, +1 more.

dbgprobe.plugin.listA

List loaded plugins with their tool names and metadata. Each plugin may include a 'meta' dict with matching hints like device_name_contains or description — use these to determine which plugin fits the connected device. Also returns whether plugins are enabled and the current policy. Plugins require DBGPROBE_MCP_PLUGINS env var — set to 'all' for all or 'name1,name2' to allow specific plugins. If disabled, tell the user to set this variable when adding the MCP server.

Inputs: no named parameters.

dbgprobe.plugin.loadB

Load a new plugin from a file or directory path. Requires DBGPROBE_MCP_PLUGINS env var to be set.

Inputs: path (required).

dbgprobe.plugin.reloadA

Hot-reload a plugin by name. Re-imports the module and refreshes tools. Requires DBGPROBE_MCP_PLUGINS env var to be set.

Inputs: name (required).

dbgprobe.plugin.templateA

Return a Python plugin template. Use this when creating a new plugin. Optionally pre-fill with a device name. Save the result to .dbgprobe_mcp/plugins/.py, fill in the tools and handlers, then load with dbgprobe.plugin.load.

Inputs: device_name.

dbgprobe.probes.listA

List attached debug probes. Returns vendor/backend-specific info (serial number, description). For J-Link, enumerates via JLinkExe.

Inputs: backend.

dbgprobe.resetA

Reset the target. Modes: 'soft' (default) — software reset and resume, 'hard' — hardware reset, 'halt' — reset and halt at first instruction.

Inputs: session_id (required), mode.

dbgprobe.rtt.readA

Read buffered RTT data from the target. Returns data as text (UTF-8 by default) or hex. Non-blocking — returns whatever is buffered, waiting up to timeout seconds for initial data.

Inputs: session_id (required), timeout, encoding.

dbgprobe.rtt.startA

Start RTT (Real-Time Transfer) on a connected session. Connects to the JLinkGDBServer RTT telnet port and begins buffering target output (channel 0). Optional address hint for the RTT control block in target RAM.

Inputs: session_id (required), address.

dbgprobe.rtt.statusA

Return RTT status: active, bytes buffered, total read/written.

Inputs: session_id (required).

dbgprobe.rtt.stopC

Stop RTT and disconnect from the telnet port.

Inputs: session_id (required).

dbgprobe.rtt.writeA

Write data to the target via RTT channel 0. Input is text (UTF-8 by default) or hex-encoded bytes.

Inputs: data (required), session_id (required), newline, encoding.

dbgprobe.statusA

Query target state. Returns whether the target is running or halted, and if halted, the current PC and stop reason (e.g. breakpoint hit).

Inputs: session_id (required).

dbgprobe.stepA

Single-step one instruction. Target must be halted first. Returns the new PC and stop reason.

Inputs: session_id (required).

dbgprobe.svd.attachA

Parse an SVD file and attach it to a session. Enables named register reads/writes, field-level access, and auto-decode on mem.read. Re-attaching replaces the previous SVD.

Inputs: path (required), session_id (required).

dbgprobe.svd.describeB

Detailed description of a peripheral, register, or field. Includes description, access type, reset value, and enum values as appropriate.

Inputs: target (required), session_id (required).

dbgprobe.svd.infoB

Get SVD metadata for a session: device name, peripheral count. Returns null if no SVD is attached.

Inputs: session_id (required).

dbgprobe.svd.list_fieldsB

List all fields for a register.

Inputs: register (required), peripheral (required), session_id (required).

dbgprobe.svd.list_peripheralsC

List all peripherals defined in the attached SVD.

Inputs: session_id (required).

dbgprobe.svd.list_registersB

List all registers for a peripheral.

Inputs: peripheral (required), session_id (required).

dbgprobe.svd.readB

Read a register or field by name. For registers (e.g. 'GPIO.OUT'), returns the raw value and all decoded fields. For fields (e.g. 'GPIO.PIN_CNF[3].PULL'), returns just the field value and enum name.

Inputs: target (required), session_id (required).

dbgprobe.svd.set_fieldA

Read-modify-write a single register field. Reads the current register value, modifies the specified field, and writes back. Accepts enum names (e.g. 'PullUp') or integer values.

Inputs: field (required), value (required), session_id (required).

dbgprobe.svd.update_fieldsA

Batch read-modify-write: update multiple fields in one register with a single read and write. Accepts a dict of field names to values (enum names or integers).

Inputs: fields (required), register (required), session_id (required).

dbgprobe.svd.writeA

Write a raw value to a full register. No read-modify-write — the entire register is overwritten. For field-level writes, use svd.set_field.

Inputs: value (required), register (required), session_id (required).

dbgprobe.trace.statusB

Return tracing config and event count.

Inputs: no named parameters.

dbgprobe.trace.tailB

Return last N trace events (default 50).

Inputs: n.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 41 tools

    Disambiguation5/5

    Each tool targets a distinct operation (e.g., breakpoint.set vs breakpoint.clear, mem.read vs mem.write) with no overlap. Even within categories like SVD, separate tools for reading, writing, describing, and listing ensure clear boundaries.

    Naming Consistency5/5

    All tools follow a consistent hierarchical pattern: 'dbgprobe.<category>.<action>' (e.g., dbgprobe.breakpoint.set, dbgprobe.elf.attach). The naming is uniform and predictable across all 41 tools.

    Tool Count4/5

    With 41 tools, the set is large but justified by the diverse domain (breakpoints, memory, ELF, SVD, RTT, tracing, plugins). Each tool serves a specific purpose, though a few categories (e.g., SVD with 8 tools) might be slightly over-partitioned.

    Completeness5/5

    The tool surface covers the full lifecycle of debug probe interaction: connection management, breakpoints, memory access, flash operations, ELF/SVD integration, RTT, tracing, and plugin extensibility. No obvious gaps hinder typical debugging workflows.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      An MCP server that provides comprehensive debugging capabilities for J-Link debuggers, enabling memory, flash, register, and RTT operations through AI assistants.
      24 PyPI
      40
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Debug microcontrollers directly from Claude. This is an MCP server that drives OpenOCD, letting Claude flash firmware, control execution, and inspect a running target — and read your variables and peripheral registers by name instead of raw addresses.
      39
      40 PyPI
      5
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Agentic Hardware-in-the-Loop (Agentic HIL) lets coding agents develop firmware on real embedded boards: flash, reset, read UART and exchange CAN traffic, diagnose and fix. Local, policy-gated MCP tools support OpenOCD, pyOCD and STM32CubeProgrammer, with pytest for CI.
      44
      972 PyPI
      25
      Apache 2.0
    • A
      license
      C
      quality
      C
      maintenance
      MCP server for AI-assisted MCU and embedded firmware debugging. It connects to real hardware via debug probes, inspects CPU/memory/peripherals, manages Keil builds, and provides structured evidence for fault diagnosis.
      19
      57 PyPI
      9
      MIT