keil-uv4-mcp
by hyl64
README.md
# Keil uVision (UV4) → MCP — full-surface reconstruction
`keil-uv4-mcp` is a dependency-free Model Context Protocol server (stdio, JSON-RPC 2.0)
that exposes the complete functional surface of Keil µVision 5 (MDK 5.43a, MDK-Lite
non-commercial) as reconstructed from the shipped binaries, the shipped help and
controlled runtime probing.
Target installation analysed:
`C:\Users\ERDAN.DESKTOP-MVAKHCO\AppData\Local\Keil_v5\UV4`
(`UV4.exe` sha256 `428baf13d15e6760af1618def9c9815c97f0321cc5e459ec7adc4dde41c42f89`, x86 MFC,
41 876 functions / 18 794 strings after auto-analysis).
**93 MCP tools · 106/106 acceptance · 10/10 adversarial · 48/48 portable · 37/37 licence-free compile · 44/44 beginner diagnostics · 68/68 release gate.**
```
UV4.exe ──CLI/exit codes──▶ uv4cli.mjs build / rebuild / clean / download / convert / export / symbols
──XML model──────▶ project.mjs .uvprojx / .uvproj / .uvmpw / .uvoptx (byte-faithful)
──.dep & .d──────▶ deps.mjs UV4 dependency database, incremental impact, stamps
──device DB──────▶ packs.mjs CMSIS-Pack .pdsc devices, components, flash algorithms
──toolchain──────▶ toolchain.mjs armcc / armasm / armclang / armlink / fromelf command lines
──images─────────▶ image.mjs .axf ELF, linker .map, Intel-HEX
──batch──────────▶ batchgen.mjs <Target>.BAT + .__i / ._ia / .lnp via files
──debugger───────▶ gdbstub.mjs SVD → register/bitfield description, peripheral map
──license────────▶ license.mjs armlm / armlm-ipc / FlexNet inventory, #601 build gate
──remote control─▶ uvscanner.mjs UVSOCK probe, UV4 process control
```
## Round 2: exhaustive companion coverage
All 21 companion binaries are profiled in [reverse/modules.md](reverse/modules.md): UVSC.dll/UVSC64
(103-export UVSOCK client library -> full wire protocol decoded, re-implemented in
`src/uvsock.mjs`, and verified live end-to-end: V2.29 handshake, remote rebuild with event
streaming), UVCDB2XML (2831-device legacy .cdb), SfrCC2, SVDConv 3.3.48,
uv2csolution 1.6.0 (Go; AC6-only guard verified), PackInstaller/PackUnzip plus a native
.pack ZIP reader, Scintilla *.prop / UTF-16 *.SVCS config formats, armlm.exe +
armlm-ipc.exe (Go 1.24.5 wrapping FlexNet Embedded; activation via
https://api.arm.com/p-software-licensing/v1/activate, store ~/.armlm), KSPAWN
console spawner, DCOMUTIL (UV4.dll) OLE/DDE registration strings, UvCC.dll (LLVM AC6
integration layer), UVAC code-completion engine.
New tools: uvsock_connect / uvsock_request / uvsock_disconnect / uvsock_service_table,
cdb_devices / cdb_vendors, editor_props, svcs_menus, pack_file_inspect /
pack_file_extract, companion_run, license_architecture.
## Quick start
```powershell
cd keil-uv4-mcp
node src/server.mjs # MCP server on stdio
node tests/run_tests.mjs # 106 acceptance checks (builds real projects with UV4)
node tests/fuzz_test.mjs # 10 adversarial checks
node tests/pack_test.mjs # 68 release-gate checks (npm pack -> unpack -> cold start -> real build)
npm run test:all # all six suites (313 checks)
```
No Keil installation and no `npm install` are required: `dependencies: {}` and the data the
server needs ships in `data/fixtures/`. Copy the directory (or `npm pack` / `npm i -g`) onto
any machine with Node >= 20 and start it.
```bash
npm pack # keil-uv4-mcp-<version>.tgz (source + fixture snapshot)
npm i -g ./keil-uv4-mcp-*.tgz && keil-uv4-mcp
```
## Start here (three commands)
You do not need to know any Keil error code.
```jsonc
{"tool":"doctor"} // is this machine ready?
{"tool":"pack_diagnose","project":"your.uvprojx"} // why won't it build, and how to fix it
{"tool":"compile","project":"your.uvprojx"} // build without any licence
{"tool":"flash","project":"your.uvprojx","chip":"STM32F103C8"} // dry run first, then dryRun:false
```
`pack_diagnose` answers in plain language and returns copy-paste fixes:
```
summary: 构建会被拒绝:工程写死要 STM32F1xx_DFP 2.4.0,这台机器装的是 2.4.1。
actions:
- 装回工程要求的 STM32F1xx_DFP 2.4.0 -> pack_fetch {...}
- 把工程的 PackID 改成已装的 ...2.4.1 -> project_set_option {...}
```
## Licence-free build and flash
A µVision project can be compiled and programmed **without an MDK licence, without
activation and without UV4.exe**. Nothing on this path calls armlm or checks Error #601.
| capability | tool | backend | licence |
| --- | --- | --- | --- |
| compile | `compile` | clang+lld or arm-none-eabi-gcc | none |
| flash | `flash` | pyOCD / OpenOCD / STM32_Programmer_CLI / ST-LINK_CLI / J-Link | none |
| probe | `build_backends` | reports what is installed | — |
* The linker script is generated from the project's own `Cpu:` string (`IROM(...) IRAM(...)`);
when the project omits CMSIS, `CMSIS/Core/Include` is resolved from the pack roots or the
bundled snapshot automatically.
* Outputs are `.elf` + `.hex` + `.bin` + `.map`, and the server's own `elf_info`/`hex_info`/
`map_info` re-validate them (tests assert the vector table: initial SP = top of IRAM,
Reset vector in flash with the Thumb bit).
* **No byte-parity claim with armcc**: every result carries `backend` and `fidelity`. Use
`uv4_build` only when armcc/armclang behaviour is required (that needs a real install).
* Both paths coexist: `uv4_build` drives UV4 when present, `compile` never does.
The underlying algorithms (`.dep` stamps, via files, UVSOCK frames, device-fragment assembly)
are documented with function addresses in [docs/algorithms.md](docs/algorithms.md).
Client registration (any stdio MCP client):
```json
{ "mcpServers": { "keil-uv4": {
"command": "node",
"args": ["C:\\path\\to\\keil-uv4-mcp\\src\\server.mjs"]
} } }
```
| environment | default | meaning |
| --- | --- | --- |
| `KEIL_MDK_ROOT` | `%LOCALAPPDATA%\Keil_v5`, `C:\Keil_v5` | installation root (TOOLS.INI owner) |
| `KEIL_UVSOCK_PORT` | `4328` | default UVSOCK port for `uvsock_probe` |
| `UV4_TIMEOUT_MS` | `900000` | default per-invocation timeout; the process tree is killed on expiry |
## Recovered contract (evidence first)
### Command line — `UV4.exe [command] [projectfile] [options]`
| command | effect | verified |
| --- | --- | --- |
| `-b` | build current target (batch-selected targets for `.uvmpw`) | exit 0/1/2 |
| `-r` | re-translate current target | exit 0/1/2 |
| `-c` | clean all targets ("deleting intermediate output files…") | exit 0 |
| `-cr` | clean + rebuild | exit 0 |
| `-z` / `-q` | rebuild all / batch-selected targets of a **multi-project** | hangs on a single project (documented; refused unless `force`) |
| `-f` | download to Flash | requires a configured adapter |
| `-5` | convert `.uvproj` → `.uvprojx` (+ `.saved_uv4` backup) | exit 0, log "Migration completed" |
| `-et` / `-ep` | export `.cprj` (target / all) | exit 0 |
| `-X` / `-X1` | `co-arm_<target>.h` symbol files | file produced |
Options: `-j0` (headless, mandatory for automation), `-o` log, `-t` target, `-s` UVSOCK
port, `-sg` ignore `uvguix`, `-i` import XML, `-n`/`-np` create device project,
`-l` logfile (with `-5`), `-x`/`-y` DDE (with `-d`).
ERRORLEVEL: 0 none, 1 warnings, 2 errors, 3 fatal, 11 cannot write project,
12 unknown device, 13 write error, 15 import XML error, 20 conversion error.
Multi-project facts discovered by probing: the workspace is `<ProjectWorkspace>` with
`<project><PathAndName/>` members (`UV4\project_mpw.xsd`), and Batch Build selection is
the **per-target** `<SelectedForBatchBuild>` inside each member — UV4 -b reports
`Batch-Build summary: N succeeded, M failed, K skipped`.
### Project / dependency formats
* `.uvprojx` (`project_projx.xsd`) and `.uvoptx` (`project_optx.xsd`) are read **and
written byte-faithfully**: an untouched file round-trips identically, edits rewrite
only the affected subtree (`node src/xml.mjs` round-trip test in the suite).
* `<Output>\<project>_<target>.dep` writer is `FUN_00680b10`; the `(0x%08X)` column is
the dependency file's **clock time written as decimal HHMMSS and printed in hex**
(`main.c` 15:52:13 → `0x00155213`); `F` records hold the exact tool command
(`-IC:\…\Device\Include`, `-D__UVISION_VERSION="543"`, defines, `-o`, `--depend`),
`I` records the include closure produced by `armcc --depend`.
* Per-file depend files (`*.d`) are literally `"<obj>: <dep>\r\n"` lines emitted by the
compiler; UV4 and this server both parse that format.
* Batch build artefacts (`Create Batch File`): `<Target>.BAT` with `SET PATH/ARMCC5_*OPT/
CPU_TYPE/CPU_VENDOR/UV2_TARGET/CPU_CLOCK`, per-file `ArmAsm --Via .\objects\x._ia` /
`ArmCC --Via .\objects\x.__i`, `ArmLink --Via .\Objects\out.lnp`, then `fromelf`.
### Device data
`pack_devices`, `pack_device_detail`, `pack_components`, `pack_conditions` and
`pack_rte_validate` parse the installed `.pdsc` (also `UV4\PACK.xsd` layout). The
generated `Cpu:` string, `FlashDriverDll` (`UL2CM3(… -FS08000000 -FL020000 …)`) and
`SFDFile`/`RegisterFile` are **byte-identical to UV4's own `-5` migration output**
(asserted in the acceptance suite).
## Tool catalogue (93)
| group | tools |
| --- | --- |
| CLI reproduction | `uv4_build`, `uv4_rebuild`, `uv4_clean`, `uv4_clean_rebuild`, `uv4_rebuild_all`, `uv4_download`, `uv4_symbols`, `uv4_export_cprj`, `uv4_convert`, `uv4_import`, `uv4_run`, `uv4_job_status`, `uv4_job_list`, `uv4_job_wait`, `uv4_build_log`, `uv4_processes` |
| project model | `project_read`, `project_files`, `project_multiproject`, `project_set_option`, `project_group_add`, `project_group_remove`, `project_file_add`, `project_file_remove`, `project_target_clone`, `project_target_remove`, `project_validate`, `project_diff`, `project_create`, `project_create_legacy`, `project_create_multi`, `project_multi_add`, `project_multi_read`, `project_batch_flags` |
| dependencies | `dep_read`, `dep_graph`, `dep_impact`, `dep_scan`, `dep_write`, `test_build` |
| packs | `pack_list`, `pack_devices`, `pack_device_detail`, `pack_components`, `pack_conditions`, `pack_rte_validate` |
| images | `elf_info`, `map_info`, `hex_info`, `memory_usage`, `symbols_file` |
| session options | `options_read`, `options_set`, `options_create` |
| scaffolding / batch | `batch_file`, `batch_generate`, `import_xml_document`, `import_xml_from_project` |
| debugger | `debug_script`, `debug_command_reference`, `debug_pack_generate`, `debug_peripheral_map` |
| installation | `keil_install`, `toolchain_versions`, `tool_run`, `license_status`, `uvsock_probe`, `cli_reference`, `cli_table` |
Generated short table: [tool_table.md](reverse/tool_table.md).
## Evidence kept under `reverse/`
| file | content |
| --- | --- |
| `cli_matrix.json` | every command probed, exit codes, `-z`/`-q` hang, timings |
| `dep_stamp_evidence.json` | `F/I` records, producer function `FUN_00680b10`, stamp semantics |
| `rte_injection_evidence.json` | implicit RTE include / `__UVISION_VERSION` / define injection, ordering |
| `batch_via_files_evidence.json` | the byte-exact `Flash.BAT`, `main.__i`, `startup._ia`, `main.lnp` |
| `uv4_profiles.json` | binary identity, CLI contract, side effects, UVSOCK / armlm artefacts |
| `acceptance_results.json` | latest acceptance run (106/106) |
| `samples/` | `Minimal` (verified build), `Gen2` (scaffolded, builds), `Legacy` (converted by UV4), `Multi*` (workspaces), `Scaffold`, `Import` |
| `hashprobe*.mjs`, `pdsc_probe*.mjs`, `stamp_decode*.mjs` | the probes used to pin the formats |
## Behaviour notes and limits
* UV4 is a GUI-subsystem binary: without `-j0` a shell waiting on stdio deadlocks.
Every invocation rewrites `<project>.uvoptx` (current target, debug options).
* `-z`/`-q` only exist for `.uvmpw`; the wrapper refuses them on single projects and
kills the process tree on timeout (`uv4_processes {kill:true}` recovers stale runs).
* RTE components are validated structurally (pack presence, class/group/sub match).
UV4 additionally applies `condition` rules; mismatches are reported as `#540`,
missing packs as `#544` — the same codes UV4 logs.
* Scaffolded projects use AC5-safe include sets: CMSIS 6.x ships `cmsis_compiler.h`
whose AC5 section ends in `#error Unknown compiler.` (verified at
`Core/Include/cmsis_compiler.h:279`), therefore `project_create` prefers CMSIS ≤ 5
or omits it and writes a self-contained `sysinit.c`. Passing `addSystem:true` copies
the DFP `system_*.c` when CMSIS 5 is installed.
* `armlm.exe` / `armlm-ipc.exe` / FlexNet daemons are treated as black boxes: the server
reports their files, running instances and the live `Error #601` build gate; it never
edits license data.
* The UVSOCK protocol (frames over the `-s` port) is probed and reported (reachability,
greeting bytes, listener inventory). Frame-level command encoding is not published by
Arm; the debugger command surface itself is fully exposed through `-d` scripts
(`debug_script`, `debug_command_reference`).
* `tool_run` never leaves the toolset `bin` directory (path traversal is rejected).
* The bundled DFP snapshot contains `.pdsc` declarations only — no device sources
(`Device/Include/*.h`, `startup_*.s`, `.FLM` payloads). Without Keil, `project_create`
still emits the exact `<Device>/<Vendor>/<PackID>/Cpu/FlashDriverDll` fragments, and
`project_validate` truthfully reports `NO_STARTUP` / `INCLUDE_MISSING`: those files really
are absent, and the validator is not allowed to pretend otherwise.
## Release and open source
### Why the release gate exists
A working checkout is not a working package. Two defects only appear after `npm pack`:
1. **Data read at import time from the development tree.** `module_catalog.json` lived in
`reverse/`, which is not in `files:`, so a published package died with `ENOENT` on startup.
It now ships at `data/fixtures/catalog/module_catalog.json`, is loaded lazily on first use,
and degrades to a structured error instead of taking the server down.
2. **Main-module detection by string comparison.** 8.3 short paths (`C:\Users\ERDAN~1.DES\…`),
symlinks and case differences made `isMain` false: the process imported cleanly and exited
silently. Detection now uses `realpathSync.native` with Windows case folding.
`tests/pack_test.mjs` runs `npm pack`, unpacks the tarball outside the repository, blanks
`LOCALAPPDATA`/`KEIL_MDK_ROOT` to simulate a machine without Keil, cold-starts the server and
asserts the device DB (2831 devices / 33 vendors), the three `.pdsc` packs, STM32F103C8's
64 KiB flash / 20 KiB RAM / `STM32F10X_MD` / `SVD/STM32F103xx.svd` / `STM32F10x_128.FLM`,
`module_catalog` resolving inside the package, `.axf`/`.map`/`.hex` parsing, `uv4_build`
returning `needsKeil`, and startup through an 8.3 short path.
### Repository layout
| directory | contents | shipped |
| --- | --- | --- |
| `src/` | all runtime code (22 modules + 14 tool registrars) | yes |
| `data/fixtures/` | runtime snapshot (134 files / 6.44 MB) | yes |
| `tests/` | 313 assertions + release gate | no |
| `reverse/` | decompilation scripts, evidence JSON, controlled-run logs | no |
### Licence and attribution
MIT (see `LICENSE`). The `.pdsc` files and the `UV4.cdb` dump under `data/fixtures/` remain the
property of their respective owners and are included for interoperability; review the licence
terms inside each `.pdsc` before commercial redistribution. This project contains no vendor
binary code.
### Contributing
Read `CONTRIBUTING.md` first. The governing rule is **fidelity to the original, decompilation
first**: every behaviour must point at a function address in `UV4.exe`/`UVSC.dll` or a
controlled-run record under `reverse/`. When the evidence is missing, return a structured
diagnostic — never a plausible-looking guess.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues