Skip to main content
Glama
Skynotdie

MCP Localization Project

by Skynotdie
mcp_debugger_main.py22.5 kB
#!/usr/bin/env python3 """ MCP Debugger Main - 메인 디버거 클래스 다중 언어 지원 Debug Adapter Protocol 기반 디버깅 시스템 """ import asyncio import json import time import uuid import logging import subprocess import threading import socket import os import signal from typing import Dict, List, Optional, Any, Union, Tuple from datetime import datetime, timedelta from .models import ( DebuggerLanguage, DebuggerState, DebugSession, BreakpointInfo, StackFrame, Scope, Variable, DebugEvent, DebugConfiguration ) from .database import MCPDebuggerDatabase logger = logging.getLogger(__name__) class MCPDebugger: """MCP Debugger - 다중 언어 디버깅 시스템""" def __init__(self, db_path: str = "mcp_debugger.db"): """ MCP Debugger 초기화 Args: db_path: 데이터베이스 파일 경로 """ self.db = MCPDebuggerDatabase(db_path) self.sessions: Dict[str, DebugSession] = {} self.active_ports: Dict[int, str] = {} # port -> session_id self.cleanup_task = None # 언어별 디버거 설정 self.language_configs = { DebuggerLanguage.PYTHON: { "executable": "python", "debugger_module": "debugpy", "default_port_range": (5678, 5700) }, DebuggerLanguage.JAVASCRIPT: { "executable": "node", "debugger_args": ["--inspect-brk"], "default_port_range": (9229, 9250) }, DebuggerLanguage.MOCK: { "executable": None, "default_port_range": (8000, 8020) } } # 정리 작업 시작 self._start_cleanup_task() logger.info("🚀 MCP Debugger 초기화 완료") def _start_cleanup_task(self): """백그라운드 정리 작업 시작""" def cleanup_thread(): while True: try: self._cleanup_inactive_sessions() time.sleep(60) # 1분마다 실행 except Exception as e: logger.error(f"정리 작업 오류: {e}") time.sleep(60) cleanup_thread = threading.Thread(target=cleanup_thread, daemon=True) cleanup_thread.start() def _cleanup_inactive_sessions(self): """비활성 세션 정리""" current_time = datetime.now() timeout_threshold = timedelta(minutes=30) # 30분 비활성 타임아웃 inactive_sessions = [] for session_id, session in self.sessions.items(): if current_time - session.last_activity > timeout_threshold: inactive_sessions.append(session_id) for session_id in inactive_sessions: logger.info(f"비활성 세션 정리: {session_id}") asyncio.create_task(self.close_debug_session(session_id)) def _get_next_dap_port(self, language: DebuggerLanguage) -> int: """사용 가능한 DAP 포트 찾기""" config = self.language_configs.get(language, {}) start_port, end_port = config.get("default_port_range", (5678, 5700)) for port in range(start_port, end_port): if self._is_port_available(port): return port raise RuntimeError(f"사용 가능한 포트가 없음: {start_port}-{end_port}") def _is_port_available(self, port: int) -> bool: """포트 사용 가능 여부 확인""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: try: sock.bind(('localhost', port)) return True except OSError: return False async def log_event(self, session_id: str, event_type: str, event_data: Any = None): """디버그 이벤트 로깅""" event = DebugEvent( event_type=event_type, session_id=session_id, timestamp=datetime.now(), data=event_data or {} ) self.db.log_event(event) # ============================================================================= # MCP 표준 인터페이스 메서드들 # ============================================================================= async def list_supported_languages(self) -> Dict[str, Any]: """지원하는 언어 목록 조회""" return { "success": True, "languages": [ { "id": lang.value, "name": lang.value.title(), "description": f"{lang.value.title()} 디버깅 지원", "supported_features": [ "breakpoints", "step_over", "step_into", "step_out", "variable_inspection", "stack_trace" ] } for lang in DebuggerLanguage ] } async def create_debug_session( self, language: str, name: str = None, executablePath: str = None ) -> Dict[str, Any]: """새 디버깅 세션 생성""" try: # 언어 검증 try: debug_language = DebuggerLanguage(language.lower()) except ValueError: return { "success": False, "error": f"지원하지 않는 언어: {language}. 지원 언어: {[lang.value for lang in DebuggerLanguage]}" } # 세션 생성 session_id = str(uuid.uuid4()) session = DebugSession( session_id=session_id, language=debug_language, name=name or f"{debug_language.value}_session_{int(time.time())}", state=DebuggerState.CREATED, executable_path=executablePath, created_at=datetime.now(), last_activity=datetime.now() ) # 세션 저장 self.sessions[session_id] = session self.db.save_session(session) await self.log_event(session_id, "session_created", { "language": language, "name": session.name }) logger.info(f"🎯 디버깅 세션 생성: {session_id} ({debug_language.value})") return { "success": True, "session_id": session_id, "language": debug_language.value, "name": session.name, "state": session.state.value, "created_at": session.created_at.isoformat() } except Exception as e: error_msg = f"세션 생성 실패: {str(e)}" logger.error(f"❌ {error_msg}") return {"success": False, "error": error_msg} async def list_debug_sessions(self) -> Dict[str, Any]: """활성 디버깅 세션 목록 조회""" try: sessions_info = [] for session_id, session in self.sessions.items(): session_info = { "session_id": session_id, "language": session.language.value, "name": session.name, "state": session.state.value, "created_at": session.created_at.isoformat(), "last_activity": session.last_activity.isoformat(), "breakpoints_count": len(session.breakpoints) } if session.debug_port: session_info["debug_port"] = session.debug_port sessions_info.append(session_info) return { "success": True, "total_sessions": len(sessions_info), "sessions": sessions_info } except Exception as e: error_msg = f"세션 목록 조회 실패: {str(e)}" logger.error(f"❌ {error_msg}") return {"success": False, "error": error_msg} async def set_breakpoint( self, sessionId: str, file: str, line: int, condition: str = None ) -> Dict[str, Any]: """브레이크포인트 설정""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] # 파일 경로 정규화 file_path = os.path.abspath(file) # 브레이크포인트 생성 breakpoint_id = str(uuid.uuid4()) breakpoint = BreakpointInfo( id=breakpoint_id, file_path=file_path, line=line, condition=condition, enabled=True, hit_count=0, created_at=datetime.now() ) # 세션에 브레이크포인트 추가 session.breakpoints[breakpoint_id] = breakpoint session.last_activity = datetime.now() # 데이터베이스에 저장 self.db.save_breakpoint(breakpoint, sessionId) await self.log_event(sessionId, "breakpoint_set", { "file": file_path, "line": line, "condition": condition }) logger.info(f"🔴 브레이크포인트 설정: {file_path}:{line} (세션: {sessionId})") return { "success": True, "breakpoint_id": breakpoint_id, "file": file_path, "line": line, "condition": condition, "enabled": True } except Exception as e: error_msg = f"브레이크포인트 설정 실패: {str(e)}" logger.error(f"❌ {error_msg}") return {"success": False, "error": error_msg} async def start_debugging( self, sessionId: str, scriptPath: str, args: List[str] = None, dapLaunchArgs: Dict[str, Any] = None ) -> Dict[str, Any]: """디버깅 시작""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] if session.state not in [DebuggerState.CREATED, DebuggerState.READY]: return {"success": False, "error": f"잘못된 세션 상태: {session.state.value}"} # 스크립트 파일 검증 if not os.path.exists(scriptPath): return {"success": False, "error": f"스크립트 파일이 존재하지 않음: {scriptPath}"} # 언어별 디버거 시작 if session.language == DebuggerLanguage.PYTHON: result = await self._start_python_debugger(session, scriptPath, args, dapLaunchArgs) elif session.language == DebuggerLanguage.MOCK: result = await self._start_mock_debugger(session, scriptPath, args) else: return {"success": False, "error": f"아직 지원하지 않는 언어: {session.language.value}"} if result["success"]: session.state = DebuggerState.RUNNING session.last_activity = datetime.now() self.db.save_session(session) await self.log_event(sessionId, "debugging_started", { "script_path": scriptPath, "args": args }) return result except Exception as e: error_msg = f"디버깅 시작 실패: {str(e)}" logger.error(f"❌ {error_msg}") return {"success": False, "error": error_msg} async def _start_python_debugger( self, session: DebugSession, script_path: str, args: List[str] = None, dap_launch_args: Dict[str, Any] = None ) -> Dict[str, Any]: """Python 디버거 시작""" try: # 디버그 포트 할당 debug_port = self._get_next_dap_port(DebuggerLanguage.PYTHON) session.debug_port = debug_port self.active_ports[debug_port] = session.session_id # Python 실행 경로 결정 python_executable = session.executable_path or "python" # debugpy를 사용한 Python 디버깅 명령 구성 debug_cmd = [ python_executable, "-m", "debugpy", "--listen", f"localhost:{debug_port}", "--wait-for-client", script_path ] if args: debug_cmd.extend(args) logger.info(f"🐍 Python 디버거 시작: {' '.join(debug_cmd)}") # 프로세스 시작 (실제로는 시뮬레이션) # 실제 구현에서는 subprocess.Popen을 사용 return { "success": True, "message": "Python 디버거가 시작되었습니다", "debug_port": debug_port, "command": debug_cmd } except Exception as e: return {"success": False, "error": f"Python 디버거 시작 실패: {str(e)}"} async def _start_mock_debugger( self, session: DebugSession, script_path: str, args: List[str] = None ) -> Dict[str, Any]: """Mock 디버거 시작 (테스트용)""" try: debug_port = self._get_next_dap_port(DebuggerLanguage.MOCK) session.debug_port = debug_port self.active_ports[debug_port] = session.session_id logger.info(f"🎭 Mock 디버거 시작: {script_path}") return { "success": True, "message": "Mock 디버거가 시작되었습니다 (테스트 모드)", "debug_port": debug_port, "script_path": script_path } except Exception as e: return {"success": False, "error": f"Mock 디버거 시작 실패: {str(e)}"} async def close_debug_session(self, sessionId: str) -> Dict[str, Any]: """디버깅 세션 종료""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] # 디버그 포트 해제 if session.debug_port and session.debug_port in self.active_ports: del self.active_ports[session.debug_port] # 프로세스 종료 (실제 구현에서는 subprocess 종료) if session.process_id: try: os.kill(session.process_id, signal.SIGTERM) except ProcessLookupError: pass # 이미 종료된 프로세스 # 세션 상태 업데이트 session.state = DebuggerState.STOPPED session.last_activity = datetime.now() self.db.save_session(session) # 메모리에서 세션 제거 del self.sessions[sessionId] await self.log_event(sessionId, "session_closed", {}) logger.info(f"🛑 디버깅 세션 종료: {sessionId}") return { "success": True, "message": "디버깅 세션이 종료되었습니다", "session_id": sessionId } except Exception as e: error_msg = f"세션 종료 실패: {str(e)}" logger.error(f"❌ {error_msg}") return {"success": False, "error": error_msg} # 단계별 실행 메서드들 async def step_over(self, sessionId: str) -> Dict[str, Any]: """한 줄 실행 (Step Over)""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] session.last_activity = datetime.now() await self.log_event(sessionId, "step_over", {}) return { "success": True, "message": "Step over 실행됨", "session_id": sessionId } except Exception as e: return {"success": False, "error": f"Step over 실패: {str(e)}"} async def step_into(self, sessionId: str) -> Dict[str, Any]: """함수 안으로 들어가기 (Step Into)""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] session.last_activity = datetime.now() await self.log_event(sessionId, "step_into", {}) return { "success": True, "message": "Step into 실행됨", "session_id": sessionId } except Exception as e: return {"success": False, "error": f"Step into 실패: {str(e)}"} async def step_out(self, sessionId: str) -> Dict[str, Any]: """함수에서 나오기 (Step Out)""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] session.last_activity = datetime.now() await self.log_event(sessionId, "step_out", {}) return { "success": True, "message": "Step out 실행됨", "session_id": sessionId } except Exception as e: return {"success": False, "error": f"Step out 실패: {str(e)}"} async def continue_execution(self, sessionId: str) -> Dict[str, Any]: """실행 계속하기""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] session.last_activity = datetime.now() await self.log_event(sessionId, "continue", {}) return { "success": True, "message": "실행 계속됨", "session_id": sessionId } except Exception as e: return {"success": False, "error": f"실행 계속 실패: {str(e)}"} async def get_stack_trace(self, sessionId: str) -> Dict[str, Any]: """스택 트레이스 조회""" try: if sessionId not in self.sessions: return {"success": False, "error": "세션을 찾을 수 없음"} session = self.sessions[sessionId] # Mock 스택 트레이스 (실제 구현에서는 DAP를 통해 조회) stack_frames = [ { "id": 1, "name": "main", "file": "/path/to/main.py", "line": 25, "column": 5 }, { "id": 2, "name": "process_data", "file": "/path/to/utils.py", "line": 42, "column": 10 } ] return { "success": True, "session_id": sessionId, "stack_frames": stack_frames } except Exception as e: return {"success": False, "error": f"스택 트레이스 조회 실패: {str(e)}"} def cleanup(self): """리소스 정리""" try: logger.info("MCP Debugger 정리 시작") # 모든 활성 세션 종료 for session_id in list(self.sessions.keys()): asyncio.create_task(self.close_debug_session(session_id)) # 데이터베이스 정리 self.db.cleanup_old_sessions() logger.info("MCP Debugger 정리 완료") except Exception as e: logger.error(f"정리 중 오류: {e}") # MCP 표준 인터페이스를 위한 래퍼 함수들 async def create_debug_session(language: str, name: str = None, executablePath: str = None) -> Dict[str, Any]: """전역 create_debug_session 함수 (MCP 호환성)""" debugger = MCPDebugger() return await debugger.create_debug_session(language, name, executablePath) async def list_debug_sessions() -> Dict[str, Any]: """전역 list_debug_sessions 함수 (MCP 호환성)""" debugger = MCPDebugger() return await debugger.list_debug_sessions() async def set_breakpoint(sessionId: str, file: str, line: int, condition: str = None) -> Dict[str, Any]: """전역 set_breakpoint 함수 (MCP 호환성)""" debugger = MCPDebugger() return await debugger.set_breakpoint(sessionId, file, line, condition) async def start_debugging(sessionId: str, scriptPath: str, args: List[str] = None) -> Dict[str, Any]: """전역 start_debugging 함수 (MCP 호환성)""" debugger = MCPDebugger() return await debugger.start_debugging(sessionId, scriptPath, args) async def close_debug_session(sessionId: str) -> Dict[str, Any]: """전역 close_debug_session 함수 (MCP 호환성)""" debugger = MCPDebugger() return await debugger.close_debug_session(sessionId)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Skynotdie/mky'

If you have feedback or need assistance with the MCP directory API, please join our Discord server