@codesys/mcp-toolkit 1.4.6 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.7] - 2025-05-02
9
+ ### Fixed
10
+ - Fixed create_project freezing in persistent sessions
11
+ - Added special detection and handling for project creation operations
12
+ - Extended timeout for project creation to 120 seconds (from 60 seconds)
13
+ - Added graceful handling of timeouts with partial success detection
14
+ - Improved debugging and monitoring of long-running operations in sessions
15
+
8
16
  ## [1.4.6] - 2025-05-02
9
17
  ### Fixed
10
18
  - Fixed session management "hanging" issue with start_session
@@ -229,9 +229,34 @@ def execute_command(command_data):
229
229
  "error": ""
230
230
  }
231
231
 
232
+ print("Executing command type: {0} with ID: {1}".format(cmd_type, cmd_id))
233
+ sys.stdout.flush()
234
+
232
235
  if cmd_type == "execute_script" and script:
233
236
  # Execute the script in the current CODESYS context
234
237
  try:
238
+ # Log the first few lines of the script for debugging
239
+ script_lines = script.split("\n")
240
+ print("Script preview (first 3 lines):")
241
+ for i in range(min(3, len(script_lines))):
242
+ print(" {0}: {1}".format(i+1, script_lines[i]))
243
+ sys.stdout.flush()
244
+
245
+ print("Script length: {0} chars, {1} lines".format(len(script), len(script_lines)))
246
+ sys.stdout.flush()
247
+
248
+ # Check for critical operations in the script
249
+ has_project_create = "copy2" in script or "shutil.copy" in script
250
+ has_project_open = "projects.open" in script
251
+
252
+ if has_project_create:
253
+ print("WARNING: Script contains project creation operations - these may require special handling")
254
+ sys.stdout.flush()
255
+
256
+ if has_project_open:
257
+ print("WARNING: Script contains project open operations - these may require special handling")
258
+ sys.stdout.flush()
259
+
235
260
  # Create a temporary globals dictionary with the current globals
236
261
  # This allows us to isolate the script execution somewhat
237
262
  local_globals = globals().copy()
@@ -250,17 +275,41 @@ def execute_command(command_data):
250
275
  old_stdout = sys.stdout
251
276
  old_stderr = sys.stderr
252
277
 
278
+ print("Execution starting - output will be captured")
279
+ sys.stdout.flush()
280
+
253
281
  # Replace with our buffers
254
282
  sys.stdout = stdout_buffer
255
283
  sys.stderr = stderr_buffer
256
284
 
257
- try:
258
- # Execute the script
259
- exec(script, local_globals)
260
- finally:
261
- # Restore original stdout and stderr
262
- sys.stdout = old_stdout
263
- sys.stderr = old_stderr
285
+ # For critical operations that might deadlock, we need to be more careful
286
+ if has_project_create or has_project_open:
287
+ # For project creation/opening, we'll execute the script with some guards
288
+ print("Executing critical script with guards...")
289
+ try:
290
+ # Execute the script
291
+ exec(script, local_globals)
292
+ except Exception as e:
293
+ # If the script fails, capture the error
294
+ print("ERROR in critical script execution: {0}".format(e))
295
+ traceback.print_exc()
296
+
297
+ print("Critical script execution finished")
298
+ else:
299
+ # For normal scripts, execute normally
300
+ try:
301
+ # Execute the script
302
+ exec(script, local_globals)
303
+ finally:
304
+ # No special handling needed
305
+ pass
306
+
307
+ # Restore original stdout and stderr
308
+ sys.stdout = old_stdout
309
+ sys.stderr = old_stderr
310
+
311
+ print("Execution completed - processing results")
312
+ sys.stdout.flush()
264
313
 
265
314
  # Get the captured output
266
315
  stdout_output = stdout_buffer.getvalue()
@@ -269,18 +318,28 @@ def execute_command(command_data):
269
318
  # Check for success/error markers
270
319
  if SCRIPT_SUCCESS_MARKER in stdout_output or SCRIPT_SUCCESS_MARKER in stderr_output:
271
320
  result["success"] = True
321
+ print("Found success marker in script output")
272
322
  elif SCRIPT_ERROR_MARKER in stdout_output or SCRIPT_ERROR_MARKER in stderr_output:
273
323
  result["success"] = False
324
+ print("Found error marker in script output")
274
325
  else:
275
326
  # No explicit markers, assume success
276
327
  result["success"] = True
328
+ print("No markers found in script output, assuming success")
329
+
330
+ # Tally output length to check if output is reasonable
331
+ print("Output length: stdout={0} chars, stderr={1} chars".format(
332
+ len(stdout_output), len(stderr_output)))
277
333
 
278
334
  result["output"] = stdout_output
279
335
  if stderr_output:
280
336
  result["error"] = stderr_output
337
+
338
+ sys.stdout.flush()
281
339
 
282
340
  except Exception as e:
283
341
  traceback.print_exc()
342
+ sys.stdout.flush()
284
343
  result["success"] = False
285
344
  result["error"] = "Error executing script: {0}\n{1}".format(str(e), traceback.format_exc())
286
345
  result["output"] = "{0}: {1}".format(SCRIPT_ERROR_MARKER, str(e))
package/dist/server.js CHANGED
@@ -66,6 +66,7 @@ const zod_1 = require("zod");
66
66
  const codesys_interop_1 = require("./codesys_interop");
67
67
  const path = __importStar(require("path"));
68
68
  const promises_1 = require("fs/promises");
69
+ const fs = __importStar(require("fs"));
69
70
  const scriptLoader_1 = require("./scriptLoader");
70
71
  const sessionManager_1 = require("./sessionManager");
71
72
  // --- Wrap server logic in an exported function ---
@@ -381,8 +382,50 @@ function startMcpServer(config) {
381
382
  'TEMPLATE_PROJECT_PATH': normalizedTemplatePath
382
383
  });
383
384
  console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
384
- const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
385
+ // Use a longer timeout for project creation operations in sessions (120 seconds)
386
+ // This helps prevent timing out on slower systems or when CODESYS is busy
387
+ let result;
388
+ // Check if we're running in a session
389
+ if (sessionManager &&
390
+ (sessionManager.getState() === sessionManager_1.SessionState.RUNNING ||
391
+ sessionManager.getState() === sessionManager_1.SessionState.BUSY)) {
392
+ console.error(">>> create_project: Using session with extended timeout (120s)");
393
+ try {
394
+ // Execute script directly through session manager to specify longer timeout
395
+ const scriptResult = yield sessionManager.executeScript(scriptContent, 120000);
396
+ result = {
397
+ success: scriptResult.success,
398
+ output: scriptResult.output
399
+ };
400
+ }
401
+ catch (sessionError) {
402
+ console.error(`>>> create_project: Session execution failed, falling back to one-time execution: ${sessionError.message}`);
403
+ // Fall back to one-time execution
404
+ result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
405
+ }
406
+ }
407
+ else {
408
+ // Standard execution without a session
409
+ result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
410
+ }
385
411
  console.error(">>> create_project (copy-then-open): EXECUTION RESULT:", JSON.stringify(result));
412
+ // Check if there was a timeout but the project might still have been created
413
+ if (!result.success && result.output.includes("timed out") && result.output.includes("project file might still have been created")) {
414
+ console.error(">>> create_project: Script timed out but project might have been created. Checking if file exists.");
415
+ // Check if the project file was actually created despite the timeout
416
+ const fileExists = fs.existsSync(absPath);
417
+ if (fileExists) {
418
+ console.error(`>>> create_project: Project file ${absPath} exists despite timeout.`);
419
+ return {
420
+ content: [{
421
+ type: "text",
422
+ text: `Project file was created at ${absPath}, but the operation timed out before completion. ` +
423
+ `The project might be usable, but you may need to manually open it in CODESYS.`
424
+ }],
425
+ isError: true // Still mark as error because operation didn't complete properly
426
+ };
427
+ }
428
+ }
386
429
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
387
430
  return {
388
431
  content: [{
@@ -667,9 +667,19 @@ class CodesysSessionManager extends events_1.EventEmitter {
667
667
  */
668
668
  readResultFile(scriptId) {
669
669
  console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
670
+ // Count polling attempts for logging purposes
671
+ let pollCount = 0;
672
+ let lastLogTime = Date.now();
670
673
  // We'll use a polling approach to read from the file
671
674
  const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
672
675
  try {
676
+ pollCount++;
677
+ const now = Date.now();
678
+ // Log progress every 5 seconds to show we're still working
679
+ if (now - lastLogTime > 5000) {
680
+ console.error(`SESSION_MANAGER: Still polling for result of script ${scriptId}, attempt ${pollCount}`);
681
+ lastLogTime = now;
682
+ }
673
683
  if (!this.pendingScripts.has(scriptId)) {
674
684
  console.error(`SESSION_MANAGER: Script ${scriptId} is no longer pending, stopping poll`);
675
685
  clearInterval(pollInterval);
@@ -677,7 +687,9 @@ class CodesysSessionManager extends events_1.EventEmitter {
677
687
  }
678
688
  // Check if the result file exists
679
689
  if (!fs.existsSync(this.resultFilePath)) {
680
- console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath}`);
690
+ if (pollCount % 50 === 0) { // Log less frequently
691
+ console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath} (attempt ${pollCount})`);
692
+ }
681
693
  return;
682
694
  }
683
695
  // Read the result file
@@ -689,7 +701,7 @@ class CodesysSessionManager extends events_1.EventEmitter {
689
701
  if (resultData.status === 'complete' && resultData.result) {
690
702
  const result = resultData.result;
691
703
  if (result.id === scriptId) {
692
- console.error(`SESSION_MANAGER: Found result for script ${scriptId}`);
704
+ console.error(`SESSION_MANAGER: Found result for script ${scriptId} after ${pollCount} polls`);
693
705
  // Remove from pending scripts
694
706
  if (this.pendingScripts.has(scriptId)) {
695
707
  const pending = this.pendingScripts.get(scriptId);
@@ -717,9 +729,36 @@ class CodesysSessionManager extends events_1.EventEmitter {
717
729
  console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
718
730
  }
719
731
  }), 100); // Poll every 100ms
720
- // Set a timeout to stop polling after the script timeout
732
+ // Set a timeout to stop polling and forcibly resolve/reject the script after the script timeout
721
733
  setTimeout(() => {
734
+ console.error(`SESSION_MANAGER: Max polling time reached for script ${scriptId}, cleaning up`);
722
735
  clearInterval(pollInterval);
736
+ // If the script is still pending, force resolve/reject with timeout error
737
+ if (this.pendingScripts.has(scriptId)) {
738
+ const pending = this.pendingScripts.get(scriptId);
739
+ this.pendingScripts.delete(scriptId);
740
+ // Create a timeout result
741
+ const timeoutResult = {
742
+ id: scriptId,
743
+ success: false,
744
+ output: `SCRIPT_ERROR: Script execution timed out after reaching maximum polling time. This typically happens when:\n` +
745
+ `1. The script is stuck in an infinite loop\n` +
746
+ `2. The script has triggered a deadlock in CODESYS\n` +
747
+ `3. A CODESYS dialog or error popup is blocking execution\n` +
748
+ `4. The project creation or opening is taking too long\n\n` +
749
+ `If this is a project creation script, the project file might still have been created successfully.\n` +
750
+ `Check the CODESYS UI to see if a project was created and manually close any dialog boxes.`
751
+ };
752
+ // Resolve with the timeout result rather than rejecting
753
+ // This allows the session to continue operating instead of going into error state
754
+ console.error(`SESSION_MANAGER: Resolving script ${scriptId} with timeout result`);
755
+ pending.resolve(timeoutResult);
756
+ }
757
+ // Check if we need to update session state
758
+ if (this.pendingScripts.size === 0) {
759
+ console.error(`SESSION_MANAGER: No more pending scripts, setting state to RUNNING`);
760
+ this.sessionState = exports.SessionState.RUNNING;
761
+ }
723
762
  }, 60000); // 1 minute max polling time
724
763
  }
725
764
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {