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

14 KiB
Raw Blame History

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:

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-сервер:

{
  "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:

{
  "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. Доступны также --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, list_ports, list_shares, read_share, read_vram, read_screen_pixels, screenshot, press_key, 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_source. Ответы SDK 2.x содержат structuredContent. step_source принимает into, over или out и возвращает принятие команды; итоговую остановку получите через recent_events с курсором last. Логи SDBG_LOG приходят как события output; журнал ограничен 1024 событиями и сообщает first/last/lost.

Число 27 в attach-режиме (29 в автономном с 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 уже перенесены; набор строки, произвольные поля портов и мышь требуют отдельного контракта ввода
Raw disassembly Связать адрес/банк с проверенной C-картой; не выдавать физический адрес за logical

Это следующий этап, а не запрет на функции raw MCP. Нельзя просто загрузить mamebridge рядом с sdbgbridge: два независимых обработчика начнут менять CPU и точки без общего owner ID и журнала. Результаты удаления legacy Lua и этапы переноса всех 30 возможностей в общую C-сессию описаны в плане сведения MCP и матрице покрытия.

Чтение памяти принимает десятичный адрес или 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.

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 не заменяет его. Набор строки, произвольные имена клавиш, мышь и порты ввода ещё не публичны в 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:

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