jlink-mcp
Drives a SEGGER J-Link probe to debug Arm Cortex-M microcontrollers (verified on an STM32F411CE / Cortex-M4 r0p1). Provides tools to flash firmware, halt/resume/reset/step the CPU, read and write memory, registers and breakpoints, run to breakpoints, and diagnose Cortex-M faults with source-level localisation of faulting addresses.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jlink-mcphalt the CPU and dump the core registers"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jlink-mcp
An MCP server that drives a SEGGER J-Link probe to debug microcontrollers: flash firmware, halt and resume the CPU, read and write memory, registers and breakpoints, and diagnose Cortex-M faults.
Built for use with pi and any other MCP client.
Status
The full tool surface is implemented and the server runs end to end. Everything below was verified on real hardware — a J-Link V11 (S/N 941000024, firmware 2025-04-01) driving an STM32F411CE over SWD at 4000 kHz, with J-Link software V7.52a on Windows:
Area | Tools | Evidence |
Probe |
| Reads S/N, firmware, hardware version, VTref, ITarget and pin states. |
Device database |
| 8621 built-in devices exported via |
Connection and state |
| Connect reports the Cortex-M4 r0p1 core; reset and step confirmed against the running firmware. |
Memory |
| Read the vector table and SCB; write-and-read-back confirmed in RAM. |
Registers |
| Wrote |
Breakpoints |
| Hit a breakpoint in the firmware's main loop; also verified the timeout path. |
Faults |
| Induced a controlled |
Escape hatch |
| Ran raw Commander commands; the reference is taken from this J-Link's own |
Flash |
| Chip erase, range erase, and programming |
Source locations |
| Addresses resolve to |
Fault localisation |
| A deliberate bus fault was traced back to the exact HAL line that dereferenced the bad pointer — see below. |
Everything is now verified on hardware. The flash tools were the last gap, tested once the board's firmware was expendable: scripts/flash-test.mjs backs the whole flash up, validates the image, then exercises every flash path and finally restores the original byte-for-byte (SHA-256 identical, CPU running again).
Several bugs were found by testing against hardware rather than by reading the documentation:
jlink_write_memoryemittedw32, but J-Link names its write commands by width in bytes (w1/w2/w4), so every write failed withUnknown command. Reads are different: those really aremem8/mem16/mem32.jlink_run_toforced a software breakpoint (SetBP <addr> S). In flash that makes J-Link reprogram the containing sector: 6538 ms versus 1764 ms for a hardware breakpoint at the same address. It now defaults toH.jlink_resetreported "CPU running" unconditionally, and both it andjlink_flashrelied onrto leave the CPU running.ractually leaves it halted at the reset vector when vector catch is active, so both now issue an explicitg.decodeExcReturnsilently failed on a JavaScript trap: bitwise operators coerce to signed int32, so(0xFFFFFFFD & 0xff000000) === 0xff000000compared-16777216against4278190080. Exception-frame recovery never ran until this was fixed.jlink_resumetreated J-Link'sError: CPU is not haltedas a failure; it is a state report, and failure detection had to become line-aware to express that.
Related MCP server: dbgprobe-mcp-server
Requirements
Node.js 18 or newer.
SEGGER J-Link software installed (
JLink.exeon Windows,JLinkExeelsewhere). Verified against V7.52a; newer releases should work but the exact command surface has not been checked.
Path resolution order: the JLINK_PATH environment variable, then a scan of C:\Program Files\SEGGER, C:\Program Files (x86)\SEGGER, /opt/SEGGER and /usr/local/SEGGER (including versioned JLink_V* directories, newest first).
Build and verify
npm install
npm run buildnpm run smoke boots the built server over stdio and exercises the tools that need no MCU: jlink_status, jlink_devices, and the error paths (a misspelled device name, an invalid Commander command).
npm run hardware is the hardware-in-the-loop suite. It needs a powered target and it halts and resets the CPU, so do not point it at something that must keep running:
node scripts/hardware-check.mjs STM32F411CEIt defaults to JLINK_DEVICE, then STM32F411CE, and exits non-zero if any check misbehaves.
npm run flash is a separate, destructive suite for the flash tools: it erases the target's flash. It requires an explicit flag so it cannot run by accident, backs the flash up first, validates that the image is a plausible Cortex-M image before erasing anything, and restores the original byte-for-byte at the end. A second copy of the backup is written to the git-ignored tmp/.
npm run flash -- --yes-destroy-flash STM32F411CEEnd-to-end against real firmware
test_project/ is a CubeMX project for this exact board (STM32F411CEU6, PC13 LED) that blinks at 2 Hz. It is the one firmware whose behaviour is known in advance, which makes it the end-to-end fixture: build it, flash it, then prove from the debugger that the pin really toggles.
cd test_project
"/c/Keil_v5/UV4/UV4.exe" -b "$(cygpath -w MDK-ARM/test_project.uvprojx)" -j0 -o "$(cygpath -w build.log)"
cd ..
HEX="$(cygpath -m "$PWD/test_project/MDK-ARM/test_project/test_project.hex")" node scripts/verify-blink.mjsThe project ships CMake presets for a GCC toolchain as well. That path is verified end to end with Arm GNU Toolchain 15.3.1 plus Ninja:
export PATH=/path/to/arm-gnu-toolchain-15.3.rel1/bin:$PATH
cd test_project
cmake --preset Debug && cmake --build --preset Debug
arm-none-eabi-objcopy -O ihex build/Debug/test_project.elf build/Debug/test_project.hexNo C23 problems: GCC 15 defaults to C23, which the GCC project warns breaks many older codebases, but this STM32F4 HAL builds clean with 0 warnings. If that ever changes, -std=gnu11 is the escape hatch.
The objcopy step is not optional. J-Link 7.52a's loadfile does not accept .elf, so jlink_flash refuses it with the exact objcopy command to run instead.
Trap worth knowing: do not unpack that toolchain zip with Git Bash's unzip. It can write arm-none-eabi/bin/ld.exe (2.1 MB) as a 0-byte file while unzip -t still reports "No errors detected", and the only symptom is collect2.exe: fatal error: CreateProcess: No such file or directory at link time. Use 7-Zip or the official installer — or verify afterwards by comparing every zip entry's size against the extracted tree (7360 files, exactly one was damaged here).
scripts/verify-blink.mjs is worth reading as a technique: it samples GPIOC->ODR inside a single J-Link session, using Sleep between reads. Sampling across separate tool calls would alias badly against a 250 ms half period and show nothing. It measured 300/200 ms runs, which is exactly how a 250 ms square wave quantises at 100 ms sampling.
Reference documentation
Everything this project depends on for reference lives under docs/, collected by:
npm run docs:syncdocs/reference/is committed. It holds facts extracted from the installed J-Link itself: its version banner, its complete command list, its reset-type list, and a manifest of what was copied. These can be grepped without a probe attached.docs/vendor/holds SEGGER's own documents (UM08001 and friends) as PDFs pluspdftotextoutput. These files are SEGGER's copyright and are not covered by this project's MIT licence. SEGGER's licence forbids redistribution without written authorisation and this repository is public, so including them here is a known conflict, deliberately accepted by the repository owner. Provenance and removal instructions are in docs/vendor/NOTICE.md; they live in a separate commit, so a singlegit revertundoes it without touching any code.
Note that the J-Link software installed here is 7.52a while its bundled UM08001 manual is labelled 7.50, so the two are not strictly the same revision — prefer the tool's own output when they disagree.
If you are working on this codebase with an AI agent, point it at AGENTS.md first.
Configuration
All settings are environment variables; most can also be overridden per tool call.
Variable | Default | Purpose |
| auto-detected | Full path to |
| none | Default MCU name, e.g. |
|
|
|
|
| Interface speed in kHz. |
| none | Probe serial number, for setups with several probes. |
|
| Per-invocation timeout. Raise it for large flash images. |
|
| Hard cap on captured J-Link output per invocation. |
| unset | Set to |
| unset | Set to |
| unset | Set to |
The default timeout is deliberately below the 60 s request timeout most MCP clients apply, so a stuck operation returns a diagnosable error instead of the client giving up first.
Tools
Tool | Target needed | Purpose |
| no | Probe serial number, firmware, VTref, pin states. Start here. |
| no | Search J-Link's device list for core, flash banks and RAM. |
| yes | Establish a debug connection and report state. |
| yes | Halted or running, and why it stopped. |
| yes | Halt the CPU and confirm it stopped. |
| yes | Resume the CPU. Already-running is reported, not treated as an error. |
| yes | Reset, optionally with a specific reset strategy or a delayed halt, then report the resulting state. |
| yes | Single step N instructions and report PC. |
| yes | Read memory at 8/16/32-bit width, optionally halting first for a consistent snapshot. |
| yes | Write values, then read back and report mismatches. |
| yes | Halt and dump core registers. |
| yes | Write core registers with read-back verification. |
| yes | Set breakpoints, resume, wait for a hit, and report where it stopped. |
| yes | Decode CFSR/HFSR and recover the stacked exception frame. |
| yes | Chip erase, or erase an address range. |
| yes | Program |
| optional | Escape hatch: run arbitrary J-Link Commander commands. |
| no | The commands this J-Link accepts, for use with |
Addresses are hexadecimal by default — both 0x20000000 and 20000000 mean the same thing; pass a JSON number for decimal.
Device names are checked against J-Link's own list
jlink_devices builds its answer from J-Link itself: ExpDevList exports the DLL's built-in list (8621 devices on 7.52a) and JLinkDevices.xml supplies 241 further names that the built-in list lacks. The parsed result is cached on disk and keyed to the J-Link executable's mtime, so the export cost is paid once per J-Link install.
Before connecting, the target tools check the device name against that list and fail early with suggestions:
Error: J-Link has no device called "STM32F411ZZ", so the target could not be connected.
Hint: J-Link knows similarly named devices: STM32F411CC, STM32F411CD, ... Set JLINK_SKIP_DEVICE_VALIDATION=1 to bypass this check.This matters because J-Link's own behaviour is unhelpful: an unknown name either retries until the timeout, or is silently swapped for a different device. The check is best-effort — if the device list cannot be built at all, the server logs to stderr and proceeds.
Register names
rreg/wreg accept R0-R12, R14 (or LR), XPSR, MSP, PSP, RAZ, CFBP, APSR, EPSR, IPSR, PRIMASK, BASEPRI, BASEPRI_MAX, FAULTMASK, CONTROL, IAPSR, EAPSR, IEPSR, FPSCR, FPS0-FPS31 and CycleCnt.
J-Link rejects R13, R15, SP, PC and LR with Illegal register name, which is surprising because it prints all of them in its own list. jlink_write_registers therefore routes PC through SetPC automatically and rejects SP/R13 with a hint to use MSP or PSP.
Using it from pi
pi's MCP gateway installs servers by URL, so a stdio server needs a small bridge. Any stdio-to-HTTP proxy works, for example:
npx -y supergateway --stdio "node dist/index.js" --port 8321 --ssethen install http://localhost:8321/sse through the gateway. Run the bridge with JLINK_DEVICE and friends set in its environment.
If your MCP client supports stdio servers directly, point it at node dist/index.js with no bridge and no arguments.
Design notes
J-Link Commander is driven in script mode, one fresh process per tool call, rather than as a long-lived interactive session. Four behaviours of J-Link 7.52 on Windows forced this:
Piped stdout is block-buffered. When stdout is not a console, output is held until the buffer fills or the process exits. An interactive session that waits for the
J-Link>prompt never receives it, so prompt-based synchronisation is impossible.Reading stdin at EOF spins forever. If a script finishes and J-Link then reads stdin, it loops printing
Unknown command— hundreds of megabytes in seconds. Every generated script therefore ends with anexitterminator so EOF is never reached, andexit-family commands are stripped from caller input so the terminator always stays last.Script files must use CRLF line endings. A LF-only script is mis-parsed.
Closing a session restarts the target, by default. SEGGER states that when the debug connection is closed the target is left running, or its execution is restarted from the pause point. So by default a halt does not survive the call that requested it, and neither does a breakpoint. This is not a hard limit: SEGGER's remedy is the command string
SetRestartOnClose, which is reachable only through J-Link Commander'sexeccommand and applies per session — it does not carry over between processes, so it must be reapplied in every session. SettingJLINK_PERSIST_HALT=1makes this server addexec SetRestartOnClose = 0to every script it generates.
Note that even with that option, a bare memory read resumes the CPU: mem32 performs a halt/restore cycle, so it leaves the core running. Use halt: true to keep it stopped.
Because each call is a fresh process, each one reconnects to the target (roughly a second). The compensating benefit is that no session can be left in a broken state by a crashed call.
Two consequences shape the API:
Tools that need a halted CPU halt within their own call.
jlink_read_registers,jlink_stepandjlink_fault_infoall issuehfirst, andjlink_read_memorytakeshalt: true. This is what makes them correct regardless ofJLINK_PERSIST_HALT, since a fresh session's connect sequence cannot be assumed to preserve the state you left behind.Breakpoints are set and waited on inside one session.
jlink_run_tocombinesSetBP,gandWaitHaltin a single process, because a breakpoint set by an earlier call is already gone. It accepts up to four addresses, all armed in that one session.
Safety rails around the J-Link process:
Hard timeout, then the process tree is killed (
taskkill /T /Fon Windows).Output capped in memory; the process is killed if it exceeds the cap.
A detector kills the process early if J-Link asks an interactive question it can never receive an answer to.
Failure detection is line-aware, so a message that reports a state rather than an error can be excluded per call. This is what lets
jlink_resumetreat****** Error: CPU is not haltedas "already running".Temporary script files are always cleaned up.
Reset behaviour
SEGGER documents that every Cortex-M reset strategy halts the CPU after the reset, because J-Link sets VC_CORERESET in the DEMCR so the core stops before executing user code. A bare r therefore leaves the target stopped at the reset vector, which is a genuine surprise: jlink_reset used to report "CPU running" unconditionally and was simply wrong.
The tool now follows a plain reset with an explicit g, and asks the probe for the resulting state instead of assuming it. haltAfterMs maps to the rx <ms> form, which is the documented way to let a ROM bootloader run before the core is stopped. resetType exposes RSetType; SEGGER recommends leaving it at type 0, which lets J-Link pick the best strategy for the selected device — which is another reason the device name matters.
Breakpoints: force hardware, or pay for flash
SetBP takes an S/H suffix which the SEGGER Commander reference defines as "S: Force software BP" and "H: Force hardware BP". That distinction matters enormously in flash, because SEGGER describes flash breakpoints as "The J-Link software reprograms a flash sector to set or clear a breakpoint."
Measured on the STM32F411CE, waiting 1500 ms for a breakpoint that is never reached:
Breakpoint | Total |
none (baseline) | 1770 ms |
RAM, | 1770 ms |
RAM, | 1774 ms |
flash, | 1764 ms |
flash, | 6538 ms |
flash, | 1771 ms |
Forcing a software breakpoint in flash cost ~4.8 s extra for set plus clear, while a hardware breakpoint at the same address was free. This is a trap worth knowing: by design "J-Link prioritizes the use of hardware breakpoints and automatically switches to flash breakpoints once the available hardware breakpoints are exhausted", but an explicit S overrides that preference and forces the slow path. jlink_run_to therefore defaults to hardware breakpoints and only forces software when asked.
Setting JLINK_DISABLE_FLASH_BP=1 goes further and turns the FlashBP feature off entirely, so J-Link can only ever use hardware comparators. Worth doing as a safety rail, because SEGGER documents two hazards on the flash-breakpoint path:
It temporarily uses the first 2-4 KiB of internal RAM as a flash loader buffer (contents preserved and restored).
DMA engines keep running while the CPU is halted, so a DMA that touches that RAM will corrupt the operation. SEGGER states this "cannot be supported in a generic way to pause/temporarily disable all DMAs".
On a part that uses DMA heavily, disable flash breakpoints.
Flash programming
jlink_flash and jlink_erase are verified end to end by npm run flash. That script backs the target's flash up first, refuses to erase unless the backup validates as a plausible Cortex-M image, and restores the original byte-for-byte at the end. What it proved:
Check | Result |
Chip erase leaves the flash blank | yes |
A | yes |
| yes |
An unsupported extension is refused up front | yes |
| restored byte-identical |
| yes |
| yes |
Range erase clears only the requested sector | yes |
The neighbouring sector and the firmware region stay untouched | yes |
The original image is restored with a matching SHA-256, CPU running | yes |
For a .bin, the tool additionally cross-checks the load address against the flash and RAM ranges J-Link reports for the device, and says what it found:
Address check: 0x08000000 is inside a flash bank (0x08000000 (512 KiB)).That check is advisory rather than a gate, because loading into RAM or into an external bank J-Link does not model is legitimate.
One quirk this release cannot avoid: loadfile resets the device when it finishes and its ? output documents no way to suppress that, so programming resets twice (once inside loadfile, once for the explicit run afterwards).
Source-level reporting
Every tool that reports a program counter takes an optional elf argument. Given one, addresses come back as source locations:
Breakpoint hit at 0x0800112E on STM32F411CE, SWD, 4000 kHz.
Source: Core/Src/main.c:126 in main
Code: led_set(1);Two layers do this:
A built-in ELF symbol-table reader (
src/symbols.ts) — no external tool needed, so it always works, and it also covers firmware built by a toolchain whose binutils are not on PATH (Keil, for instance). It supplies the function name.addr2line, when it is on PATH or set viaJLINK_MCP_ADDR2LINE, for real DWARF file/line. It is the canonical tool and handles DWARF 5, discriminators and inlining correctly, which a hand-rolled.debug_lineparser would not.
Paths are shortened against a project root derived from the ELF's own location: CMake emits to build/<config>/ and Keil to MDK-ARM/<target>/, both two directories below the project root, so a single rule covers both layouts.
The resolver is checked against addr2line address by address — npm run symbols -- <elf> <addr>... — and agrees on every case including the boundaries: an address past the end of the image resolves to nothing rather than to the nearest symbol.
jlink_fault_info resolves the stacked faulting PC, which is the most valuable case of all. That needed one more fix: when a fault is taken in Thread mode on MSP, the C handler's own prologue pushes below the exception frame, so the frame sits above the current MSP and reading eight words from MSP misses it. The tool now reads a 256-byte window and scans upward, validating each candidate against the executable sections from the ELF so stack garbage cannot produce a false positive. A worked example, from a deliberately corrupted pointer:
Bus fault address (BFAR) = 0xDEADBEE0
Stacked exception frame at 0x2001FF94 (Thread mode, MSP):
Faulting instruction PC = 0x0800028C
Source: Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c:232 in HAL_RCC_OscConfig
Code: if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) == RCC_OSCILLATORTYPE_HSE)
The frame sits 4 bytes above MSP, because the fault handler's own prologue pushed below it;
it was found by scanning.npm run debug runs a complete session end to end, breakpoint and fault included.
Fault diagnosis
jlink_fault_info reads SHCSR, CFSR, HFSR, MMFAR and BFAR, decodes every set bit, and — when the CPU is inside a fault handler — follows the EXC_RETURN value in LR to the stack holding the exception frame and reports the address of the faulting instruction. Example from a deliberately induced fault:
A fault is latched in the System Control Block.
Current exception: HardFault (IPSR = 3)
MemManage: IACCVIOL - instruction access violation (MPU or XN region)
HardFault: FORCED - escalated from a configurable fault (see CFSR)
Stacked exception frame at 0x200003C0 (Thread mode, PSP):
Faulting instruction PC = 0xFFFFFFF0
LR at the fault = 0x08004495, xPSR = 0x61000000Frame recovery needs LR to still hold EXC_RETURN, which is true for the common HardFault_Handler: b . idiom. If the handler has called a function, the tool says so and suggests breaking at the handler entry with jlink_run_to instead.
Known limitations
No persistent session, so each tool call pays a connection round trip (~200 ms here) and breakpoints cannot outlive a call. Halts outlive a call only with
JLINK_PERSIST_HALT=1, and even then a bare memory read resumes the CPU.Flash breakpoints cost seconds and wear flash, unless you keep to hardware breakpoints. See above.
Programming flash resets the target twice. This J-Link release's
loadfileresets the device on completion and its?output documents no way to suppress that (thenoresetkeyword in the online documentation belongs to a newer release), sojlink_flashcannot help but reset once itself and once again when it runs the CPU afterwards. Harmless, but visible as two resets.verifyon flash is only supported for.bin(verifybin); other formats rely on J-Link's own load verification.This J-Link release loads only
.bin,.mot,.hexand.srec. The online SEGGER documentation lists.elf,.s19and.s37as well, but that describes a newer J-Link: 7.52a's own?output does not.jlink_flashrejects other extensions up front with theobjcopycommand needed to convert.RTT is not reachable through Commander on 7.52a. Its
?output lists no RTT commands, so RTT needsJLinkRTTLogger.exeor the JLinkARM DLL rather thanjlink_exec.Watchpoints are not wrapped:
SetWP/ClrWPexist and are documented injlink_command_reference, but there is no dedicated tool.
Roadmap
RTT, to read
SEGGER_RTToutput forprintf-style debugging without a UART. NeedsJLinkRTTLogger.exeor the DLL, since Commander does not expose it.SWO/ITM trace. Commander does expose
SWOStart/SWORead/SWOShow, so this is scriptable but needs the SWO pin wired.ELF symbol resolution, so
jlink_run_toandjlink_fault_infocan acceptmainorHardFault_Handlerinstead of raw addresses.Watchpoint tools built on
SetWP/ClrWP.GDB Server management, for stepping through code with a real debugger in parallel.
A persistent session, if a way to force line-buffered output on Windows turns up. That would also make breakpoints durable, which
JLINK_PERSIST_HALTalone cannot do.
License
MIT — see LICENSE.
Exception: the files under docs/vendor/ are copyrighted documentation of SEGGER Microcontroller GmbH. They are included for reference only and are not covered by the MIT licence above. See docs/vendor/NOTICE.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Securely control computers you explicitly pair through files, terminals, processes, screenshots, desktop UI/input, clipboard, browser automation, diagnostics, and document tools.
Run commands and read/write files on your servers over Termalin's keyless tunnels (hosted MCP).
Develop, manage, and debug Railway projects, services, and deployments from within agents.
Drive real devices from your AI Coding tool. Embed a client SDK (Unity, Godot, Flutter, iOS/macOS, Android, React Native, Web) in your app, then capture screenshots, traverse the UI tree, inject taps and key events, and run automated test tasks on the physical device over a secure relay.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI to directly control SEGGER J-Link embedded debug probes via the Model Context Protocol for debugging and firmware management. Users can perform tasks like reading registers, analyzing memory, flashing firmware, and tracking RTT logs using natural language commands.211MIT
- AlicenseAqualityCmaintenanceStateful MCP server for driving debug probes (J-Link) to flash, debug, and inspect embedded targets. Enables AI agents to perform flash, memory, breakpoint, and ELF/SVD-aware operations conversationally.4125 PyPI10MIT
- AlicenseAqualityDmaintenanceEnables AI assistants like Claude to directly debug microcontrollers via JLink, supporting breakpoints, single-step, memory/register access, variable inspection, RTT logging, and firmware flashing.256MIT
- AlicenseAqualityFmaintenanceEnables LLMs to interact with embedded devices by reading and writing Segger RTT data through a J-Link debugger.91MIT