Files
Sprinter-SDCC/docs/mcp-convergence-plan.md
T
2026-09-17 23:11:08 +03:00

219 lines
24 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-интерфейсов MAME и C-отладки
## Цель и границы
Для C-приложения один `sdbg_server.py` владеет одним процессом MAME. VS Code
подключается к нему через DAP, Codex и Claude — через MCP, в том числе без
запущенного VS Code. Агент получает возможности нынешнего raw MCP, сохраняя
проверку C-карты, банки, события и владение точками. Один процесс MAME не
загружает одновременно `mamebridge` и `sdbgbridge`.
`MAME.HT/plugins/mamebridge` вместе с `src/mame_mcp.py` публикует 30
raw-инструментов. Удалённый на этапе А `src/mame_bridge.lua` использовал тот
же Python frontend, но обрабатывал только 16 команд и не отвечал при hard-stop.
`toolchain/sdbg_mcp.py` публикует 28 C-инструментов через общую с DAP сессию
(30 в автономном режиме с командами start/stop).
**Цель — покрыть возможности всех 30 raw-инструментов в C-сессии, а не
механически скопировать имена и небезопасную семантику.** Полный raw MCP
остаётся отдельным режимом для задач без C-пакета и других машин MAME.
Матрица покрытия должна для каждого raw-инструмента содержать имя/аргументы,
адресное пространство, состояние CPU, владельца операции, эквивалент в
C-MCP, ограничения совместной работы с DAP и живой тест. «Покрыт» означает
проверенное поведение, а не только регистрацию инструмента в MCP SDK.
## А. Проверить и убрать `mame_bridge.lua`
1. Зафиксировать 16 команд старого Lua-dispatch: `regs`, `mem`, `setmem`,
`bp`, `bpclr`, `bplist`, `wp`, `wpclr`, `step`, `over`, `out`, `cont`,
`pause`, `status`, `dasm`, `cmd`. Проверять именно Lua-обработчики: Python
frontend показывает 30 инструментов и со старым скриптом, хотя 14 из них
получают `unknown command`.
2. Расширить `MAME.HT/scripts/sprinter/probe-raw-mcp.py` отдельным
воспроизводимым профилем. Запускать legacy script и plugin **по очереди**
с одинаковым BIOS, ROM, fixture и изолированными каталогами IPC/дисков.
Для чтения сравнивать нормализованные результаты, для `setmem` писать лишь
в выделенный scratch-буфер и восстанавливать байты. У legacy проверять
создание/перечень/очистку не срабатывающих точек при running CPU; у plugin
дополнительно проверять реальное попадание и очистку после stop.
`cmd` в сравнении ограничить читающей командой, например `print pc`.
Не выполнять reset/state load ради сравнения: безопасный reset ещё не
подтверждён.
3. Старый frame notifier не обслуживает команды при остановленном CPU.
Поэтому stop-dependent `step`/`over`/`out` и интерактивную работу точек
нельзя требовать от legacy в этом состоянии. Для них сверить трансляцию
команд по коду и проверить поведение plugin при stop→step→inspect. Ошибка
или timeout старого скрипта при hard-stop — его известное ограничение,
а не недостающая функция plugin.
4. Отдельно прогнать 30 инструментов plugin через настоящий MCP stdio-клиент
с корректными предусловиями: running для ввода, stopped для шагов,
изолированные пути для снимков. Для каждой команды проверить содержимое
ответа, а не только отсутствие `unknown command`; после ввода и точек
проверить очистку состояния.
5. Если все 16 legacy-команд покрыты plugin и нет внешнего обязательного
потребителя `-autoboot_script mame_bridge.lua`, удалить файл из активного
дерева MAME.HT. Сохранить его в Git-истории; поправить ссылки в
`MAME_MCP_GUIDE.md`, `src/CLAUDE.md`, `scripts/sprinter/README.md`,
`src/mame_mcp.py` и заголовок plugin, где сейчас заявлено, что отличается
только способ опроса. Зафиксировать отдельным коммитом MAME.HT.
**Выход А:** plugin обеспечивает весь прежний набор и 30 своих инструментов;
проверяемых ссылок на удалённый script в текущем способе запуска нет. До
этого шага считать legacy устаревшим, но не удалённым.
**Статус А, 2026-09-17 — выполнено.** Сравнение Lua-dispatch показало 16/16
общих команд. Последовательные MCP stdio-прогоны на MAME.HT проверили
регистры, память, дизассемблирование, консоль, точки/watchpoints и
resume/pause у legacy при running CPU и plugin при stop. На реальном `hello`
plugin прошёл DSS→`main`, запись и восстановление `errno`, попадание в
watchpoint, ввод `x` через `getchar()` и машинные шаги over/out. Старый
frame notifier не смог обслужить запрос после service-stop в `main`, что
подтвердило известное ограничение. Файл удалён из активного дерева MAME.HT;
после удаления оба plugin-пробника повторно прошли. Повторный прогон `hello`
с `-video soft -window` показал вывод программы и `Press any key to exit...`
на снимке работающего экрана; затем `x` довёл PC до строки после `getchar()`.
Пробник `tests/sdbg/run_raw_mcp_hello_probe.py` теперь по умолчанию использует
видимое окно, а `--proof` сохраняет снимок вне временного каталога сессии.
## Б. Подготовить общую C-сессию к расширению
1. Ввести control lease между DAP и MCP для `continue`, `pause`, шагов,
записи памяти, ввода и raw-команд. Читающие операции допускают нескольких
клиентов; конфликтующие изменения получают явный отказ или передачу
управления. У каждой точки, watchpoint и удерживаемой клавиши есть owner;
heartbeat и закрытие соединения освобождают их даже после аварии клиента.
2. Все ответы и запросы связывать с session ID, build ID и generation. При
reset/load/exit или замене EXE инвалидировать старые операции и снимки.
Ограничить размеры запросов, очередь событий и время ожидания; после
timeout не повторять изменяющую команду вслепую. Для дополнительных
возможностей расширять один `sdbgbridge`, а не загружать рядом raw plugin.
3. Определить единый контракт адресов: logical Z80, raw program/data/io,
VRAM/share и текущие страницы банков — разные типы адреса. Существующий
C-инструмент `read_memory` оставляет значение «logical Z80»; raw-чтение
получает другое имя, например `read_program_memory`. Аналогично
`clear_breakpoint` продолжает принимать логический ID владельца, а не
незащищённый MAME ID. Совпадение имени не должно менять старую семантику.
**Выход Б:** два MCP-клиента и DAP читают одну сессию; конкурентные мутации
сериализованы, а потеря клиента не оставляет его точек или нажатых клавиш.
**Промежуточный статус Б, 2026-09-17.** В session server добавлен 30-секундный
control lease: команды CPU и ввод принимаются от текущего владельца, чужие
получают явный отказ. MCP предоставляет `claim_control`/`release_control` и
продлевает lease фоновым heartbeat; DAP продлевает его при опросе событий и
освобождает при disconnect. Каждый процесс session server выдаёт новый
`session_id`. Unit-тест проверил конфликт, освобождение и истечение lease;
живой DAP+MCP-прогон проверил передачу управления обратно VS Code и попадание
в его точку. Сервер теперь также удаляет личные MCP-точки и отпускает
удерживаемые клавиши после истечения heartbeat. Живой прогон с владельцем
без heartbeat подтвердил событие `owner_expired`, удаление точки и отсутствие
ложной остановки в `hello.c:62` после возобновления DAP.
RPC теперь привязывает последующие запросы DAP/MCP/CLI к session ID и build ID;
мутации CPU и точек также требуют актуальную generation. Смена сессии/socket
или устаревшая generation дают явный отказ, без автоматического повтора.
**Этап Б не закрыт:** ещё нужны проверка reset/load на живом MAME, контроль
новых типов мутаций и политика передачи lease между долгоживущими клиентами.
## В. Перенести 30 возможностей raw MCP по группам
| Raw-инструменты | Реализация в общей сессии и проверка |
|---|---|
| `status`, `read_registers`, `step`, `step_over`, `step_out`, `resume`, `pause` | Сохранить C-позицию и события; добавить ограниченный счётчик машинных шагов там, где raw его принимает. Проверить F10/F11/Shift+F11, остановку на пользовательской точке, ожидание `getchar()` и управление из VS Code и MCP. |
| `read_memory`, `read_logical_memory`, `read_vram`, `read_share`, `list_shares`, `write_memory` | Явные пространства и пределы длины, чтение без side effects, маркировка банков. Запись — только с lease и при остановленном CPU, с проверкой диапазона и read-back. Изменение кода/банков инвалидирует или повторно проверяет C-карту; не выдавать изменённый EXE за исходный build. |
| `set_breakpoint`, `clear_breakpoint`, `list_breakpoints`, `set_watchpoint`, `clear_watchpoint` | Raw-адрес и условие вынести в отдельный управляемый API; сохранить C-точки по строке/функции. Учитывать owner, bank/window и физический alias, показывать чужие точки только для чтения. Для watchpoint доказать срабатывание на read/write/IO, корректный PC и отсутствие ложного попадания при загрузке DSS. |
| `disassemble`, `debugger_command`, `screenshot`, `read_screen_pixels` | Дизассемблировать с явным пространством/банком. Снимок и пиксели отдавать с размером, форматом, временем кадра и ограничением объёма; при hard-stop сообщать, что кадр может быть старым. `debugger_command` в общей DAP-сессии сначала поддерживает проверенные читающие команды; полный pass-through — только под эксклюзивным raw lease с переоценкой/инвалидацией состояния после команды. Недопустимую команду отклонять явно, не выдавать частичный результат за поддержку. |
| `list_ports`, `press_key`, `type_string`, `type_text`, `move_mouse`, `click_mouse`, `press_input`, `set_input` | Одна очередь ввода с владельцем, временем удержания и гарантированным release при stop/disconnect/error. Вводить лишь после запуска EXE при running CPU; автоматический ввод DSS оставить launcher. Проверить `getchar()`, графическое приложение, мышь, прямую физическую клавиатуру MAME и отсутствие перехвата фокуса другой программы. |
Для каждой группы сначала добавить ограниченный метод `sdbgbridge`, затем
проверку/событие в `sdbg_server.py`, затем MCP-инструмент и документацию.
Не переносить старые строковые Lua-команды напрямую через MCP: они обходят
проверку типов, владения и generation. Если точная семантика raw-инструмента
небезопасна при открытом DAP, сохранить его функцию в явно эксклюзивном
режиме, а в общей сессии вернуть объясняемое ограничение.
**Выход В:** матрица всех 30 строк закрыта живыми тестами; ограничения raw
pass-through и особенности адресов перечислены поимённо. Число методов C-MCP
может быть больше 30 из-за разных адресных пространств и C-операций.
**Промежуточный статус В, 2026-09-17.** Через существующий `sdbgbridge`
добавлены `list_shares`, `read_share`, `read_vram`, `read_screen_pixels`,
`screenshot`, `read_program_memory`, `list_ports` с пределами размеров,
точным tag share, изолированным каталогом PNG и признаком устаревшего кадра
при stop. Живой DAP+MCP-прогон подтвердил совпадение raw program/logical Z80
в `main`, VRAM/share, список портов и PNG; второй прогон снял экран работающего
`hello` во время `getchar()` и затем завершил шаг клавишей `x`. Полное
поимённое состояние — в [матрице 30 raw-инструментов](mcp-capability-matrix.md).
Одиночный публичный `press_key` теперь требует running CPU и control lease,
удерживает клавишу 1..60 кадров и отпускает её при stop/ошибке. Автономный
официальный MCP-клиент ввёл `x` в ожидающий `getchar()` и попал на следующую
C-строку. Очередь строки, другие имена физических клавиш и мышь ещё не
реализованы; поэтому строка raw `press_key` покрыта частично.
Читающий `list_breakpoints` теперь показывает логические C-точки всех
владельцев, адреса и полученные от MAME условия банковской страницы;
удалять чужие точки он не позволяет. Живой MCP-прогон увидел личную точку
перед `getchar()`. Точки, вручную созданные в родном debugger, пока вне списка.
Дизассемблирование текущего logical Z80 окна добавлено как
`disassemble_logical`: фиксированная команда MAME с числовыми аргументами,
пределом 256 байт и изолированным временным файлом; ответ включает
`bank_pages` и generation. Живой MCP-прогон получил инструкции `main` с
адреса `0x8224`; байты до и после операции совпали. Raw program вне
текущего отображения остаётся открытым.
## Г. Сделать запуск пригодным для Codex и Claude без VS Code
Сейчас `sdbg_mcp.py` подключается лишь к уже работающему socket. Сам
`sdbg_launcher.py` умеет поднять MAME→DSS→EXE→`main` и `sdbg_server.py`
без VS Code, но это ещё не удобный жизненный цикл для MCP-клиента.
1. Добавить supervisor/команду запуска C-сессии с параметрами build,
`MAME_HOME`/`MAME_BIN`/ROM/DSS/образов и стабильным session socket.
MCP handshake должен завершаться быстро; запуск MAME выполняется
асинхронным инструментом с событиями прогресса, чтобы долгий DSS boot не
выглядел как зависший MCP-сервер. Предусмотреть attach к уже работающей
VS Code-сессии и DAP attach к сессии, запущенной агентом.
2. Определить судьбу MAME при закрытии каждого клиента: отсоединение одного
агента не завершает VS Code-сессию; явно управляемая автономная сессия
живёт до `stop_session` или закрытия её владельца согласно выбранной
политике. Повторный MCP старт не должен подключаться к старому socket/PID.
3. Проверить реальные stdio-подключения Codex и Claude отдельно: агент
запускает C-приложение без VS Code, останавливается в `main`, читает
переменную и экран, вводит клавишу; затем VS Code подключается к той же
сессии. Обратный порядок: F5 в VS Code, агент подключается по socket и
действует без второго Lua-моста. Проверить смену lease и cleanup.
**Выход Г:** C-отладка полностью доступна агенту без VS Code, а подключение
агента к VS Code-сессии не создаёт второй MAME и не теряет точки DAP.
**Промежуточный статус Г, 2026-09-17.** `sdbg_mcp.py --build` публикует
асинхронный `start_session`, фазовый `session_status` и `stop_session`.
Живой официальный MCP-клиент получил ответ старта за 5–7 мс, дождался
DSS→`hello.exe``main`, прочитал переменную и PNG. DAP подключился к тому же
socket; его disconnect оставил MAME работающим. Повторный MCP start был
отклонён, `stop_session` удалил socket и завершил свой MAME. При закрытии
MCP-процесса supervisor также завершает принадлежащий ему launcher.
Тем же официальным клиентом проверены установка C-точки перед `getchar()`,
ввод `x` через публичный MCP `press_key` и остановка на следующей C-строке.
**Этап Г не закрыт:** нужны реальные подключения из Codex и Claude,
проверка передачи control lease между их клиентами и DAP, а также устойчивость
к сбоям launcher/клиента на разных стадиях boot.
## Проверки и ограничения на всём пути
- Живые прогоны выполнять на `MAME.HT/sprinter`, собранном командой
`make SUBTARGET=sprinter SOURCES=src/mame/sinclair/sprinter.cpp`; отдельно
проверить stock + `osx` и patched + `sdbg` там, где функция не требует
патча. Lua-изменения не требуют пересборки бинарника, но требуют нового
запуска MAME.
- Использовать локальный pyenv Python 3.12. Raw MCP пока требует SDK 1.x,
C-MCP — SDK 2.x; окружения разделять до явной миграции frontend. Проверять
оба MCP-клиента на реальном протоколе, а не только прямыми Python-вызовами.
- Тесты на macOS включают окно `osx` как опцию, отзывчивость MAME, ручной
ввод и Debug Console. Linux проверять отдельным живым прогоном. Полный
Windows-маршрут пока не поддерживается из-за Unix socket/host launcher.
- Несколько процессов MAME с MCP остаются отдельной отложенной задачей:
их одновременная работа не проверялась и не гарантируется. Не объявлять
её рабочей по результату тестов нескольких клиентов одной сессии.
- Сохранять риск раннего фокуса MAME отдельной финальной задачей: защита от
случайного ввода до старта EXE не должна ломать DSS bootstrap и прямой
ввод после запуска приложения.