Files
Sprinter-SDCC/docs/sdbg-mcp.md
T
2026-09-17 23:32:46 +03:00

208 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MCP-доступ к source debugger Sprinter
`toolchain/sdbg_mcp.py` подключается к уже работающей `sdbg_server.py` через
Unix socket. Это отдельный stdio MCP-сервер для C-уровня: он использует ту же
сессию, карту сборки и журнал, что VS Code/DAP. В MAME.HT отдельно работает
raw MCP-мост `-plugin mamebridge` с `src/mame_mcp.py`. Старый
`src/mame_bridge.lua` удалён после проверки: он обрабатывал лишь 16 из 30
публикуемых Python-команд и не отвечал при остановленном CPU. Plugin использует
`register_periodic` и отвечает даже при остановленном CPU. Ни один из этих
вариантов не должен одновременно управлять тем же MAME в обход sdbg-сессии.
`src/mame_mcp.py` всё ещё импортирует `FastMCP` по пути SDK 1.x;
наш адаптер использует `MCPServer` SDK 2.x. Пока raw frontend не мигрирован,
держите для него отдельное Python-окружение. Это различие касается Python
обвязки, а не Lua-плагина `mamebridge`.
Для MCP нужен Python 3.12 с официальным SDK 2.x. Он не требуется для сборки
приложений и работы VS Code. Например, создайте отдельное окружение и
установите `toolchain/requirements-mcp.txt`:
```sh
pyenv exec python -m venv /tmp/sprinter-sdbg-mcp-venv
/tmp/sprinter-sdbg-mcp-venv/bin/python -m pip install -r toolchain/requirements-mcp.txt
```
В профиле `launch` типа `sprinter-mame` укажите фиксированный socket,
например `"socket": "/tmp/sprinter-sdbg-hello.sock"`. После F5 дождитесь
остановки в `main`. Путь должен быть коротким (у Unix socket есть лимит длины),
уникальным для этого сеанса и доступным только вашему пользователю. Затем
зарегистрируйте в MCP-клиенте stdio-сервер:
```json
{
"command": "/tmp/sprinter-sdbg-mcp-venv/bin/python",
"args": [
"/ABS/PATH/C-Compiler/toolchain/sdbg_mcp.py",
"--socket", "/tmp/sprinter-sdbg-hello.sock"
]
}
```
Замените `/ABS/PATH/C-Compiler` абсолютным путём к toolkit. MCP-сервер при
старте проверяет подключение; если VS Code ещё не запустил MAME или socket
устарел, он завершается с понятной ошибкой в stderr. После Stop запустите
MCP-сервер заново вместе с новой DAP-сессией. Альтернатива параметру
`--socket` — переменная окружения `SDBG_SOCKET`.
Для работы **без VS Code** запустите тот же MCP-сервер с `--build`:
```json
{
"command": "/tmp/sprinter-sdbg-mcp-venv/bin/python",
"args": [
"/ABS/PATH/C-Compiler/toolchain/sdbg_mcp.py",
"--build", "/ABS/PATH/C-Compiler/tests/hello/.sprinter-cc-hello",
"--socket", "/tmp/sprinter-hello-agent.sock",
"--mame-home", "/ABS/PATH/MAME/runtime",
"--mame-bin", "/ABS/PATH/MAME.HT/sprinter"
]
}
```
MCP handshake не ждёт загрузку DSS. `start_session` сразу возвращает socket и
launch ID; `session_status` показывает `starting`, затем `ready` с build ID,
session ID, PC и PID MAME либо `failed` с диагностикой. Повторный start при
активном сеансе отклоняется. `stop_session` завершает только запущенный этим
MCP-сервером MAME и удаляет socket. Закрытие самого MCP-процесса также
завершает его автономный MAME. DAP/VS Code может подключиться к тому же socket;
его disconnect не завершает MCP-owned MAME. Конфигурация VS Code — в
[разделе attach](vscode-sprinter-debug.md#ручной-attach).
Доступны также `--mame-rompath`, `--mame-dss-image`,
`--mame-system-hdd-image`, `--mame-bios`, `--app-hdd`, `--launch-path`,
`--data`, `--debugger`, `--launch-at`, `--dss-timeout`.
Живой stdio MCP→DSS→`main`→DAP attach→stop прогон прошёл; подключение
непосредственно из UI Codex и Claude ещё не проверено.
Доступные инструменты: `session_status`, `where`, `read_registers`,
`read_memory`, `read_program_memory`, `disassemble_logical`, `list_ports`,
`list_shares`, `read_share`, `read_vram`,
`read_screen_pixels`, `screenshot`, `press_key`, `type_string`,
`list_variables`, `read_variable`, `recent_events`,
`mame_console_tail`, `set_line_breakpoint`, `set_function_breakpoint`,
`list_breakpoints`, `clear_breakpoint`, `clear_my_breakpoints`,
`claim_control`, `release_control`,
`continue_execution`,
`pause_execution`, `step_instruction`, `step_over_instruction`,
`step_out_instruction`, `step_source`. Ответы SDK 2.x содержат
`structuredContent`. `step_source` принимает `into`, `over` или `out` и
возвращает принятие команды; итоговую остановку получите через
`recent_events` с курсором `last`. Логи `SDBG_LOG` приходят как события
`output`; журнал ограничен 1024 событиями и сообщает `first`/`last`/`lost`.
`step_instruction(count)` синхронно выполняет 1..64 машинных шагов
(`count=1` по умолчанию) и возвращает позицию остановки. Если раньше
сработает breakpoint, выполнение может остановиться до заданного числа.
`step_over_instruction(count=1..64)` и `step_out_instruction()` дают именно
машинную семантику MAME. Они отвечают сразу `accepted`; итоговую остановку
получайте через `recent_events` или `session_status`, при долгом вызове
доступен `pause_execution`. Например, `out` из `___sdcc_enter_ix` не
останавливается быстро: этот helper возвращает управление через `jp (hl)`,
а не через `ret`. Живой тест проверил `over` через такой вызов и `out` из
возвращаемого `_gettextmode`. Команды DAP F11/F10/Shift+F11 сохраняют
прежнюю C-семантику.
Число 31 в attach-режиме (33 в автономном с `start_session`/`stop_session`)
не означает полного переноса более нового `-plugin mamebridge`:
его `src/mame_mcp.py` публикует 30 raw-инструментов, ориентированных на
машину MAME. Здесь инструменты сгруппированы по операциям C-сессии, а
некоторые функции (C-позиция, typed global/static, журнал с generation)
в raw MCP вообще отсутствуют. Оставшиеся группы требуют отдельного контракта:
| Группа raw MCP | Что требуется перед переносом в общую сессию |
|---|---|
| `setmem`, raw `debugger_command`, watchpoints | Проверка прав, банка, диапазона и согласование с DAP-точками; произвольная debugger-команда может нарушить состояние сессии |
| VRAM/shares, screen pixels, screenshot | Реализованы через общий `sdbgbridge`; точный tag, пределы 4096 байт/8192 пикселя, PNG только в каталоге сессии |
| Клавиатура, мышь, type/press | `list_ports`, одиночный `press_key` и ограниченный `type_string` перенесены; natural keyboard, произвольные поля портов и мышь требуют отдельного контракта |
| Raw disassembly | Текущее окно logical Z80 уже доступно через `disassemble_logical`; адреса raw program вне отображённого банка требуют отдельного безопасного режима |
Это следующий этап, а не запрет на функции raw MCP. Нельзя просто загрузить
`mamebridge` рядом с `sdbgbridge`: два независимых обработчика начнут менять
CPU и точки без общего owner ID и журнала.
Результаты удаления legacy Lua и этапы переноса всех 30 возможностей в общую
C-сессию описаны в [плане сведения MCP](mcp-convergence-plan.md) и
[матрице покрытия](mcp-capability-matrix.md).
Чтение памяти принимает десятичный адрес или `0xHEX`, 1256 байт logical
Z80 memory, только при остановленном CPU, без side effects. Ответ содержит
generation и страницы банков; диапазон не может пересекать границу 64 КБ.
`read_variable` работает с проверенными global/static размером 1, 2 или 4
байта, только когда нужный банк отображён. Локальные, стек, watchpoints,
запись переменных и произвольные выражения пока не поддерживаются.
`read_program_memory` читает raw program space 0..0x3ffff без side effects,
до 4096 байт при остановленном CPU; это отдельный инструмент, так как
`read_memory` означает logical Z80. `list_ports` возвращает tag, имена полей
и битовые маски; результат ограничен 512 портами/4096 полями и помечает
`truncated`, если достигнут предел. Оба инструмента проверены в живом MAME.
`disassemble_logical` принимает числовой адрес (десятичный или `0xHEX`) и
длину 1..256 байт в текущем logical Z80 пространстве, только при stop.
Сервер сверяет загруженный C-код, вызывает фиксированную читающую команду
MAME `dasm` во временном каталоге сессии и возвращает текст, generation и
текущие `bank_pages`. Это не произвольная debugger-команда. Дизассемблирование
raw program вне текущих банков пока не поддержано. На живом `hello` ответ
начинался с адреса `8224`, соответствующего `main`.
`list_shares` возвращает точные tag/размеры. `read_share` требует полный tag,
`read_vram` выбирает единственный VRAM share; обе операции читают до 4096
байт без Z80 bank mapping. `read_screen_pixels` возвращает pen16-значения
в hex и номер кадра. `screenshot` сохраняет PNG до 8 МиБ в каталоге текущей
сессии и возвращает путь; после завершения сессии временный каталог удаляется.
Для обоих экранных инструментов `stale_frame=true` означает, что CPU
остановлен и показан последний нарисованный кадр. Экран работающего `hello`
во время `getchar()` проверен живым MCP-прогоном.
`press_key` принимает один символ раскладки PC или `enter`, `space`, `tab`
и `frames` от 1 до 60 (по умолчанию 3). Он доступен только при работающем
CPU, после запуска EXE; применяется общий control lease. Shift удерживается
автоматически для заглавных букв и соответствующих символов. После нужного
числа кадров или остановки CPU клавиша отпускается, включая путь ошибки.
Ответ сообщает число прошедших кадров и признак остановки. Живой автономный
MCP-прогон ввёл `x` в ожидающий `getchar()` и дошёл до следующей C-строки.
Прямой физический ввод в MAME проверялся отдельно; `press_key` не заменяет
его. `type_string` принимает 1..64 символа той же PC-раскладки, включая
реальный перевод строки `\n` для Enter. Все символы проверяются до первого
нажатия. Каждый символ удерживается три кадра и затем отпускается;
между символами выдерживаются четыре кадра. При stop дальнейший набор
прекращается, ответ сообщает `requested`, `typed`, `complete` и `stopped`.
Живой тест `gets()` получил `Ab9` и Enter; снимок показал введённую и
напечатанную строку, а CPU остановился на следующей C-строке. Ввод через
natural keyboard, произвольные имена физических клавиш, мышь и поля портов
пока не публичны в C-MCP.
`list_breakpoints` возвращает логические ID, владельца (`mcp:…`, `dap` или
`build`), вид C-точки, адреса и фактические условия банковской страницы для
всех точек общей сессии. Это чтение доступно и при running CPU. Точки,
поставленные вручную в родном debugger MAME, в этот список не входят.
Инструмент не даёт удалить чужую точку: `clear_breakpoint` по-прежнему
проверяет owner.
После первого `session_status` клиент отправляет session ID и build ID в
каждом RPC; команды, меняющие CPU/точки, дополнительно сверяют generation.
При замене сеанса или устаревшей generation команда отклоняется без
автоматического повтора.
У каждой MCP-копии свой owner ID. Она может удалить только свои точки; при
обычном закрытии stdio они очищаются. Живой тест подтвердил, что попытка
удалить DAP-точку отвергается и точка VS Code срабатывает после выхода MCP.
Удаление личной точки при running CPU отдельно проверено на ожидании
`getchar()`; оно не ставит CPU на паузу и не блокирует ввод.
Общий session server выдаёт 30-секундный control lease первому владельцу
команды CPU; MCP может захватить/освободить его явно. Фоновый heartbeat MCP
и опрос событий DAP продлевают активный lease. Конкурентная команда получает
отказ с именем владельца, после штатного закрытия lease освобождается.
При аварийном завершении MCP lease истекает за 30 секунд; сервер удаляет
личные точки и отпускает удерживаемые клавиши. Живой прогон с владельцем без
heartbeat подтвердил удаление его точки и отсутствие ложной остановки после
возобновления DAP; освобождение удерживаемой клавиши пока проверено только
unit-тестом.
Несколько одновременно
запущенных MAME с MCP не проверялись и не гарантируются; это отложенный тест.
Проверка совместной работы с DAP и реальным MAME:
```sh
MAME_HOME=/path/to/MAME/runtime pyenv exec python tests/sdbg/run_vscode_dap_probe.py \
--socket /tmp/sprinter-sdbg-mcp-probe.sock \
--mcp-python /tmp/sprinter-sdbg-mcp-venv/bin/python
```