UNPKG

@debugmcp/mcp-debugger

Version:

Run-time step-through debugging for LLM agents.

462 lines (365 loc) 12.8 kB
# Debug Adapter Pattern Design ## Overview The Debug Adapter Pattern transforms mcp-debugger from a Python-specific tool into a multi-language debugging platform. This design introduces a language-agnostic adapter interface that encapsulates all language-specific behavior while maintaining backward compatibility with existing functionality. ### Design Philosophy 1. **Wrap, Don't Rewrite**: The existing ProxyManager provides excellent process management. We inject adapters to handle language-specific concerns. 2. **Interface Segregation**: Keep interfaces focused and cohesive 3. **Dependency Inversion**: Core depends on interfaces, not implementations 4. **Open/Closed**: Open for extension (new languages), closed for modification 5. **Single Responsibility**: Each adapter handles one language ## Architecture Diagram ```mermaid graph TD Client[MCP Client] -->|MCP Protocol| Server[server.ts] Server -->|Creates/Manages| SM[SessionManager] SM -->|Gets Adapter| AR[AdapterRegistry] AR -->|Creates| AD[IDebugAdapter] SM -->|Injects Adapter| PM[ProxyManager] PM -->|Uses Adapter| AD PM -->|Spawns| AP[Adapter Process] AP -->|DAP Protocol| DP[Debug Runtime] subgraph "Language Adapters" PA[PythonAdapter] MA[MockAdapter] NA[NodeAdapter] GA[GoAdapter] end AR --> PA AR --> MA AR --> NA AR --> GA style AD fill:#9cf,stroke:#333,stroke-width:2px style AR fill:#9cf,stroke:#333,stroke-width:2px style PM fill:#f9f,stroke:#333,stroke-width:2px ``` ## Interface Design Decisions ### Why These Methods? #### Lifecycle Management - **`initialize()`**: Performs one-time setup and environment validation - **`dispose()`**: Ensures clean resource cleanup **Rationale**: Separates construction from initialization, allowing dependency injection while deferring expensive operations. #### State Management - **`getState()`**: Provides current adapter state - **`isReady()`**: Quick check for debugging readiness - **`getCurrentThreadId()`**: Tracks active debugging thread **Rationale**: Enables state monitoring without tight coupling to internal implementation. #### Environment Validation - **`validateEnvironment()`**: Comprehensive environment check - **`getRequiredDependencies()`**: Lists what's needed **Rationale**: Fail-fast principle - detect problems early with clear error messages. #### Executable Management - **`resolveExecutablePath()`**: Finds the language runtime - **`getDefaultExecutableName()`**: Platform-aware defaults - **`getExecutableSearchPaths()`**: Where to look for executables **Rationale**: Abstracts platform differences and user configurations. #### DAP Protocol Operations - **`sendDapRequest()`**: Sends requests to debug adapter - **`handleDapEvent()`**: Processes incoming events - **`handleDapResponse()`**: Handles request responses **Rationale**: While ProxyManager handles the transport, adapters may need to transform or enhance DAP messages for language-specific behavior. ### Event Model Adapters emit events for state changes and DAP protocol events: ```typescript adapter.on('stateChanged', (oldState, newState) => { logger.info(`Adapter state: ${oldState} → ${newState}`); }); adapter.on('stopped', (event) => { logger.info(`Debugger stopped: ${event.body.reason}`); }); ``` ### Error Handling Consistent error patterns across adapters: ```typescript try { await adapter.validateEnvironment(); } catch (error) { if (error instanceof AdapterError) { if (error.code === AdapterErrorCode.EXECUTABLE_NOT_FOUND) { // Show installation instructions console.log(adapter.getInstallationInstructions()); } if (error.recoverable) { // Attempt recovery } } } ``` ## Sequence Diagrams ### Session Creation with Adapter ```mermaid sequenceDiagram participant C as MCP Client participant S as Server participant SM as SessionManager participant AR as AdapterRegistry participant A as Adapter participant SS as SessionStore C->>S: create_debug_session(language='python') S->>SM: createSession({language, name}) SM->>AR: isLanguageSupported('python') AR-->>SM: true SM->>SS: createSession(params) SS-->>SM: sessionInfo SM-->>S: sessionInfo S-->>C: {sessionId, name, language} ``` ### Debugging Lifecycle ```mermaid sequenceDiagram participant C as MCP Client participant S as Server participant SM as SessionManager participant AR as AdapterRegistry participant A as Adapter participant PM as ProxyManager participant AP as Adapter Process C->>S: start_debugging(sessionId, script) S->>SM: startDebugging(...) SM->>AR: create(language, config) AR->>A: new PythonAdapter(deps) AR-->>SM: adapter SM->>A: validateEnvironment() A-->>SM: {valid: true} SM->>PM: new ProxyManager(adapter, ...) SM->>PM: start(config) PM->>A: buildAdapterCommand(config) A-->>PM: {command, args, env} PM->>AP: spawn(command, args) AP-->>PM: process started PM->>A: connect(host, port) A-->>PM: connected PM-->>SM: initialized SM-->>S: {success: true} S-->>C: Debugging started ``` ### Step Operation with Adapter ```mermaid sequenceDiagram participant C as MCP Client participant SM as SessionManager participant PM as ProxyManager participant A as Adapter participant AP as Adapter Process C->>SM: stepOver(sessionId) SM->>PM: sendDapRequest('next', {threadId}) PM->>A: sendDapRequest('next', {threadId}) A->>A: Transform if needed A->>AP: DAP next request AP-->>A: DAP response A->>A: Transform if needed A-->>PM: response PM-->>SM: response AP->>A: stopped event A->>PM: emit('stopped', event) PM->>SM: emit('stopped', event) SM-->>C: {success: true} ``` ## Migration Strategy ### Phase 1: Interface Creation 1. Create IDebugAdapter interface ✅ 2. Create IAdapterRegistry interface 3. Create mock adapter for testing 4. ✅ Update types to use `executablePath` instead of `pythonPath` (COMPLETED) ### Phase 2: Mock Implementation 1. Implement MockDebugAdapter 2. Create mock adapter process script 3. Add mock adapter tests 4. Verify interface completeness ### Phase 3: Python Adapter 1. Create PythonDebugAdapter class 2. Move logic from python-utils.ts 3. Extract Python-specific code from SessionManager 4. Update ProxyManager to accept adapters ### Phase 4: Integration 1. Update SessionManager to use AdapterRegistry 2. Update server.ts to remove language validation 3. Add backward compatibility layer 4. Update all tests ## Component Responsibilities ### ProxyManager (What it Keeps) - Process lifecycle management - IPC communication with proxy process - Request/response correlation - Event forwarding - DAP message transport ### IDebugAdapter (What it Owns) - Language-specific configuration - Executable discovery and validation - Command building for debug adapter - Error message translation - Feature capability declaration - Language-specific path handling - DAP message transformation (if needed) ### AdapterRegistry (New Component) - Adapter registration and discovery - Language support checking - Adapter instance creation - Dependency injection into adapters ### SessionManager (Modified Role) - Session lifecycle orchestration - Adapter selection based on language - ProxyManager creation with adapter - High-level debugging operations ## Performance Considerations ### Overhead Analysis | Operation | Current | With Adapters | Impact | |-----------|---------|---------------|--------| | Session Creation | 100ms | 105ms | +5% (adapter creation) | | Language Detection | 80ms | 80ms | No change (moves to adapter) | | Breakpoint Setting | <10ms | <10ms | No change | | Step Operations | <50ms | <50ms | No change | | Memory per Session | Baseline | +~1MB | Adapter instance | ### Optimization Strategies 1. **Lazy Adapter Loading** ```typescript // Only load adapter when first used const adapter = await registry.create(language, config); ``` 2. **Adapter Pooling** ```typescript // Reuse adapters for same language class AdapterPool { private adapters = new Map<string, IDebugAdapter>(); } ``` 3. **Parallel Initialization** ```typescript // Initialize adapter while creating session const [session, adapter] = await Promise.all([ sessionStore.create(params), registry.create(language, config) ]); ``` ## MCP Tool Changes ### Current Tool Signature (As of 2025-07-12) ```json { "name": "create_debug_session", "inputSchema": { "properties": { "language": { "enum": ["python"] }, "executablePath": { "type": "string" } } } } ``` **Note**: The `pythonPath` parameter has been completely removed. There is no backward compatibility - all clients must use `executablePath`. ### Session Creation Flow 1. **Client Request** ```json { "tool": "create_debug_session", "arguments": { "language": "python", "executablePath": "/usr/bin/python3" } } ``` 2. **Server Processing** - Check if language is supported via AdapterRegistry - Create session with language stored - Return session info 3. **Debugging Start** - SessionManager creates appropriate adapter - Adapter validates environment - ProxyManager uses adapter for configuration ## Error Handling Patterns ### Adapter Not Found ```typescript class AdapterNotFoundError extends AdapterError { constructor(language: string) { super( `No debug adapter registered for language: ${language}`, AdapterErrorCode.ADAPTER_NOT_FOUND, false ); } } ``` ### Environment Invalid ```typescript class EnvironmentInvalidError extends AdapterError { constructor(message: string, instructions: string) { super(message, AdapterErrorCode.ENVIRONMENT_INVALID, true); this.instructions = instructions; } } ``` ### Graceful Degradation ```typescript // If feature not supported, disable UI elements if (!adapter.supportsFeature(DebugFeature.CONDITIONAL_BREAKPOINTS)) { disableConditionalBreakpoints(); } ``` ## Testing Strategy ### Unit Tests - Test each adapter method in isolation - Mock external dependencies - Verify error handling ### Integration Tests - Test adapter with real ProxyManager - Verify DAP communication - Test language-specific features ### E2E Tests - Full debugging session per language - Cross-language compatibility - Performance benchmarks ## Future Extensibility ### Adding a New Language 1. **Create Adapter Class** ```typescript export class GoDebugAdapter extends EventEmitter implements IDebugAdapter { readonly language = DebugLanguage.GO; readonly name = "Go Debug Adapter"; // ... implement interface } ``` 2. **Register with Registry** ```typescript registry.register('go', new GoAdapterFactory()); ``` 3. **Update Enum** ```typescript export enum DebugLanguage { PYTHON = 'python', NODE = 'node', GO = 'go' // New language } ``` 4. **Add Tests** - Unit tests for adapter - Integration tests - Update E2E test matrix ### Feature Flags ```typescript // Enable experimental languages const EXPERIMENTAL_LANGUAGES = process.env.EXPERIMENTAL_LANGUAGES?.split(',') || []; if (EXPERIMENTAL_LANGUAGES.includes('rust')) { registry.register('rust', new RustAdapterFactory()); } ``` ## Success Metrics ### Functional Metrics - ✅ All existing Python tests pass - ✅ No breaking changes to MCP API - ✅ Can add new language without changing core - ✅ Mock adapter enables testing without external dependencies ### Performance Metrics - ✅ Session creation < 150ms - ✅ Adapter overhead < 5ms per operation - ✅ No memory leaks with multiple adapters - ✅ DAP operations performance unchanged ### Code Quality Metrics - ✅ Reduced coupling (Python deps in adapter only) - ✅ Increased testability (mockable adapters) - ✅ Clear separation of concerns - ✅ Type safety throughout ## Conclusion This adapter pattern design provides a clean, extensible architecture for multi-language debugging support. Key benefits: 1. **Minimal Risk**: Existing ProxyManager continues to work as-is 2. **Incremental Migration**: Each phase provides value 3. **Future-Proof**: Easy to add new languages 4. **Testable**: Mock adapter enables comprehensive testing 5. **Performant**: Minimal overhead per operation The design successfully balances extensibility with stability, allowing mcp-debugger to evolve into a true multi-language platform while maintaining its current reliability.