208 lines
17 KiB
Markdown
208 lines
17 KiB
Markdown
# 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`, 1–256 байт 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
|
||
```
|