UNPKG

@debugmcp/mcp-debugger

Version:

Run-time step-through debugging for LLM agents.

794 lines (720 loc) 35.5 kB
/** * Debug MCP Server - Main Server Implementation */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; // StdioServerTransport is used in index.ts, not here import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode as McpErrorCode, McpError, ServerResult, } from '@modelcontextprotocol/sdk/types.js'; import { SessionManager, SessionManagerConfig } from './session/session-manager.js'; import { createProductionDependencies } from './container/dependencies.js'; import { ContainerConfig } from './container/types.js'; import { DebugSessionInfo, Variable, StackFrame, DebugLanguage, Breakpoint, SessionLifecycleState } from './session/models.js'; import { DebugProtocol } from '@vscode/debugprotocol'; import path from 'path'; /** * Configuration options for the Debug MCP Server */ export interface DebugMcpServerOptions { logLevel?: string; logFile?: string; } /** * Language metadata for supported languages */ interface LanguageMetadata { id: string; displayName: string; version: string; requiresExecutable: boolean; defaultExecutable?: string; } /** * Tool arguments interface */ interface ToolArguments { sessionId?: string; language?: string; name?: string; executablePath?: string; // Language-agnostic executable path file?: string; line?: number; condition?: string; scriptPath?: string; args?: string[]; dapLaunchArgs?: Partial<DebugProtocol.LaunchRequestArguments>; dryRunSpawn?: boolean; scope?: number; frameId?: number; expression?: string; linesContext?: number; } /** * Main Debug MCP Server class */ export class DebugMcpServer { public server: Server; private sessionManager: SessionManager; private logger; private constructorOptions: DebugMcpServerOptions; private supportedLanguages: string[] = []; // Get supported languages from adapter registry private getSupportedLanguages(): string[] { const adapterRegistry = this.getAdapterRegistry(); return adapterRegistry.getSupportedLanguages(); } // Get language metadata for all supported languages private getLanguageMetadata(): LanguageMetadata[] { const adapterRegistry = this.getAdapterRegistry(); const languages = adapterRegistry.getSupportedLanguages(); // Map to metadata - in future, this info will come from adapter registry return languages.map((lang: string) => { switch (lang) { case 'python': return { id: 'python', displayName: 'Python', version: '1.0.0', requiresExecutable: true, defaultExecutable: 'python' }; case 'mock': return { id: 'mock', displayName: 'Mock', version: '1.0.0', requiresExecutable: false }; default: return { id: lang, displayName: lang.charAt(0).toUpperCase() + lang.slice(1), version: '1.0.0', requiresExecutable: true }; } }); } /** * Validate session exists and is not terminated */ private validateSession(sessionId: string): void { const session = this.sessionManager.getSession(sessionId); if (!session) { throw new McpError(McpErrorCode.InvalidParams, `Session not found: ${sessionId}`); } // Check the new lifecycle state instead of legacy state if (session.sessionLifecycle === SessionLifecycleState.TERMINATED) { throw new McpError(McpErrorCode.InvalidRequest, `Session is terminated: ${sessionId}`); } } // Public methods to expose SessionManager functionality for testing/external use public async createDebugSession(params: { language: DebugLanguage; name?: string; executablePath?: string; }): Promise<DebugSessionInfo> { // Validate language support const adapterRegistry = this.getAdapterRegistry(); if (!adapterRegistry.isLanguageSupported(params.language)) { const supported = adapterRegistry.getSupportedLanguages(); throw new McpError( McpErrorCode.InvalidParams, `Language '${params.language}' is not supported. Available languages: ${supported.join(', ')}` ); } const name = params.name || `${params.language}-debug-${Date.now()}`; try { const sessionInfo: DebugSessionInfo = await this.sessionManager.createSession({ language: params.language as DebugLanguage, name: name, executablePath: params.executablePath // Use executablePath for consistency }); return sessionInfo; } catch (error) { const errorMessage = (error as Error).message || String(error); this.logger.error('Failed to create debug session', { error: errorMessage, stack: (error as Error).stack }); throw new McpError(McpErrorCode.InternalError, `Failed to create debug session: ${errorMessage}`); } } public async startDebugging( sessionId: string, scriptPath: string, args?: string[], dapLaunchArgs?: Partial<DebugProtocol.LaunchRequestArguments>, dryRunSpawn?: boolean ): Promise<{ success: boolean; state: string; error?: string; data?: unknown; }> { this.validateSession(sessionId); // In container mode, prepend /workspace/ to the path if (process.env.MCP_CONTAINER === 'true') { scriptPath = `/workspace/${scriptPath}`; } this.logger.info(`[DebugMcpServer.startDebugging] Using scriptPath: ${scriptPath}`); const result = await this.sessionManager.startDebugging( sessionId, scriptPath, args, dapLaunchArgs, dryRunSpawn ); return result; } public async closeDebugSession(sessionId: string): Promise<boolean> { return this.sessionManager.closeSession(sessionId); } public async setBreakpoint(sessionId: string, file: string, line: number, condition?: string): Promise<Breakpoint> { this.validateSession(sessionId); // In container mode, prepend /workspace/ to the path if (process.env.MCP_CONTAINER === 'true') { file = `/workspace/${file}`; } this.logger.info(`[DebugMcpServer.setBreakpoint] Using file path: ${file}`); return this.sessionManager.setBreakpoint(sessionId, file, line, condition); } public async getVariables(sessionId: string, variablesReference: number): Promise<Variable[]> { this.validateSession(sessionId); return this.sessionManager.getVariables(sessionId, variablesReference); } public async getStackTrace(sessionId: string): Promise<StackFrame[]> { this.validateSession(sessionId); const session = this.sessionManager.getSession(sessionId); const currentThreadId = session?.proxyManager?.getCurrentThreadId(); if (!session || !session.proxyManager || !currentThreadId) { throw new McpError(McpErrorCode.InvalidRequest, "Cannot get stack trace: no active proxy, thread, or session not found/paused."); } return this.sessionManager.getStackTrace(sessionId, currentThreadId); } public async getScopes(sessionId: string, frameId: number): Promise<DebugProtocol.Scope[]> { this.validateSession(sessionId); return this.sessionManager.getScopes(sessionId, frameId); } public async continueExecution(sessionId: string): Promise<boolean> { this.validateSession(sessionId); const result = await this.sessionManager.continue(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to continue execution'); } return true; } public async stepOver(sessionId: string): Promise<boolean> { this.validateSession(sessionId); const result = await this.sessionManager.stepOver(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to step over'); } return true; } public async stepInto(sessionId: string): Promise<boolean> { this.validateSession(sessionId); const result = await this.sessionManager.stepInto(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to step into'); } return true; } public async stepOut(sessionId: string): Promise<boolean> { this.validateSession(sessionId); const result = await this.sessionManager.stepOut(sessionId); if (!result.success) { throw new Error(result.error || 'Failed to step out'); } return true; } constructor(options: DebugMcpServerOptions = {}) { this.constructorOptions = options; const containerConfig: ContainerConfig = { logLevel: options.logLevel, logFile: options.logFile, sessionLogDirBase: options.logFile ? path.resolve(path.dirname(options.logFile), 'sessions') : undefined }; const dependencies = createProductionDependencies(containerConfig); this.logger = dependencies.logger; this.logger.info('[DebugMcpServer Constructor] Main server logger instance assigned.'); this.server = new Server( { name: 'debug-mcp-server', version: '0.1.0' }, { capabilities: { tools: {} } } ); const sessionManagerConfig: SessionManagerConfig = { logDirBase: containerConfig.sessionLogDirBase }; this.sessionManager = new SessionManager(sessionManagerConfig, dependencies); this.registerTools(); this.server.onerror = (error) => { this.logger.error('Server error', { error }); }; } /** * Sanitize request data for logging (remove sensitive information) */ private sanitizeRequest(args: Record<string, unknown>): Record<string, unknown> { const sanitized = { ...args }; // Remove absolute paths from executablePath if (sanitized.executablePath && typeof sanitized.executablePath === 'string' && path.isAbsolute(sanitized.executablePath)) { sanitized.executablePath = '<absolute-path>'; } // Truncate long arrays if (sanitized.args && Array.isArray(sanitized.args) && sanitized.args.length > 5) { sanitized.args = [...sanitized.args.slice(0, 5), `... +${sanitized.args.length - 5} more`]; } return sanitized; } /** * Get session name for logging */ private getSessionName(sessionId: string): string { try { const session = this.sessionManager.getSession(sessionId); return session?.name || 'Unknown Session'; } catch { return 'Unknown Session'; } } private getPathDescription(parameterName: string): string { // Hands-off approach: provide simple, generic path guidance // Let OS/containers/debug adapters handle paths naturally if (parameterName === 'script') { return `Path to the script to debug. Use absolute paths or paths relative to your current working directory`; } return `Path to the ${parameterName}. Use absolute paths or paths relative to your current working directory`; } private registerTools(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { this.logger.debug('Handling ListToolsRequest'); // Get supported languages dynamically - deferred until request time let supportedLanguages: string[]; try { supportedLanguages = this.getSupportedLanguages(); } catch (error) { // Fallback if adapter registry isn't ready supportedLanguages = ['python', 'mock']; this.logger.warn('Adapter registry not ready, using default languages', { error }); } // Generate dynamic descriptions for path parameters const fileDescription = this.getPathDescription('source file'); const scriptPathDescription = this.getPathDescription('script'); return { tools: [ { name: 'create_debug_session', description: 'Create a new debugging session', inputSchema: { type: 'object', properties: { language: { type: 'string', enum: supportedLanguages, description: 'Programming language for debugging' }, name: { type: 'string', description: 'Optional session name' }, executablePath: {type: 'string', description: 'Path to language executable (optional, will auto-detect if not provided)'} }, required: ['language'] } }, { name: 'list_supported_languages', description: 'List all supported debugging languages with metadata', inputSchema: { type: 'object', properties: {} } }, { name: 'list_debug_sessions', description: 'List all active debugging sessions', inputSchema: { type: 'object', properties: {} } }, { name: 'set_breakpoint', description: 'Set a breakpoint. Setting breakpoints on non-executable lines (structural, declarative) may lead to unexpected behavior', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, file: { type: 'string', description: fileDescription }, line: { type: 'number', description: 'Line number where to set breakpoint. Executable statements (assignments, function calls, conditionals, returns) work best. Structural lines (function/class definitions), declarative lines (imports), or non-executable lines (comments, blank lines) may cause unexpected stepping behavior' }, condition: { type: 'string' } }, required: ['sessionId', 'file', 'line'] } }, { name: 'start_debugging', description: 'Start debugging a script', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scriptPath: { type: 'string', description: scriptPathDescription }, args: { type: 'array', items: { type: 'string' } }, dapLaunchArgs: { type: 'object', properties: { stopOnEntry: { type: 'boolean' }, justMyCode: { type: 'boolean' } }, additionalProperties: true }, dryRunSpawn: { type: 'boolean' } }, required: ['sessionId', 'scriptPath'] } }, { name: 'close_debug_session', description: 'Close a debugging session', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_over', description: 'Step over', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_into', description: 'Step into', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_out', description: 'Step out', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'continue_execution', description: 'Continue execution', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'pause_execution', description: 'Pause execution (Not Implemented)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'get_variables', description: 'Get variables (scope is variablesReference: number)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scope: { type: 'number', description: "The variablesReference number from a StackFrame or Variable" } }, required: ['sessionId', 'scope'] } }, { name: 'get_stack_trace', description: 'Get stack trace', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'get_scopes', description: 'Get scopes for a stack frame', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, frameId: { type: 'number', description: "The ID of the stack frame from a stackTrace response" } }, required: ['sessionId', 'frameId'] } }, { name: 'evaluate_expression', description: 'Evaluate expression (Not Implemented)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, expression: { type: 'string' } }, required: ['sessionId', 'expression'] } }, { name: 'get_source_context', description: 'Get source context (Not Implemented)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, file: { type: 'string', description: fileDescription }, line: { type: 'number' }, linesContext: { type: 'number' } }, required: ['sessionId', 'file', 'line'] } }, ], }; }); this.server.setRequestHandler( CallToolRequestSchema, async (request): Promise<ServerResult> => { const toolName = request.params.name; const args = request.params.arguments as ToolArguments; // Log tool call with structured logging this.logger.info('tool:call', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, request: this.sanitizeRequest(args as Record<string, unknown>), timestamp: Date.now() }); try { let result: ServerResult; switch (toolName) { case 'create_debug_session': { const sessionInfo = await this.createDebugSession({ language: (args.language || 'python') as DebugLanguage, name: args.name, executablePath: args.executablePath }); // Log session creation this.logger.info('session:created', { sessionId: sessionInfo.id, sessionName: sessionInfo.name, language: sessionInfo.language, executablePath: args.executablePath, timestamp: Date.now() }); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, sessionId: sessionInfo.id, message: `Created ${sessionInfo.language} debug session: ${sessionInfo.name}` }) }] }; break; } case 'list_debug_sessions': { result = await this.handleListDebugSessions(); break; } case 'set_breakpoint': { if (!args.sessionId || !args.file || args.line === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const breakpoint = await this.setBreakpoint(args.sessionId, args.file, args.line, args.condition); // Log breakpoint event this.logger.info('debug:breakpoint', { event: 'set', sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), breakpointId: breakpoint.id, file: breakpoint.file, line: breakpoint.line, verified: breakpoint.verified, timestamp: Date.now() }); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, breakpointId: breakpoint.id, file: breakpoint.file, line: breakpoint.line, verified: breakpoint.verified, message: `Breakpoint set at ${breakpoint.file}:${breakpoint.line}` }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'start_debugging': { if (!args.sessionId || !args.scriptPath) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const debugResult = await this.startDebugging(args.sessionId, args.scriptPath, args.args, args.dapLaunchArgs, args.dryRunSpawn); const responsePayload: Record<string, unknown> = { success: debugResult.success, state: debugResult.state, message: debugResult.error ? debugResult.error : (debugResult.data as Record<string, unknown>)?.message || `Operation status for ${args.scriptPath}`, }; if (debugResult.data) { responsePayload.data = debugResult.data; } result = { content: [{ type: 'text', text: JSON.stringify(responsePayload) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message, state: 'stopped' }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'close_debug_session': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } const sessionName = this.getSessionName(args.sessionId); const sessionCreatedAt = Date.now(); // In real implementation, would track creation time const closed = await this.closeDebugSession(args.sessionId); if (closed) { // Log session closure this.logger.info('session:closed', { sessionId: args.sessionId, sessionName: sessionName, duration: Date.now() - sessionCreatedAt, timestamp: Date.now() }); } result = { content: [{ type: 'text', text: JSON.stringify({ success: closed, message: closed ? `Closed debug session: ${args.sessionId}` : `Failed to close debug session: ${args.sessionId}` }) }] }; break; } case 'step_over': case 'step_into': case 'step_out': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { let stepResult: boolean; if (toolName === 'step_over') { stepResult = await this.stepOver(args.sessionId); } else if (toolName === 'step_into') { stepResult = await this.stepInto(args.sessionId); } else { stepResult = await this.stepOut(args.sessionId); } result = { content: [{ type: 'text', text: JSON.stringify({ success: stepResult, message: stepResult ? `Stepped ${toolName.replace('step_', '')}` : `Failed to ${toolName.replace('_', ' ')}` }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else if (error instanceof Error) { // Handle other expected errors (like "Failed to step over") result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'continue_execution': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { const continueResult = await this.continueExecution(args.sessionId); result = { content: [{ type: 'text', text: JSON.stringify({ success: continueResult, message: continueResult ? 'Continued execution' : 'Failed to continue execution' }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else if (error instanceof Error) { // Handle other expected errors result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'pause_execution': { result = await this.handlePause(args as { sessionId: string }); break; } case 'get_variables': { if (!args.sessionId || args.scope === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const variables = await this.getVariables(args.sessionId, args.scope); // Log variable inspection (truncate large values) const truncatedVars = variables.map(v => ({ name: v.name, type: v.type, value: v.value.length > 200 ? v.value.substring(0, 200) + '... (truncated)' : v.value })); this.logger.info('debug:variables', { sessionId: args.sessionId, sessionName: this.getSessionName(args.sessionId), variablesReference: args.scope, variableCount: variables.length, variables: truncatedVars.slice(0, 10), // Log first 10 variables timestamp: Date.now() }); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, variables, count: variables.length, variablesReference: args.scope }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'get_stack_trace': { if (!args.sessionId) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required sessionId'); } try { const stackFrames = await this.getStackTrace(args.sessionId); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, stackFrames, count: stackFrames.length }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found') || error.message.includes('Cannot get stack trace'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'get_scopes': { if (!args.sessionId || args.frameId === undefined) { throw new McpError(McpErrorCode.InvalidParams, 'Missing required parameters'); } try { const scopes = await this.getScopes(args.sessionId, args.frameId); result = { content: [{ type: 'text', text: JSON.stringify({ success: true, scopes }) }] }; } catch (error) { // Handle validation errors specifically if (error instanceof McpError && (error.message.includes('terminated') || error.message.includes('closed') || error.message.includes('not found'))) { result = { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }) }] }; } else { // Re-throw unexpected errors throw error; } } break; } case 'evaluate_expression': { result = await this.handleEvaluateExpression(args as { sessionId: string; expression: string }); break; } case 'get_source_context': { result = await this.handleGetSourceContext(args as { sessionId: string; file: string; line: number; linesContext?: number }); break; } case 'list_supported_languages': { result = await this.handleListSupportedLanguages(); break; } default: throw new McpError(McpErrorCode.MethodNotFound, `Unknown tool: ${toolName}`); } // Log successful tool response this.logger.info('tool:response', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, success: true, timestamp: Date.now() }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Log tool error this.logger.error('tool:error', { tool: toolName, sessionId: args.sessionId, sessionName: args.sessionId ? this.getSessionName(args.sessionId) : undefined, error: errorMessage, timestamp: Date.now() }); if (error instanceof McpError) throw error; throw new McpError(McpErrorCode.InternalError, `Failed to execute tool ${toolName}: ${errorMessage}`); } } ); } private async handleListDebugSessions(): Promise<ServerResult> { try { const sessionsInfo: DebugSessionInfo[] = this.sessionManager.getAllSessions(); const sessionData = sessionsInfo.map((session: DebugSessionInfo) => { const mappedSession: Record<string, unknown> = { id: session.id, name: session.name, language: session.language as DebugLanguage, state: session.state, createdAt: session.createdAt.toISOString(), }; if (session.updatedAt) { mappedSession.updatedAt = session.updatedAt.toISOString(); } return mappedSession; }); return { content: [{ type: 'text', text: JSON.stringify({ success: true, sessions: sessionData, count: sessionData.length }) }] }; } catch (error) { this.logger.error('Failed to list debug sessions', { error }); throw new McpError(McpErrorCode.InternalError, `Failed to list debug sessions: ${(error as Error).message}`); } } private async handlePause(args: { sessionId: string }): Promise<ServerResult> { try { this.logger.info(`Pause requested for session: ${args.sessionId}`); throw new McpError(McpErrorCode.InternalError, "Pause execution not yet implemented with proxy."); } catch (error) { this.logger.error('Failed to pause execution', { error }); if (error instanceof McpError) throw error; throw new McpError(McpErrorCode.InternalError, `Failed to pause execution: ${(error as Error).message}`); } } private async handleEvaluateExpression(args: { sessionId: string, expression: string }): Promise<ServerResult> { try { this.logger.info(`Evaluate requested for session: ${args.sessionId}, expression: ${args.expression}`); throw new McpError(McpErrorCode.InternalError, "Evaluate expression not yet implemented with proxy."); } catch (error) { this.logger.error('Failed to evaluate expression', { error }); if (error instanceof McpError) throw error; throw new McpError(McpErrorCode.InternalError, `Failed to evaluate expression: ${(error as Error).message}`); } } private async handleGetSourceContext(args: { sessionId: string, file: string, line: number, linesContext?: number }): Promise<ServerResult> { try { // In container mode, prepend /workspace/ to the path let file = args.file; if (process.env.MCP_CONTAINER === 'true') { file = `/workspace/${file}`; } this.logger.info(`Source context requested for session: ${args.sessionId}, file: ${file}, line: ${args.line}`); throw new McpError(McpErrorCode.InternalError, "Get source context not yet implemented with proxy."); } catch (error) { this.logger.error('Failed to get source context', { error }); if (error instanceof McpError) throw error; throw new McpError(McpErrorCode.InternalError, `Failed to get source context: ${(error as Error).message}`); } } private async handleListSupportedLanguages(): Promise<ServerResult> { try { const languages = this.getLanguageMetadata(); return { content: [{ type: 'text', text: JSON.stringify({ success: true, languages, count: languages.length }) }] }; } catch (error) { this.logger.error('Failed to list supported languages', { error }); throw new McpError(McpErrorCode.InternalError, `Failed to list supported languages: ${(error as Error).message}`); } } /** * Public methods for server lifecycle management */ public async start(): Promise<void> { // For MCP servers, start is handled by transport this.logger.info('Debug MCP Server started'); } public async stop(): Promise<void> { await this.sessionManager.closeAllSessions(); this.logger.info('Debug MCP Server stopped'); } /** * Get adapter registry from session manager */ public getAdapterRegistry() { return this.sessionManager.adapterRegistry; } }