219 lines
24 KiB
Markdown
219 lines
24 KiB
Markdown
# План сведения 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 и прямой
|
||
ввод после запуска приложения.
|