stlink-mcp 0.8.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. stlink_mcp-0.8.5/.gitattributes +7 -0
  2. stlink_mcp-0.8.5/.gitignore +7 -0
  3. stlink_mcp-0.8.5/AGENTS.md +85 -0
  4. stlink_mcp-0.8.5/CHANGELOG.md +271 -0
  5. stlink_mcp-0.8.5/CLAUDE.md +1 -0
  6. stlink_mcp-0.8.5/CONTRIBUTING.md +45 -0
  7. stlink_mcp-0.8.5/GEMINI.md +1 -0
  8. stlink_mcp-0.8.5/LICENSE +21 -0
  9. stlink_mcp-0.8.5/PKG-INFO +380 -0
  10. stlink_mcp-0.8.5/README.md +354 -0
  11. stlink_mcp-0.8.5/ROADMAP.md +55 -0
  12. stlink_mcp-0.8.5/docs/findings.md +611 -0
  13. stlink_mcp-0.8.5/pyproject.toml +113 -0
  14. stlink_mcp-0.8.5/src/stlink_mcp/__init__.py +13 -0
  15. stlink_mcp-0.8.5/src/stlink_mcp/__main__.py +175 -0
  16. stlink_mcp-0.8.5/src/stlink_mcp/console.py +128 -0
  17. stlink_mcp-0.8.5/src/stlink_mcp/debugger.py +386 -0
  18. stlink_mcp-0.8.5/src/stlink_mcp/disasm.py +62 -0
  19. stlink_mcp-0.8.5/src/stlink_mcp/faults.py +366 -0
  20. stlink_mcp-0.8.5/src/stlink_mcp/gdbmi.py +225 -0
  21. stlink_mcp-0.8.5/src/stlink_mcp/image.py +243 -0
  22. stlink_mcp-0.8.5/src/stlink_mcp/probe.py +1293 -0
  23. stlink_mcp-0.8.5/src/stlink_mcp/py.typed +0 -0
  24. stlink_mcp-0.8.5/src/stlink_mcp/quirks.py +767 -0
  25. stlink_mcp-0.8.5/src/stlink_mcp/read_effects.py +213 -0
  26. stlink_mcp-0.8.5/src/stlink_mcp/result.py +128 -0
  27. stlink_mcp-0.8.5/src/stlink_mcp/runner.py +596 -0
  28. stlink_mcp-0.8.5/src/stlink_mcp/sampling.py +124 -0
  29. stlink_mcp-0.8.5/src/stlink_mcp/server.py +4027 -0
  30. stlink_mcp-0.8.5/src/stlink_mcp/settings.py +125 -0
  31. stlink_mcp-0.8.5/src/stlink_mcp/svd.py +417 -0
  32. stlink_mcp-0.8.5/src/stlink_mcp/symbols.py +377 -0
  33. stlink_mcp-0.8.5/src/stlink_mcp/toolchain.py +339 -0
  34. stlink_mcp-0.8.5/src/stlink_mcp/vendordb.py +279 -0
  35. stlink_mcp-0.8.5/tests/NEEDS.md +171 -0
  36. stlink_mcp-0.8.5/tests/blind/README.md +17 -0
  37. stlink_mcp-0.8.5/tests/blind/__init__.py +0 -0
  38. stlink_mcp-0.8.5/tests/blind/faults/arbiter-rulings-n13.json +16 -0
  39. stlink_mcp-0.8.5/tests/blind/faults/audit.md +91 -0
  40. stlink_mcp-0.8.5/tests/blind/faults/contract.json +164 -0
  41. stlink_mcp-0.8.5/tests/blind/faults/contract.md +238 -0
  42. stlink_mcp-0.8.5/tests/blind/faults/decisions.json +774 -0
  43. stlink_mcp-0.8.5/tests/blind/faults/ledger.json +1295 -0
  44. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N1-s1.json +12 -0
  45. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N1-s2.json +12 -0
  46. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N1-s3.json +12 -0
  47. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N10-s1.json +12 -0
  48. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N10-s2.json +12 -0
  49. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N11-s1.json +12 -0
  50. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N11-s2.json +12 -0
  51. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N11-s3.json +12 -0
  52. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N11-s4.json +12 -0
  53. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N12-s1.json +12 -0
  54. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N12-s2.json +12 -0
  55. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N12-s3.json +12 -0
  56. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N13-s1.json +12 -0
  57. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N13-s2.json +20 -0
  58. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N2-s1.json +12 -0
  59. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N2-s2.json +12 -0
  60. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N2-s3.json +12 -0
  61. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N2-s4.json +12 -0
  62. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N3-s1.json +12 -0
  63. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N3-s2.json +12 -0
  64. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N3-s3.json +12 -0
  65. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N4-s1.json +12 -0
  66. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N4-s2.json +12 -0
  67. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N4-s3.json +12 -0
  68. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N5-s1.json +12 -0
  69. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N5-s2.json +12 -0
  70. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N6-s1.json +12 -0
  71. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N6-s2.json +12 -0
  72. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N7-s1.json +12 -0
  73. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N7-s2.json +12 -0
  74. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N7-s3.json +12 -0
  75. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N8-s1.json +12 -0
  76. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N8-s2.json +12 -0
  77. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N8-s3.json +12 -0
  78. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N8-s4.json +12 -0
  79. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N9-s1.json +12 -0
  80. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N9-s2.json +12 -0
  81. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-N9-s3.json +12 -0
  82. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-R1.json +11 -0
  83. stlink_mcp-0.8.5/tests/blind/faults/seeds/FAULTS-R2.json +15 -0
  84. stlink_mcp-0.8.5/tests/blind/faults/sources/LICENSE-Apache-2.0.txt +202 -0
  85. stlink_mcp-0.8.5/tests/blind/faults/sources/README.md +32 -0
  86. stlink_mcp-0.8.5/tests/blind/faults/sources/cmsis-scb-fault-registers.md +156 -0
  87. stlink_mcp-0.8.5/tests/blind/test_faults.py +726 -0
  88. stlink_mcp-0.8.5/tests/blind/test_faults_n13.py +623 -0
  89. stlink_mcp-0.8.5/tests/conftest.py +15 -0
  90. stlink_mcp-0.8.5/tests/elfgen.py +38 -0
  91. stlink_mcp-0.8.5/tests/fixtures/connect_f4_daemon_keeps_probe_plain.txt +5 -0
  92. stlink_mcp-0.8.5/tests/fixtures/connect_f4_daemon_keeps_probe_shared.txt +57 -0
  93. stlink_mcp-0.8.5/tests/fixtures/connect_h7_asleep_under_reset_with_cpuid.txt +33 -0
  94. stlink_mcp-0.8.5/tests/fixtures/connect_h7_swd_pins_taken_hotplug.txt +17 -0
  95. stlink_mcp-0.8.5/tests/fixtures/connect_h7_swd_pins_taken_under_reset.txt +19 -0
  96. stlink_mcp-0.8.5/tests/fixtures/connect_h7_v3_stuck_dev_tcp_cmd_err.txt +16 -0
  97. stlink_mcp-0.8.5/tests/fixtures/connect_n6_v3_jtag.txt +19 -0
  98. stlink_mcp-0.8.5/tests/fixtures/connect_v2_f407.txt +19 -0
  99. stlink_mcp-0.8.5/tests/fixtures/connect_v2_jtag_under_reset.txt +19 -0
  100. stlink_mcp-0.8.5/tests/fixtures/connect_v2_old_firmware_by_index.txt +6 -0
  101. stlink_mcp-0.8.5/tests/fixtures/connect_v2_old_firmware_by_serial.txt +5 -0
  102. stlink_mcp-0.8.5/tests/fixtures/connect_v3e_jtag_refused.txt +17 -0
  103. stlink_mcp-0.8.5/tests/fixtures/coreReg_f4_halted_before.txt +80 -0
  104. stlink_mcp-0.8.5/tests/fixtures/coreReg_f4_running_before.txt +80 -0
  105. stlink_mcp-0.8.5/tests/fixtures/core_state_f4_halted.txt +76 -0
  106. stlink_mcp-0.8.5/tests/fixtures/core_state_h7_after_reset.txt +75 -0
  107. stlink_mcp-0.8.5/tests/fixtures/core_state_h7_held_after_lockup.txt +113 -0
  108. stlink_mcp-0.8.5/tests/fixtures/core_state_h7_held_running.txt +113 -0
  109. stlink_mcp-0.8.5/tests/fixtures/core_state_h7_locked_up.txt +75 -0
  110. stlink_mcp-0.8.5/tests/fixtures/core_state_h7_running.txt +75 -0
  111. stlink_mcp-0.8.5/tests/fixtures/core_state_n6_locked_up.txt +75 -0
  112. stlink_mcp-0.8.5/tests/fixtures/elf/disasm.elf +0 -0
  113. stlink_mcp-0.8.5/tests/fixtures/elf/disasm_a.c +4 -0
  114. stlink_mcp-0.8.5/tests/fixtures/elf/disasm_b.c +89 -0
  115. stlink_mcp-0.8.5/tests/fixtures/elf/flash_runs.bin +0 -0
  116. stlink_mcp-0.8.5/tests/fixtures/elf/flash_runs.c +16 -0
  117. stlink_mcp-0.8.5/tests/fixtures/elf/flash_runs.elf +0 -0
  118. stlink_mcp-0.8.5/tests/fixtures/elf/flash_runs.hex +14 -0
  119. stlink_mcp-0.8.5/tests/fixtures/elf/flash_runs.ld +11 -0
  120. stlink_mcp-0.8.5/tests/fixtures/elf/h7_vars_dwarf3.elf +0 -0
  121. stlink_mcp-0.8.5/tests/fixtures/elf/h7_vars_dwarf4.elf +0 -0
  122. stlink_mcp-0.8.5/tests/fixtures/elf/h7_vars_dwarf5.elf +0 -0
  123. stlink_mcp-0.8.5/tests/fixtures/gdbmi_h7_transcript.txt +82 -0
  124. stlink_mcp-0.8.5/tests/fixtures/gdbmi_run_until_cases.txt +680 -0
  125. stlink_mcp-0.8.5/tests/fixtures/gdbserver_g4_old_probe_firmware.txt +3 -0
  126. stlink_mcp-0.8.5/tests/fixtures/gdbserver_log16_h7_watch.txt +65 -0
  127. stlink_mcp-0.8.5/tests/fixtures/gdbserver_n6_ap1_closed.txt +16 -0
  128. stlink_mcp-0.8.5/tests/fixtures/objdump_f4_bl.txt +8 -0
  129. stlink_mcp-0.8.5/tests/fixtures/objdump_n6_udf.txt +9 -0
  130. stlink_mcp-0.8.5/tests/fixtures/objdump_range_disasm_long_function.txt +418 -0
  131. stlink_mcp-0.8.5/tests/fixtures/objdump_range_disasm_scale_b.txt +20 -0
  132. stlink_mcp-0.8.5/tests/fixtures/objdump_range_h7_reset_handler.txt +90 -0
  133. stlink_mcp-0.8.5/tests/fixtures/objdump_range_h7_writer.txt +19 -0
  134. stlink_mcp-0.8.5/tests/fixtures/option_bytes_f4.txt +59 -0
  135. stlink_mcp-0.8.5/tests/fixtures/option_bytes_h7.txt +85 -0
  136. stlink_mcp-0.8.5/tests/fixtures/option_bytes_n6.txt +20 -0
  137. stlink_mcp-0.8.5/tests/fixtures/probe_list_n6_closed_plus_h743.txt +102 -0
  138. stlink_mcp-0.8.5/tests/fixtures/probe_list_none_attached.txt +176 -0
  139. stlink_mcp-0.8.5/tests/fixtures/probe_list_six_incl_h753_eval.txt +107 -0
  140. stlink_mcp-0.8.5/tests/fixtures/probe_list_v2_h743_n657.txt +211 -0
  141. stlink_mcp-0.8.5/tests/fixtures/probe_list_windows_n657.txt +32 -0
  142. stlink_mcp-0.8.5/tests/fixtures/probe_list_with_unreadable_entries.txt +96 -0
  143. stlink_mcp-0.8.5/tests/fixtures/proc_net_tcp_stlink_server_listening.txt +2 -0
  144. stlink_mcp-0.8.5/tests/fixtures/program_f407_sector_write_protected.txt +36 -0
  145. stlink_mcp-0.8.5/tests/fixtures/read_h7_asleep_with_cpuid.txt +30 -0
  146. stlink_mcp-0.8.5/tests/fixtures/read_h7_awake_with_cpuid.txt +33 -0
  147. stlink_mcp-0.8.5/tests/fixtures/read_n6_ap1_closed.txt +23 -0
  148. stlink_mcp-0.8.5/tests/fixtures/swo_f407_itm.log +14 -0
  149. stlink_mcp-0.8.5/tests/fixtures/swo_f407_itm_clock_6pc_low.log +10 -0
  150. stlink_mcp-0.8.5/tests/fixtures/swo_f407_itm_clock_wrong.log +10 -0
  151. stlink_mcp-0.8.5/tests/fixtures/swo_f407_stdout.txt +29 -0
  152. stlink_mcp-0.8.5/tests/test_canary.py +162 -0
  153. stlink_mcp-0.8.5/tests/test_cli.py +111 -0
  154. stlink_mcp-0.8.5/tests/test_console.py +113 -0
  155. stlink_mcp-0.8.5/tests/test_contract.py +3773 -0
  156. stlink_mcp-0.8.5/tests/test_debugger.py +364 -0
  157. stlink_mcp-0.8.5/tests/test_disasm.py +96 -0
  158. stlink_mcp-0.8.5/tests/test_faults.py +458 -0
  159. stlink_mcp-0.8.5/tests/test_hwcheck.py +35 -0
  160. stlink_mcp-0.8.5/tests/test_image.py +129 -0
  161. stlink_mcp-0.8.5/tests/test_interface.py +366 -0
  162. stlink_mcp-0.8.5/tests/test_parsing.py +139 -0
  163. stlink_mcp-0.8.5/tests/test_probe_list.py +359 -0
  164. stlink_mcp-0.8.5/tests/test_quirks.py +203 -0
  165. stlink_mcp-0.8.5/tests/test_read_effects.py +74 -0
  166. stlink_mcp-0.8.5/tests/test_sampling.py +82 -0
  167. stlink_mcp-0.8.5/tests/test_settings.py +112 -0
  168. stlink_mcp-0.8.5/tests/test_sharing_daemon.py +150 -0
  169. stlink_mcp-0.8.5/tests/test_stdio.py +131 -0
  170. stlink_mcp-0.8.5/tests/test_svd.py +172 -0
  171. stlink_mcp-0.8.5/tests/test_swo.py +104 -0
  172. stlink_mcp-0.8.5/tests/test_symbols.py +102 -0
  173. stlink_mcp-0.8.5/tests/test_toolchain.py +242 -0
  174. stlink_mcp-0.8.5/tests/test_user_boards.py +257 -0
  175. stlink_mcp-0.8.5/tests/test_vendordb.py +401 -0
  176. stlink_mcp-0.8.5/tools/README.md +7 -0
  177. stlink_mcp-0.8.5/tools/dwctl.py +139 -0
  178. stlink_mcp-0.8.5/tools/hwcheck/README.md +41 -0
  179. stlink_mcp-0.8.5/tools/hwcheck/build_images.py +185 -0
  180. stlink_mcp-0.8.5/tools/hwcheck/hwcheck.py +956 -0
  181. stlink_mcp-0.8.5/tools/hwcheck/images/beacon.c +222 -0
  182. stlink_mcp-0.8.5/tools/hwcheck/images/debug_target.c +70 -0
  183. stlink_mcp-0.8.5/tools/hwcheck/images/f4_data.ld +10 -0
  184. stlink_mcp-0.8.5/tools/hwcheck/images/f4_flash.ld +8 -0
  185. stlink_mcp-0.8.5/tools/hwcheck/images/f4_ram.ld +8 -0
  186. stlink_mcp-0.8.5/tools/hwcheck/images/h7_data.ld +10 -0
  187. stlink_mcp-0.8.5/tools/hwcheck/images/h7_flash.ld +8 -0
  188. stlink_mcp-0.8.5/tools/hwcheck/images/h7_hole.ld +11 -0
  189. stlink_mcp-0.8.5/tools/hwcheck/images/h7_mixed.ld +10 -0
  190. stlink_mcp-0.8.5/tools/hwcheck/images/h7_ram.ld +8 -0
  191. stlink_mcp-0.8.5/tools/hwcheck/images/n6_data.ld +10 -0
  192. stlink_mcp-0.8.5/tools/hwcheck/images/n6_ram.ld +8 -0
  193. stlink_mcp-0.8.5/tools/hwcheck/images/read_effects.c +148 -0
  194. stlink_mcp-0.8.5/tools/hwcheck/n6_flash_boot.py +221 -0
  195. stlink_mcp-0.8.5/tools/survey_device_db.py +129 -0
  196. stlink_mcp-0.8.5/uv.lock +819 -0
@@ -0,0 +1,7 @@
1
+ # Fixtures are captured output and compiled images: git must check them out
2
+ # byte for byte. On Windows its line-ending conversion turned a captured SWO
3
+ # log's LF into CRLF and broke a test that compares bytes (2026-10-02).
4
+ tests/fixtures/** -text
5
+ # GitHub's "Download ZIP" and `git archive` write the version into
6
+ # pyproject.toml's describe-subst, so a tree with no .git still knows its tag.
7
+ pyproject.toml export-subst
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
@@ -0,0 +1,85 @@
1
+ # stlink-mcp — working rules
2
+
3
+ > This file is the single source of rules for this repo. `CLAUDE.md` is a one-line `@AGENTS.md` import, and that line is load-bearing: Claude Code does not discover a bare `AGENTS.md`. `GEMINI.md` is a symlink to this file, so never write to it.
4
+
5
+ An MCP server that lets an agent program, run and inspect an STM32 target over ST-LINK, driving the STM32CubeCLT command line tools. Runs locally, on the machine the board is plugged into. Intended to be published, so it must make sense to someone whose board is not ours.
6
+
7
+ ## Intent
8
+
9
+ ### What it is
10
+
11
+ An MCP server, on the current MCP 2.x SDK, that a user runs **locally on the machine their STM32 board is plugged into** and attaches to a local agent session. Once attached, the agent can detect the attached board or be told what it is, and can then **flash it, reset it and interrogate it** over whatever debug and communication interfaces that board provides: SWD, JTAG, SWO and the serial console.
12
+
13
+ Getting it running and attached is part of the product, not a footnote. A user should be able to install it, point an agent client at it, and have a working board conversation without reading the source.
14
+
15
+ ### Deployment model, and one constraint that outlives it
16
+
17
+ One user, one machine, boards on the desk in front of them. **There is no team access model: no authentication, no approvals, no multi-tenancy.** That is a deliberate scope decision, not an oversight, and it is what keeps the tool small.
18
+
19
+ It may become a feature later, so **the architecture must not prohibit it.** Nothing may assume a single implicit caller in a way that would have to be unpicked to add identity, sessions or per-caller policy. Concretely: state keyed to a caller rather than to a module global, and every operation traceable to who asked for it even while the answer is always "the local user".
20
+
21
+ ### The quality bar, and the reason the project exists at all
22
+
23
+ **What an AI coding agent believes about the target must be what is actually true of it.**
24
+
25
+ The vendor command line tools report success for operations they did not perform. `STM32_Programmer_CLI -s` prints "Start operation achieved successfully" for a start that left the program counter untouched, and a GDB load against an already-running target completes cleanly while changing nothing.
26
+
27
+ A human developer usually catches that, because they hold information the tool never gave them: the LED on the board, the console text that looks like the previous build, the board in their hand. **An agent has none of that.** The text a tool returns is its entire model of the hardware. So a tool that overstates does not merely fail an operation. It makes the agent confidently wrong, and the agent then writes that wrongness into code, commit messages and documentation, where it costs far more than the failed operation did.
28
+
29
+ The primary user is therefore an agent, with a human supervising. Where the two want different things, the agent's need to not be misled wins.
30
+
31
+ ### The needs, each checkable by reading one result
32
+
33
+ - **N1. Every result carries its verdict and its evidence.** `verified` names what was observed, `unverified` names what was not checked and is not success, `failed` names the cause. Never return `ok: True` on the strength of an exit code.
34
+ - **N2. A missing fact is reported, never inferred.** An uncharacterised part refuses the operation rather than assuming an address. Prefer a loud gap to a quiet guess.
35
+ - **N3. Anything the agent cannot observe for itself is stated in the result:** whether the target was reset, whether the answer came from a cache, whether the access port in use is not the family's, whether another process holds the probe.
36
+ - **N4. Nothing perturbs the target silently.** Any operation that halts, resets or restarts the target either refuses or reports that it did so.
37
+ - **N5. It runs on hardware that is not ours.** Part-specific facts live in data rather than code. The board is either detected, or **described by the user without editing the installed package**, and an unknown part still identifies itself rather than failing completely.
38
+ - **N6. Every part-specific fact records how it was established.** Measured on hardware, taken from a vendor database, and stated by the user are three different states and are never presented as the same one.
39
+ - **N7. Flash, reset and interrogate are all first-class operations.** Reset in particular is something the user asks for and gets told the truth about, not only a side effect of something else.
40
+ - **N8. The interface is a parameter, not an assumption.** SWD, JTAG and the serial console are choices the user or the board makes. Nothing hardcodes one of them.
41
+
42
+ And one architectural constraint, which is checked by reading the design rather than a result:
43
+
44
+ - **N9. Nothing assumes a single implicit caller.** Identity and per-caller policy are out of scope today and must remain addable without a redesign.
45
+
46
+ Which of these the code meets today is tracked in one place, the "Where it stands" table in `ROADMAP.md`. This file states the needs and does not restate their status.
47
+
48
+ ### Non-goals
49
+
50
+ It does not build firmware, does not sign images, is not a human-facing debugger interface, does not abstract over non-ST probes, and does not replace STM32CubeProgrammer. It drives it.
51
+
52
+ ## Claims about tool behaviour are tested, not assumed
53
+
54
+ Every wrong turn in this project's history came from believing documentation or a search result over the artefact in front of us. Three examples worth keeping:
55
+
56
+ - The MCP SDK's API was assumed to be `mcp.server.fastmcp`. It is `mcp.server.MCPServer` in 2.x. One import check would have settled it, and did.
57
+ - Core debug on the STM32N6 was concluded to be unavailable, from an error message. It was an access-port problem.
58
+ - The access port itself was then concluded settled, having changed two variables at once (a firmware upgrade and the flag) without separating them.
59
+
60
+ So: run the probe, read the header, open the archive. Do not reason from the changelog.
61
+
62
+ ## Quirks belong in the table, not the code
63
+
64
+ `src/stlink_mcp/quirks.py` holds everything family specific. Adding a family is one entry there plus a device-ID mapping. The tests in `tests/test_quirks.py` enforce internal consistency (a RAM load address without a vector table address, for instance, is rejected).
65
+
66
+ **Entries in `FAMILIES` are hand-written and must be fully measured on hardware.** That is no longer only a request: `TestTheContributionRuleIsCheckedNotJustStated` fails the build if any entry there carries a value that is not `measured`. If you have a value you have not run on a board, it does not belong in that dict.
67
+
68
+ Unmeasured values are allowed in the table, but they arrive from elsewhere and they arrive labelled. Every field has a provenance, one of `measured`, `vendor-db`, `user-stated` or `assumed`, and `DEFAULT` is the worked example: its access port 0 is `assumed`, because it is a fact about the STM32 range and not about any part. What the label changes is behaviour, not presentation. `flash_firmware` refuses a base it did not get from a measurement or from the caller, because that write persists, and the readback afterwards confirms what was written, not that it went to the right place.
69
+
70
+ Every entry carries `notes`, and that field is the point rather than decoration: it is what stops the next person rediscovering the quirk the hard way.
71
+
72
+ Registers whose read changes state live in the same spirit in `src/stlink_mcp/read_effects.py`, keyed by peripheral block and register name rather than by part, with the same labels: an entry is `measured` only with the part and the date it was measured on, and `assumed` says what is missing. `tests/test_read_effects.py` enforces both.
73
+
74
+ ## Tests
75
+
76
+ `uv run pytest`. Parser samples are real captured tool output, never invented, because an invented sample tests the parser against your belief about the format, which is the thing most likely to be wrong. Each test names the need it checks, from `tests/NEEDS.md`. Hardware-dependent paths are not unit tested; `tools/hwcheck` runs them against the boards, and what was run and what it showed goes in `docs/findings.md`.
77
+
78
+ ## Public repo hygiene
79
+
80
+ - No estate specifics, no host names, no names of the author's other projects or of the hardware on his bench, and no paths from his machine: not in code, docs, tests, commit messages or tag messages. Name what a machine was ("a Windows 11 laptop", "the Linux bench"), never what it is called. History is rewritten to remove one, which costs every citation of every later commit, so the check comes before the commit, not after.
81
+ - No secrets, obviously, and no serial numbers of real probes outside test fixtures.
82
+ - Not affiliated with STMicroelectronics, and the name deliberately avoids their STM32Cube product brand.
83
+ - Never commit or package STM32CubeProgrammer's device database files, or any table derived from them. Seeding happens at run time from the user's own install and nowhere else. Those files are under ST's SLA0048, whose condition 5 forbids any use or redistribution that would put them under open-source terms, and this repo is MIT. Quoting a single address as a fact, or a count of what the database contains, is fine; a copied or pre-parsed dataset is not.
84
+ - Prose in `README.md` follows the author's voice: plain descriptive words, no idioms, and a plain ASCII `-` rather than an em-dash.
85
+ - Three audiences, three places. `README.md` is for someone who wants to use it, and carries conclusions only. Measurements, dates, and what was got wrong on the way go in `docs/findings.md`, which the README links to. Tool docstrings are instructions for the agent that calls the tool, roughly 40 to 80 words: an agent loads every one of them on every session, and a long description buries the one line that changes what it does.
@@ -0,0 +1,271 @@
1
+ # Changelog
2
+
3
+ Notable changes to stlink-mcp are recorded here. The format follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) and versions follow [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The version is read from the git tag; a build past the last tag reports the tag plus the number of commits since it, as in `0.1.0+3`.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.8.5] - 2026-11-08
8
+
9
+ The first version on PyPI. No change to the code.
10
+
11
+ ### Added
12
+
13
+ - The package is published on PyPI as `stlink-mcp` from this version, so `uvx stlink-mcp` runs it with no clone. The README's client configuration and Claude Code command use that form; the clone form stays for working on the code. A GitHub Actions workflow publishes every `v*` tag through PyPI's trusted publishing, with no stored token.
14
+ - The README carries the MCP Registry's ownership marker for `io.github.dii/stlink-mcp`, as a hidden comment.
15
+
16
+ ## [0.8.4] - 2026-11-08
17
+
18
+ The documentation only; no change to the code beyond the instructions the server gives every client. The repository goes public at this version.
19
+
20
+ ### Changed
21
+
22
+ - The README, after an outside read before the repository goes public: the opening names what the server does beyond loading an image; a `run_firmware` result is shown as the agent sees it; the usual-session guide, in the README and in the instructions the server gives every client, says that a flash-linked build goes `flash_firmware`, `reset_target`, `capture_console`, since `run_firmware` takes only an image linked for RAM; `stlink-server` is explained in one place; the STM32N6 start-up note sits in its own section; the second diagram, a repeated section and a remark on another project's TODO file are gone; and four compressed sentences are plainer.
23
+ - The related-projects section of the README says when the other servers were last read in source.
24
+ - ROADMAP: the outside-report gate is recorded as met on 2026-10-27, and the author's read of the README before going public is no longer listed, having happened.
25
+
26
+ ## [0.8.3] - 2026-10-28
27
+
28
+ ### Added
29
+
30
+ - From an STM32CubeIDE install, register names come from the SVD files in its product database plugin, so `read_memory` and `sample_values` decode them as with STM32CubeCLT. Run on the Linux bench with STM32CubeIDE 2.2.0 and no STM32CubeCLT in use: `tools/hwcheck` on the three boards passed 112 of 112, its images built with the IDE's compiler, and the programmer, the GDB server and GDB were each started from the IDE's plugin folders.
31
+
32
+ ## [0.8.2] - 2026-10-28
33
+
34
+ From a user's report on an STM32G474. Run on the Linux bench on 2026-10-28: `tools/hwcheck` on the STM32H7, STM32N6 and STM32F407 boards, without `--flash` or the closed-port section, passed 112 of 112. Finding the tools inside STM32CubeIDE is tested against that report's folder names, not run on an install.
35
+
36
+ ### Added
37
+
38
+ - Without a complete STM32CubeCLT, the programmer, the GDB server and GDB inside an STM32CubeIDE install are used, each from its newest plugin folder that has it; `--check` names the source. `STLINK_MCP_CLT_ROOT` may name a CubeIDE install.
39
+ - A tree from GitHub's "Download ZIP" or `git archive` reports its version from the tag; it reported `0.0.0+unknown`.
40
+
41
+ ### Fixed
42
+
43
+ - A GDB server that refused the probe's firmware as too old was explained as a debug port the image had closed, on the error line alone. The explanation now follows the server's "Reason:" line: refused firmware names the probe's version and how to update it, and the closed-port text is kept for "Reason: Unknown". Reported on an STM32G474 whose programmer still used the same probe.
44
+ - The hints for a probe whose firmware the programmer refuses give the upgrade tool's command line, since an agent cannot use its window.
45
+
46
+ ## [0.8.1] - 2026-10-08
47
+
48
+ The README and the package description only; no change to the code.
49
+
50
+ ### Changed
51
+
52
+ - The README opens with what a result means, and its first section is why the reports have to be true: ST's tools reporting success for things that did not happen, and the rule that follows.
53
+ - The package description said every tool verifies its own effect before reporting success. A result can be `unverified`, and the description now says so.
54
+
55
+ ## [0.8.0] - 2026-10-07
56
+
57
+ Run on an STM32H7, an STM32N6 and an STM32F407 board on 2026-10-07. With `--flash` and the N6's closed-port section, `tools/hwcheck` passed the H7's 62 of 62, the N6's 24 of 24 with its flash checks 2 of 2 and the closed-port section 4 of 4, and the F407's 39 of 39; on the code this release carries, 112 of 112 without `--flash` or the closed-port section. On a Windows 11 laptop with an STM32N6 board, 23 passed, and the check of ST's sharing daemon reported itself skipped, since without /proc it cannot look. The N6 booted from its external flash with `tools/hwcheck/n6_flash_boot.py` on 2026-10-06.
58
+
59
+ ### Added
60
+
61
+ - `sample_values`: variables, addresses or registers sampled together while the program runs, without halting or resetting it, each summarised (first, last, range, changes and when it first changed) with how many samples found the core running or not, and `until` to stop at the first sample where a condition holds.
62
+ - `disassemble`: a function by name, or the code around an address, from the ELF, each instruction with its source file and line and the function an inlined copy came from; at most 256 instructions, the rest of a longer function by address. A name two static functions share is refused with both, where objdump's own lookup takes the first. Given `probe_serial` it compares the target's code with the ELF's, without halting it, and is verified only when they match.
63
+ - `read_option_bytes`: the option bytes as STM32_Programmer_CLI reads and explains them, readout protection first, then each of ST's sections with every value and its meaning. Read only: nothing in the package writes option bytes. On the STM32N6 it fails with ST's reason.
64
+ - `flash_firmware` takes ELF and Intel HEX files, written to the addresses inside them. Every run of bytes must lie in the part's flash, from its measured base and the size its connect reports, or nothing is written; an image linked for RAM is refused, and so is any such file on the STM32N6, whose connect gives no size. `address` with such a file is refused, since the file says where.
65
+ - `flash_firmware` writes the STM32N6's external flash through ST's loader for the NUCLEO-N657X0-Q, verified by reading it back.
66
+ - `run_firmware` takes `keep_probe`. By default it lets go of the probe once the program starts; `keep_probe=True` keeps a GDB server holding it, and the reading tools work beside it. While a server holds the probe, a core that locks up is reset and halted at its reset vector, which clears its fault registers; `board_state` and `read_fault_status` say so when they find a core halted that way. Holding was the default for a while, to keep a program that closes the debug port from locking the debugger out, and it did not do that.
67
+ - `run_firmware` verifies an image that prints nothing, or a board with no console, when program-counter samples lie inside the image; it was `unverified` before.
68
+ - `board_state` says whether the core runs, is halted, locked up or sleeping, from DHCSR, with program-counter samples read without halting it; given `elf_path`, where in the source they fall.
69
+ - `locate_fault` and `run_until` give the instruction at the fault or the stop, disassembled from the ELF. `run_until` also stops on a function's inlined copies, since GDB breaks only on its out-of-line copy.
70
+ - Reads that change state: `read_memory` refuses, before anything is read, a register whose read is known to change it, such as the UART, SPI, I2C and ADC data registers and the RTC's time registers, from a list kept by peripheral block and from ST's own readAction marks. `read_effects_ok` reads it anyway and says what the read did. Any other read in the peripheral range names the registers it covered. Board files take `[[read_effects]]` entries for registers the list does not cover.
71
+ - `board_state` says whether an STM32N6 is in development boot or flash boot, from its boot pins.
72
+ - `tools/hwcheck/n6_flash_boot.py`: boots the N6 from its external flash with an image that opens debug, and checks the tools around it, a step per position of the BOOT1 switch.
73
+ - The server sends MCP instructions when a client connects: what it is for, how to read a result, and the usual order of calls. The README carries the same text for clients that do not pass it on to their agent.
74
+ - `tools/hwcheck` checks read effects on the H7 and the F407, writes and restores the flash of the H7, the F407 and the start of the N6's external flash with `--flash`, and can power-cycle probes first with `--cycle-port`.
75
+
76
+ ### Changed
77
+
78
+ - An argument no parameter of the tool has is refused, naming the nearest parameter, and nothing is done. The MCP SDK dropped it without a word, so a misspelt `length_bytes` read the default length.
79
+ - The tools reach a probe a GDB server holds: the server is started in ST's shared mode, and the reading tools connect beside it without halting the core while GDB stays attached. `reset_target` and `flash_firmware` stop the server first and say so in `released_probe_first`. Shared mode needs `stlink-server`, which STM32CubeCLT installs; without it those tools refuse until `release_probe`, as before.
80
+ - On Linux, ST's `stlink-server` is stopped before a programmer call when no ST-LINK GDB server runs on the machine, so calls after `release_probe` connect directly and reach a probe the daemon kept. The server assumes it is the probes' only user.
81
+ - `release_probe` resumes a core `run_until` left halted.
82
+ - Every GDB server keeps the log that records the chip's reason for a stop, so `run_until` uses whichever server holds the probe rather than replacing it.
83
+ - Tools that may reset (`board_state`, `core_registers` without `elf_path`, `run_firmware`, `flash_firmware`) try a connect under reset after a hotplug and a reset-pulse connect have failed.
84
+ - `flash_firmware` says what its erase took beyond the image: the bytes beside it in each 128 KB block it touches are read before and after the write. A write that fails partway says flash may already have changed. On a part that boots only signed images, its next step says to sign the image first.
85
+ - `read_fault_status` is `unverified` for a clean read beside a held GDB server that caught a vector, since a lockup the server reset looks clean. MMFAR and BFAR appear only when CFSR marks them valid.
86
+ - `run_until` refuses to watch a register whose read changes state, since GDB reads a watched address when it sets the watchpoint.
87
+ - Result fields renamed so one fact has one name across the tools: `halted_during_call` and `core_after` replace `target_halted`, `halted_to_read_registers`, `halted_to_read_pc`, `core_released`, `core_after_release` and the prose notes about the core; `capture_console` returns `console`, `console_port` and `console_port_source` as `run_firmware` does, and `capture_swo` returns `swo`; `run_firmware` echoes `elf_path` and `flash_firmware` echoes `image_path`; `flash_firmware`'s `detail` is `programmer_output`; `read_fault_status`'s `debug_halt_reason` is `debug_events_since_cleared`; `locate_fault` gives a place in the source as function, file, line and address, as `run_until` does; a register's `fields` are numbers, with ST's explanations in `field_meanings`. `run_until` reports `target_was_reset`.
88
+ - `core_registers` gives the same registers whichever way it reads them, and `core_registers` and `locate_fault` say from DHCSR whether the core runs afterwards.
89
+ - A call stack names a file as addr2line does, "src/main.c" rather than "main.c".
90
+ - Result text is cut to what changes what the agent does: `board_state` gives each family a short `note`, and the notes on halts, watchpoints, trace, buffered console text, read effects and fault registers are a sentence or two. The connect-failure hint names the likely causes and what to do about each.
91
+ - The tool descriptions say that `reset_target` cannot get past a debug port the running program closed and `board_state` can, and that the default `keep_probe` resets a core that locks up.
92
+ - A `STLINK_MCP_CLT_ROOT` that names no complete install stops start-up with the reason, as the other settings do.
93
+ - On an STM32N6 in flash boot, `flash_firmware` refuses before ST's flash loader runs, since that loader then hangs the core until a power cycle.
94
+ - The STM32N6 entry no longer says it boots only signed images: an image with ST's header and no keys boots on a part whose secure-boot fuse is not blown. `flash_firmware`'s next step and `board_state`'s note say how to add that header and how to open debug in the image; the connect-failure and reset hints say that a flash-booted N6 stays closed unless its image opens debug.
95
+ - `list_probes` returns the programmer's errors beside the probes it listed, since a probe this user cannot open prints an error and no entry.
96
+
97
+ ### Fixed
98
+
99
+ - `locate_fault` connected with resets allowed, and a reset loses the fault it looks for; it now never resets and refuses instead.
100
+ - `sample_values` and `disassemble` raised on hex digits without their `0x` prefix, such as "08000000", and returned no status; they now refuse them, as `read_memory` does.
101
+ - `run_firmware` connected, possibly resetting the board, before finding that the ELF did not exist; it now checks first, and its refusals after the connect say whether the board was reset.
102
+ - A failed `capture_swo` did not say that the trace output may have been left on.
103
+ - A relative `elf_path` or `image_path` was looked up from the server's own working directory, which the README's client set-up makes the server's checkout, and failed as a missing file; it is now refused, naming that directory. `~` is expanded in every tool, where some did not. `board_state` given a missing ELF returned program-counter places with no source and no error; it now refuses before connecting.
104
+ - A failed `run_firmware` did not say that the GDB server it started had reset and halted the target, and `run_until`'s refusals after attaching did not say that GDB had halted the core.
105
+ - An exception a tool did not handle, such as the programmer timing out on a hung probe in `board_state`, reached the agent with no status; it is now a `failed` result that says whether the target was touched is not known.
106
+ - A probe serial typed in lower case was not matched to the server holding that probe, so another call could reset beside it. Serials are now taken in upper case, as the programmer prints them.
107
+ - A GDB server that failed to start, such as on a target that would not connect, left the folder for its log in the temporary directory. It is now removed.
108
+ - `release_probe` read the core's state over SWD after a run over JTAG; it now uses the interface of the server it released.
109
+ - A run that could not be sampled because the GDB server keeps the probe to itself now says why, and that `keep_probe=False` would let it be sampled.
110
+ - `read_memory` read an ADC's common data register without a word, though on an STM32H743 reading it cleared EOC as reading the ADC's own data register does. CDR is now on the read-effects list.
111
+ - Tools that halt the core to read it leave it as they found it. In shared mode a halt made through the programmer outlived the command, so `locate_fault` left a running core halted; and `core_registers` through GDB resumed a core it found halted.
112
+ - A board released while ST's `stlink-server` ran could not be reached again: the daemon kept the probe open, a plain connect failed with DEV_CONNECT_ERR, which the parser dropped, and `list_probes` showed the board with no firmware. DEV_CONNECT_ERR is now reported and explained.
113
+ - Reads that returned wrong data with no error were reported `verified`: on an STM32H743 whose core slept on WFI, every address read 0x01010001. Identification and every memory read now also read the core's CPUID register, and fail when its top byte is not Arm's 0x41.
114
+ - Texts an agent reads that had gone out of date: which tools refuse beside a server that keeps the probe to itself, that tool calls run one at a time (calls on different probes run together), and that `core_registers` cannot reach a held probe.
115
+
116
+ ## [0.7.0] - 2026-10-03
117
+
118
+ Run on an STM32H7, an STM32N6 and an STM32F407 board on 2026-10-03: `tools/hwcheck` passed 80 of 80 checks with `--f4`, `--f4-console`, `--jtag` and `--flash`, and 74 of 74 without `--flash` on the release commit. On a Windows 11 laptop with an STM32N6 board, `tools/hwcheck --n6` passed 14 of 14. docs/findings.md has what was run and what each README promise rests on.
119
+
120
+ ### Changed
121
+
122
+ - Without udev's `/dev/serial/by-id`, a named probe's console is found by the USB serial number pyserial reports for each port, instead of by listing every probe. On Windows that found COM3 for an STLINK-V3 on the first try.
123
+ - On Windows the toolchain's executables are looked for with `.exe`, and `C:\ST` is searched for an install, where STM32CubeCLT's installer puts it. A probe's console is reported as `COM3` rather than the `\\.\COM3` the programmer prints.
124
+
125
+ ### Added
126
+
127
+ - `tools/hwcheck` checks that `run_until` refuses a wide watch and stops verified on a write by the caller, on the H7 and the F407, and with `--jtag` that it stops at a function and after a write over JTAG, both verified, and refuses SWD while that server holds the probe.
128
+ - Windows 11 measured with a NUCLEO-N657X0-Q: every check in `tools/hwcheck --n6` passed, 14 of 14, a run verified by its console text over COM3 among them. `tools/hwcheck` builds its images on Windows, removes them at the end unless `--keep-images`, and its N6 sequence now also reads by name and takes the call stack at a stop. Its new `n6_print` image prints on the N6 board's virtual COM port, so a run on the N6 is verified by console.
129
+
130
+ ### Fixed
131
+
132
+ - `run_until` refuses a watch that is not 1, 2 or 4 bytes at an address aligned to its size. ST's GDB server accepted a 36-byte one, watched only its first byte, and the run reported no write while the program wrote the rest on every tick; a 2-byte one at an odd address was accepted and never fired.
133
+ - `run_until` fails at once, with GDB's reason, when GDB cannot insert a breakpoint, as at an address where nothing answers. It read GDB's `^running` as a run, waited out the time limit and an interrupt, and reported a target that did not stop, which had never run.
134
+ - A watchpoint result's `called_just_before` is now `returned_from`, is left out when the core is still inside that call, and is described as a candidate for the writer only. It had named a function that wrote nothing, on the H7 and the F407, whenever the caller made the write.
135
+ - An unexpected GDB error part-way through `run_until` left the core halted and the server the call had started holding the probe, with a failure that said neither. It now detaches, which resumes the core, stops that server, and says so in `core_after`.
136
+ - `run_until` and `core_registers` no longer reuse a GDB server held over the other interface, which they did without a word for a call that asked for SWD while a JTAG server held the probe; they refuse, as `run_firmware` already did. Their results now report the interface.
137
+ - `run_firmware` reports `target_was_reset`: true once its GDB batch has run, since the batch resets the target before it loads, and null with a note when the batch stopped early. Its docstring said so; its result did not.
138
+ - Texts an agent reads no longer send it to `release_probe` before `core_registers`, which works through a held GDB server, nor to `run_firmware`'s server to hold a core stopped, which is `run_until`'s job. On an STM32N6 a needless release can close the debug port until a power cycle. `release_probe` now names the tools that do need it, as measured on the H7 and the N6.
139
+ - Every tool's description is within the 80 words the repo sets, checked as an MCP client sees it; `capture_console` and `capture_swo` were over.
140
+ - A breakpoint's kind reads `software` rather than `software, in RAM`: ST's GDB server describes everything but flash as read-write, ROM included.
141
+ - A register named after its peripheral's type, as the STM32N657 file names USART1's (`USART_BRR`), is found by `USART1.BRR`, and one the file gives in two forms (`USART_ISR_FIFO_ENABLED`, `USART_ISR_FIFO_DISABLED`) is refused with both named, where it was "no register".
142
+ - Tests that assumed POSIX path separators, symlinks or chmod now pass or skip on Windows, and `.gitattributes` keeps git from rewriting the line endings of captured fixtures on checkout.
143
+
144
+ ## [0.6.0] - 2026-10-02
145
+
146
+ Run on an STM32H7, an STM32N6 and an STM32F407 board on 2026-10-02: `tools/hwcheck` passed 68 of 68 checks with `--f4`, `--f4-console`, `--jtag` and `--flash`. docs/findings.md has what was run and what each README promise rests on.
147
+
148
+ ### Added
149
+
150
+ - `read_memory` reads a variable by name, decoded with the ELF's types, and `verified` only when the target runs that ELF; and a peripheral register by name, decoded into its fields from ST's SVD files, with the file chosen from the board name or the device name and used only where every candidate file agrees. Measured on an STM32H7 and an STM32F407.
151
+ - `pyelftools` is a dependency, for reading types from an ELF's DWARF.
152
+ - `run_until`, which runs the target until it reaches a location or writes a variable and holds the core there, with the call stack, `verified` only on the Debug Fault Status Register the GDB server read at the stop. Measured on an STM32H7, an STM32F407 and an STM32N6.
153
+ - `core_registers` takes `elf_path` and returns the call stack with each frame's arguments and locals, through GDB, and works while `run_until` holds the core.
154
+ - `release_probe` reads DHCSR after releasing a core `run_until` held, and says whether it runs again.
155
+ - `tools/hwcheck` reads a variable and a register by name on the H7 and the F407, and checks `run_until`, the call stack and the resume on all three boards.
156
+
157
+ ## [0.5.0] - 2026-10-02
158
+
159
+ Run on an STM32H7, an STM32N6 and an STM32F407 board on 2026-10-02: `tools/hwcheck` passed 48 of 48 checks with `--f4`, `--f4-console`, `--jtag`, `--flash` and `--n6-closed`. docs/findings.md has what was run and what each README promise rests on.
160
+
161
+ ### Added
162
+
163
+ - `capture_swo`, which captures what the target writes to an ITM stimulus port over the SWO pin, through STM32_Programmer_CLI's own SWO capture. It takes the core clock, is `unverified` when nothing arrives, notes unreadable text, and says in every result that the target's trace output was left on. Measured on an STM32H7 and an STM32F407.
164
+ - `tools/hwcheck` checks SWO on the H7 and the F407, and with `--n6-closed` closes the N6's debug port the way ST's start-up code does and checks what the tools say to it.
165
+
166
+ ### Changed
167
+
168
+ - A read that connects and then fails names its two measured causes: a range past the end of flash or RAM, and a debug access port that has closed, as an STM32N6 image's start-up code does. A GDB server that cannot attach no longer leaves its "check power and cabling" as the only explanation.
169
+ - A `flash_firmware` that fails because the programmer could not erase now says first that write protection in the option bytes fails this way, and how to show it. Measured on an STM32F407 with one sector protected.
170
+
171
+ ## [0.4.0] - 2026-10-02
172
+
173
+ Run on an STM32H7, an STM32N6 and an STM32F407 board on 2026-10-02: `tools/hwcheck` passed 42 of 42 checks with `--f4`, `--f4-console`, `--jtag` and `--flash`. docs/findings.md has what was run and what each README promise rests on.
174
+
175
+ ### Added
176
+
177
+ - STM32F4 (device 0x413) is a measured family: run on an STM32F407IGT6 board through an ST-LINK/V2, RAM run, faults, flash written and read back, reset to it.
178
+ - `tools/hwcheck` checks an STM32F407 board named with `--f4`, by its console on UART5 when `--f4-console` names an adapter, and prints every result since the previous check when one fails. A board whose first call fails gets one failure, not one per check.
179
+
180
+ ### Fixed
181
+
182
+ - `run_firmware` no longer tells an unverified run on an unmeasured address that "the run itself IS verified"; the note now claims nothing about the run. Found on the first seeded part run on a board, an STM32F407.
183
+ - A connect that fails with "Serial number not found" no longer says the probe is absent: an ST-LINK/V2 too old for STM32CubeProgrammer fails the same way, and the hint now names STLinkUpgrade.
184
+ - `capture_console` returned `verified` with empty output when nothing arrived, which reads as a silent target. Silence is now `unverified`, naming the causes it cannot tell apart, a missing wire among them.
185
+
186
+ ## [0.3.0] - 2026-10-01
187
+
188
+ Run on an STM32H7 and an STM32N6 board on 2026-10-01: `tools/hwcheck` passed 33 of 33 checks with `--jtag` and `--flash`. docs/findings.md has what was run and what each README promise rests on.
189
+
190
+ ### Added
191
+
192
+ - `py.typed`, so type checkers read the package's annotations.
193
+ - A caller for every tool call, from the MCP context: one trace line per call on stderr, and `held_by` on refusals and releases naming the client whose run holds a probe.
194
+ - `tools/hwcheck`, which runs the README's promises against a NUCLEO-H743ZI and a NUCLEO-N657X0-Q through the server and prints PASS or FAIL for each, with the test images it builds from source.
195
+
196
+ ### Fixed
197
+
198
+ - The warning that an access port was never measured now rides on every tool that connects and on their failures, not only on `board_state`, and is no longer skipped when a probe's remembered port differs from the assumed one.
199
+ - The newest complete STM32CubeCLT install is used, not the newest one with a programmer; `--check` names a newer install it passed over, and the error for an incomplete one names the others.
200
+ - `list_probes` is `unverified`, with the programmer's error lines, when it printed errors and listed no probe; an empty bus stays verified and points at `lsusb`.
201
+
202
+ ### Changed
203
+
204
+ - `read_memory` and `read_fault_status` connect and read in one programmer call when the probe's remembered port is its family's measured port: an STM32N6 read takes 1.15 s against 2.3.
205
+
206
+ ## [0.2.0] - 2026-10-01
207
+
208
+ These changes were run on an STM32H7 and an STM32N6 board on 2026-10-01; docs/findings.md has what was run and what it showed.
209
+
210
+ ### Fixed
211
+
212
+ - `board_state` reports `board` as null for a probe with no board of its own, as `list_probes` does, where it passed on the banner's `--`.
213
+ - A GDB server that starts but never opens its port is reported with its own output.
214
+ - `run_firmware` no longer counts console output the probe kept while its serial port was closed. That output comes from whatever ran before, and it verified a run of an image that printed nothing. `run_firmware` and `capture_console` report it apart, as `console_buffered`, and never as evidence.
215
+ - `core_registers` no longer reports `"halted": true` or says the core is still halted; a programmer halt does not outlive its command. It reports `halted_to_read_registers`.
216
+ - `target_was_reset` is set when a resetting connect is issued, whether or not it connects, and rides on the failures of `board_state`, `run_firmware`, `flash_firmware` and `core_registers`. The check of an unmeasured access port no longer sends a second reset pulse.
217
+ - `read_fault_status` refuses Cortex-M0, M0+, M1 and M23 cores, which have no fault status registers, and refuses a partial read; a clean read on an unrecognised core is `unverified`. Its next step names the stack the exception frame is on.
218
+ - `run_firmware` refuses an image with any loaded segment outside the SRAM region, not only one whose vector table is elsewhere, since GDB would program such a segment into flash.
219
+ - `run_firmware` keeps GDB's and the GDB server's output when a load fails, says when a failure leaves the probe held, and returns a result instead of raising when GDB ports run out.
220
+ - Tool calls issued together no longer race for GDB ports, servers or serial ports.
221
+ - An unusable `STLINK_MCP_GDB_PORT` or `STLINK_MCP_CONSOLE_BAUD` stops start-up with one line and exit 2 instead of a traceback.
222
+ - `capture_console` keeps what arrived when the port drops mid-capture.
223
+ - A source tree without git metadata, such as a GitHub ZIP download, installs, as version `0.0.0+unknown`. `CHANGELOG.md` is in the sdist.
224
+
225
+ ### Changed
226
+
227
+ - Tool calls on different probes run at the same time; calls on one probe wait for each other, and a call that lists the probes runs alone. A call that names its probe no longer lists them, and finds its console by the probe's serial in `/dev/serial/by-id` on Linux. `probe_serial` must be hex digits, and every answer is checked to come from the probe named.
228
+ - `flash_firmware` reads the written range back and compares it with the image. Only an identical readback is `verified`; a difference is `failed` with the first differing address, and a readback that fails is `unverified`.
229
+ - `flash_firmware` takes raw binaries (`.bin`) only, and checks the file before touching the probe.
230
+ - `STLINK_MCP_CONSOLE` takes `SERIAL=path` pairs, which win over the probe list; a bare path applies only while one probe is attached. `run_firmware` now honours it, and results report `console_port_source`.
231
+ - `read_memory` and `flash_firmware` take an address as a hex string as well as an integer. `read_memory` requires aligned reads of at most 1024 bytes and fails a short read. Console captures are limited to 60 seconds.
232
+ - `mcp` is capped below 3. `__version__` comes from the installed metadata, and the server reports it in the MCP handshake.
233
+
234
+ ### Added
235
+
236
+ - `console_garbled_note` on `run_firmware` and `capture_console` when most of the output is not text, which is what a wrong baud rate looks like.
237
+ - `locate_fault`, which finds the instruction that faulted from the exception frame: the stack from EXC_RETURN, the stacked PC and LR, and with the ELF their functions and lines.
238
+ - `interface`, `swd` or `jtag`, on every tool that connects. Results report the interface and the clock from the probe's connect banner, and a failed JTAG connect says the probe may not do JTAG.
239
+ - `stlink-mcp --check`, `--version` and `--help`.
240
+ - CI for the suite on Python 3.11 to 3.13 and for the sdist contents, and an end-to-end test over stdio.
241
+ - `CONTRIBUTING.md` and issue templates.
242
+
243
+ ### Removed
244
+
245
+ - The fallback to the first `/dev/ttyACM*` when no probe listing was available.
246
+
247
+ ## [0.1.0] - 2026-09-06
248
+
249
+ First tagged version. Alpha: any tool signature or result field may still change.
250
+
251
+ ### Added
252
+
253
+ - Ten MCP tools: `board_state`, `run_firmware`, `flash_firmware`, `read_memory`, `core_registers`, `read_fault_status`, `capture_console`, `reset_target`, `release_probe`, `list_probes`. Every tool verifies its own effect before reporting success.
254
+ - Two device families measured on hardware, STM32N6 and STM32H7. A further 99 device IDs are seeded from STMicroelectronics' device databases, each value labelled with that provenance; four device IDs whose databases name several SRAM banks are refused rather than guessed until a board file settles them.
255
+ - Board files for describing a board the server does not know.
256
+ - Linux only, MIT licence.
257
+
258
+ [Unreleased]: https://github.com/dii/stlink-mcp/compare/v0.8.5...HEAD
259
+ [0.8.5]: https://github.com/dii/stlink-mcp/compare/v0.8.4...v0.8.5
260
+ [0.8.4]: https://github.com/dii/stlink-mcp/compare/v0.8.3...v0.8.4
261
+ [0.8.3]: https://github.com/dii/stlink-mcp/compare/v0.8.2...v0.8.3
262
+ [0.8.2]: https://github.com/dii/stlink-mcp/compare/v0.8.1...v0.8.2
263
+ [0.8.1]: https://github.com/dii/stlink-mcp/compare/v0.8.0...v0.8.1
264
+ [0.8.0]: https://github.com/dii/stlink-mcp/compare/v0.7.0...v0.8.0
265
+ [0.7.0]: https://github.com/dii/stlink-mcp/compare/v0.6.0...v0.7.0
266
+ [0.6.0]: https://github.com/dii/stlink-mcp/compare/v0.5.0...v0.6.0
267
+ [0.5.0]: https://github.com/dii/stlink-mcp/compare/v0.4.0...v0.5.0
268
+ [0.4.0]: https://github.com/dii/stlink-mcp/compare/v0.3.0...v0.4.0
269
+ [0.3.0]: https://github.com/dii/stlink-mcp/compare/v0.2.0...v0.3.0
270
+ [0.2.0]: https://github.com/dii/stlink-mcp/compare/v0.1.0...v0.2.0
271
+ [0.1.0]: https://github.com/dii/stlink-mcp/releases/tag/v0.1.0
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,45 @@
1
+ # Contributing
2
+
3
+ This file says how to run the tests and the hardware check, what makes a report useful, and how to contribute a board. The full rules for changing the code are in [AGENTS.md](AGENTS.md), written for the agents that work in this repository; the ones a contributor needs are below.
4
+
5
+ ## Running the tests
6
+
7
+ ```sh
8
+ uv sync
9
+ uv run pytest
10
+ ```
11
+
12
+ No hardware is needed. The tests that read a real STM32CubeCLT install skip when there is none, and everything else fakes the probe. Each test names the need it checks, from [tests/NEEDS.md](tests/NEEDS.md). Parser tests run on output captured from the real tools, never on output written by hand.
13
+
14
+ ## Running the hardware check
15
+
16
+ `tools/hwcheck` runs the README's promises through the server against a NUCLEO-H743ZI2, a NUCLEO-N657X0-Q or an STM32F407 board (`--f4 <serial>`), and prints what held; see [tools/hwcheck/README.md](tools/hwcheck/README.md). It changes the boards: it resets them and runs test images on them, which on the H7 make the SWD pins inputs and start the RTC from the LSI, and its SWO checks leave trace output on until a power cycle. `--flash` rewrites flash, saving it first and writing it back. Read that README's "What it changes on the boards" before you run it.
17
+
18
+ If you change something that talks to a board, say in the pull request what you ran, on which board, probe firmware and STM32CubeCLT version, and what it showed. If you have not run it on hardware, say so; that is a fact about the change, not an apology.
19
+
20
+ ## Reporting a problem
21
+
22
+ Open an issue with:
23
+
24
+ - the output of `uv run stlink-mcp --check`
25
+ - the tool that was called, its arguments, and the full result it returned
26
+ - the board, the probe generation (ST-LINK/V2, V2-1, V3) and its firmware
27
+ - the STM32CubeCLT version and the operating system
28
+
29
+ If the problem is in how probes are listed, paste the output of `STM32_Programmer_CLI -l` exactly as printed. Every parser here is tested against captured output, and only on-board ST-LINK V3s and one ST-LINK/V2's listing have been captured so far, so a listing from any other probe, a V2-1 especially, is useful even when nothing is wrong.
30
+
31
+ ## Contributing a board
32
+
33
+ A value is `measured` when someone ran it on the part and watched it work, and only measured values go into `FAMILIES` in `src/stlink_mcp/quirks.py`; a test fails the build otherwise. The table is keyed on the device ID the probe reports, which is coarser than a part number, so put in only values that hold for everything behind the ID, and keep anything that varies by board or by image out of it. For a part this project has not measured:
34
+
35
+ 1. Describe it in a board file first (see the README). Everything there is `user-stated`, and the tools will use it.
36
+ 2. `access_port`: a connect that answers is not enough, since a port can answer identity and still not halt the core. `core_registers`, which halts the core, working on that port is.
37
+ 3. `ram_load_base` and `vector_table_addr`: run an image linked to load at `ram_load_base`, with its vector table at `vector_table_addr`, with `run_firmware`, and confirm it started, by console output from the new image, or with `sample_values` and `until "<counter> changes"` on a counter it increments.
38
+ 4. `flash_base`: write a .bin there with `flash_firmware` and `address`, see it `verified`, then `reset_target` and see the core halt at that image's reset handler.
39
+ 5. Open an issue or pull request with the device ID, the board, each value and how you established it. A value you took from a reference manual and did not run is still worth sending, labelled as such.
40
+
41
+ Never include files from STM32CubeProgrammer's `Data_Base` directory, or a table made from them. They are under ST's licence (SLA0048), which forbids putting them under open-source terms. Quoting one address is fine.
42
+
43
+ ## Where text goes
44
+
45
+ `README.md` is for people using the server and carries conclusions only. Measurements, dates, and what was got wrong go in [docs/findings.md](docs/findings.md). Tool descriptions are read by the calling agent on every session, so each stays between 40 and 80 words.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dawid Oosthuizen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.