#!/usr/bin/env python3 """Живой MCP 2.x клиент: stdio → sdbg MCP → общая DAP-сессия.""" from __future__ import annotations import argparse import asyncio import json from pathlib import Path import sys from mcp import Client, StdioServerParameters ROOT = Path(__file__).resolve().parents[2] async def probe(socket: str, source: str, foreign_id: int): parameters = StdioServerParameters( command=sys.executable, args=[str(ROOT / 'toolchain/sdbg_mcp.py'), '--socket', socket]) async with Client(parameters) as client: listed = await client.list_tools() names = {tool.name for tool in listed.tools} expected = {'session_status', 'where', 'read_registers', 'read_memory', 'recent_events', 'set_line_breakpoint', 'clear_breakpoint'} if not expected <= names: raise RuntimeError('Не хватает MCP-инструментов: ' + str(expected - names)) async def call(name: str, arguments: dict | None = None): result = await client.call_tool(name, arguments or {}) if result.is_error: raise RuntimeError(name + ': ' + str(result.content)) if result.structured_content is None: raise RuntimeError(name + ': нет structuredContent: '+repr(result.content)) return result.structured_content status = await call('session_status') location = await call('where') registers = await call('read_registers') memory = await call('read_memory', {'address': hex(location['pc']), 'length': 4}) events = await call('recent_events', {'after': 0}) if not status['build_id'] or location['status'] != 'mapped' or \ registers['registers']['PC'] != location['pc'] or \ len(memory['hex']) != 8 or not events['events']: raise RuntimeError('Неполный C-снимок MCP: ' + repr((status, location, memory))) own = await call('set_line_breakpoint', {'file': source, 'line': 62}) foreign = await client.call_tool('clear_breakpoint', {'identifier': foreign_id}) if not foreign.is_error or 'не принадлежит' not in str(foreign.content): raise RuntimeError('MCP не подтвердил защиту чужой точки VS Code') print(json.dumps({'event': 'mcp_shared_session_verified', 'build_id': status['build_id'], 'pc': location['pc'], 'tools': len(names), 'foreign_point_rejected': True, 'owned_point_for_cleanup': own['id']}, ensure_ascii=False), flush=True) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--socket', required=True) parser.add_argument('--source', required=True) parser.add_argument('--foreign-id', type=int, required=True) args = parser.parse_args() asyncio.run(probe(args.socket, args.source, args.foreign_id)) if __name__ == '__main__': main()