diff --git a/python/commands/schematic.py b/python/commands/schematic.py index edeb5ee..e481a60 100644 --- a/python/commands/schematic.py +++ b/python/commands/schematic.py @@ -1,5 +1,8 @@ from skip import Schematic import os +import logging + +logger = logging.getLogger('kicad_interface') class SchematicManager: """Core schematic operations using kicad-skip""" @@ -33,21 +36,21 @@ class SchematicManager: # For now, we'll just create the schematic. pass # Placeholder for potential metadata handling - print(f"Created new schematic: {name}") + logger.info(f"Created new schematic: {name}") return sch @staticmethod def load_schematic(file_path): """Load an existing schematic""" if not os.path.exists(file_path): - print(f"Error: Schematic file not found at {file_path}") + logger.error(f"Schematic file not found at {file_path}") return None try: sch = Schematic(file_path) - print(f"Loaded schematic from: {file_path}") + logger.info(f"Loaded schematic from: {file_path}") return sch except Exception as e: - print(f"Error loading schematic from {file_path}: {e}") + logger.error(f"Error loading schematic from {file_path}: {e}") return None @staticmethod @@ -56,10 +59,10 @@ class SchematicManager: try: # kicad-skip uses write method, not save schematic.write(file_path) - print(f"Saved schematic to: {file_path}") + logger.info(f"Saved schematic to: {file_path}") return True except Exception as e: - print(f"Error saving schematic to {file_path}: {e}") + logger.error(f"Error saving schematic to {file_path}: {e}") return False @staticmethod @@ -72,7 +75,7 @@ class SchematicManager: "generator": schematic.generator, # Add other relevant properties if needed } - print("Extracted schematic metadata") + logger.debug("Extracted schematic metadata") return metadata if __name__ == '__main__': diff --git a/src/index.ts b/src/index.ts index 6f31054..345ebce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,7 +71,7 @@ function setupGracefulShutdown(server: KiCADMcpServer) { // Handle termination signals process.on('SIGINT', async () => { logger.info('Received SIGINT signal. Shutting down...'); - await shutdownServer(server); + //await shutdownServer(server); }); process.on('SIGTERM', async () => { diff --git a/src/kicad-server.ts b/src/kicad-server.ts index 348d1c5..5dca363 100644 --- a/src/kicad-server.ts +++ b/src/kicad-server.ts @@ -308,10 +308,10 @@ class KiCADServer { console.error(`Using Python executable: ${pythonExe}`); this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], { stdio: ['pipe', 'pipe', 'pipe'], - env: { + /*env: { ...process.env, PYTHONPATH: 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages' - } + }*/ }); // Listen for process exit diff --git a/src/server.ts b/src/server.ts index d8e27a8..9cf872a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -82,6 +82,8 @@ export class KiCADMcpServer { private stdioTransport!: StdioServerTransport; private requestQueue: Array<{ request: any, resolve: Function, reject: Function }> = []; private processingRequest = false; + private responseBuffer: string = ''; + private currentRequestHandler: { resolve: Function, reject: Function, timeoutHandle: NodeJS.Timeout } | null = null; /** * Constructor for the KiCAD MCP Server @@ -267,10 +269,10 @@ export class KiCADMcpServer { } this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], { stdio: ['pipe', 'pipe', 'pipe'], - env: { + /*env: { ...process.env, PYTHONPATH: process.env.PYTHONPATH || 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages' - } + }*/ }); // Listen for process exit @@ -290,7 +292,14 @@ export class KiCADMcpServer { logger.error(`Python stderr: ${data.toString()}`); }); } - + + // Set up persistent stdout handler (instead of adding/removing per request) + if (this.pythonProcess.stdout) { + this.pythonProcess.stdout.on('data', (data: Buffer) => { + this.handlePythonResponse(data); + }); + } + // Connect server to STDIO transport logger.info('Connecting MCP server to STDIO transport...'); try { @@ -365,6 +374,64 @@ export class KiCADMcpServer { }); } + /** + * Handle incoming data from Python process stdout + * This is a persistent handler that processes all responses + */ + private handlePythonResponse(data: Buffer): void { + const chunk = data.toString(); + logger.debug(`Received data chunk: ${chunk.length} bytes`); + this.responseBuffer += chunk; + + // Try to parse complete JSON responses (may have multiple or partial) + this.tryParseResponse(); + } + + /** + * Try to parse a complete JSON response from the buffer + */ + private tryParseResponse(): void { + if (!this.currentRequestHandler) { + // No pending request, clear buffer if it has data (shouldn't happen) + if (this.responseBuffer.trim()) { + logger.warn(`Received data with no pending request: ${this.responseBuffer.substring(0, 100)}...`); + this.responseBuffer = ''; + } + return; + } + + try { + // Try to parse the response as JSON + const result = JSON.parse(this.responseBuffer); + + // If we get here, we have a valid JSON response + logger.debug(`Completed KiCAD command with result: ${result.success ? 'success' : 'failure'}`); + + // Clear the timeout since we got a response + if (this.currentRequestHandler.timeoutHandle) { + clearTimeout(this.currentRequestHandler.timeoutHandle); + } + + // Get the handler before clearing + const handler = this.currentRequestHandler; + + // Clear state + this.responseBuffer = ''; + this.currentRequestHandler = null; + this.processingRequest = false; + + // Resolve the promise with the result + handler.resolve(result); + + // Process next request if any + setTimeout(() => this.processNextRequest(), 0); + + } catch (e) { + // Not a complete JSON yet, keep collecting data + // This is normal for large responses that come in chunks + } + } + /** * Process the next request in the queue */ @@ -373,102 +440,56 @@ export class KiCADMcpServer { if (this.requestQueue.length === 0 || this.processingRequest) { return; } - + // Set processing flag this.processingRequest = true; - + // Get the next request const { request, resolve, reject } = this.requestQueue.shift()!; - + try { logger.debug(`Processing KiCAD command: ${request.command}`); - + // Format the command and parameters as JSON const requestStr = JSON.stringify(request); - - // Set up response handling - let responseData = ''; - let timeoutHandle: NodeJS.Timeout | null = null; - // Clear any previous listeners - if (this.pythonProcess?.stdout) { - this.pythonProcess.stdout.removeAllListeners('data'); - this.pythonProcess.stdout.removeAllListeners('end'); - } - - // Set up new listeners - if (this.pythonProcess?.stdout) { - this.pythonProcess.stdout.on('data', (data: Buffer) => { - const chunk = data.toString(); - logger.debug(`Received data chunk: ${chunk.length} bytes`); - responseData += chunk; - - // Check if we have a complete response - try { - // Try to parse the response as JSON - const result = JSON.parse(responseData); - - // If we get here, we have a valid JSON response - logger.debug(`Completed KiCAD command: ${request.command} with result: ${result.success ? 'success' : 'failure'}`); - - // Clear the timeout since we got a response - if (timeoutHandle) { - clearTimeout(timeoutHandle); - } - - // Reset processing flag - this.processingRequest = false; - - // Process next request if any - setTimeout(() => this.processNextRequest(), 0); - - // Clear listeners - if (this.pythonProcess?.stdout) { - this.pythonProcess.stdout.removeAllListeners('data'); - this.pythonProcess.stdout.removeAllListeners('end'); - } - - // Resolve the promise with the result - resolve(result); - } catch (e) { - // Not a complete JSON yet, keep collecting data - } - }); - } + // Clear response buffer for new request + this.responseBuffer = ''; // Set a timeout (use command-specific timeout or default) const timeoutDuration = request.timeout || 30000; - timeoutHandle = setTimeout(() => { + const timeoutHandle = setTimeout(() => { logger.error(`Command timeout after ${timeoutDuration/1000}s: ${request.command}`); + logger.error(`Buffer contents: ${this.responseBuffer.substring(0, 200)}...`); - // Clear listeners - if (this.pythonProcess?.stdout) { - this.pythonProcess.stdout.removeAllListeners('data'); - this.pythonProcess.stdout.removeAllListeners('end'); - } - - // Reset processing flag + // Clear state + this.responseBuffer = ''; + this.currentRequestHandler = null; this.processingRequest = false; - // Process next request - setTimeout(() => this.processNextRequest(), 0); - // Reject the promise reject(new Error(`Command timeout after ${timeoutDuration/1000}s: ${request.command}`)); + + // Process next request + setTimeout(() => this.processNextRequest(), 0); }, timeoutDuration); - + + // Store the current request handler + this.currentRequestHandler = { resolve, reject, timeoutHandle }; + // Write the request to the Python process logger.debug(`Sending request: ${requestStr}`); this.pythonProcess?.stdin?.write(requestStr + '\n'); } catch (error) { logger.error(`Error processing request: ${error}`); - + // Reset processing flag this.processingRequest = false; - + this.currentRequestHandler = null; + // Process next request setTimeout(() => this.processNextRequest(), 0); - + // Reject the promise reject(error); }