@codesys/mcp-toolkit 1.4.1 → 1.4.3

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,21 @@ 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.3] - 2025-05-02
9
+ ### Fixed
10
+ - Fixed "Cannot import name redirect_stdout" error in session script execution
11
+ - Replaced contextlib.redirect_stdout/redirect_stderr with direct sys.stdout/stderr assignment for IronPython 2.7 compatibility
12
+ - Improved StringIO import handling for better Python 2/3 compatibility
13
+
14
+ ## [1.4.2] - 2025-05-02
15
+ ### Fixed
16
+ - Enhanced session communication with better logging and debugging
17
+ - Improved command file handling and error recovery
18
+ - Fixed path handling for session files
19
+ - Added JSON fallback parser for IronPython 2.7
20
+ - Ensured synchronous script execution to prevent conflicts
21
+ - Increased reliability of session status detection
22
+
8
23
  ## [1.4.1] - 2025-05-02
9
24
  ### Fixed
10
25
  - Fixed IronPython 2.7 compatibility for session management scripts
@@ -6,7 +6,56 @@ try:
6
6
  import json
7
7
  except ImportError:
8
8
  # For IronPython 2.7, we need to use simplejson instead
9
- import simplejson as json
9
+ try:
10
+ import simplejson as json
11
+ except ImportError:
12
+ print("Neither json nor simplejson could be imported. Check IronPython installation.")
13
+ # Provide a minimal fallback implementation for basic needs
14
+ class DummyJson:
15
+ @staticmethod
16
+ def load(file_obj):
17
+ content = file_obj.read()
18
+ # Very basic parsing for our limited needs
19
+ if content.strip().startswith("{") and content.strip().endswith("}"):
20
+ result = {}
21
+ parts = content.strip()[1:-1].split(",")
22
+ for part in parts:
23
+ if ":" in part:
24
+ k, v = part.split(":", 1)
25
+ k = k.strip().strip('"\'')
26
+ v = v.strip().strip('"\'')
27
+ result[k] = v
28
+ return result
29
+ return {}
30
+
31
+ @staticmethod
32
+ def loads(s):
33
+ if s.strip().startswith("{") and s.strip().endswith("}"):
34
+ result = {}
35
+ parts = s.strip()[1:-1].split(",")
36
+ for part in parts:
37
+ if ":" in part:
38
+ k, v = part.split(":", 1)
39
+ k = k.strip().strip('"\'')
40
+ v = v.strip().strip('"\'')
41
+ result[k] = v
42
+ return result
43
+ return {}
44
+
45
+ @staticmethod
46
+ def dump(obj, file_obj, indent=None):
47
+ if isinstance(obj, dict):
48
+ pairs = []
49
+ for k, v in obj.items():
50
+ if isinstance(v, str):
51
+ pairs.append('"{0}": "{1}"'.format(k, v))
52
+ else:
53
+ pairs.append('"{0}": {1}'.format(k, v))
54
+ file_obj.write("{" + ", ".join(pairs) + "}")
55
+ else:
56
+ file_obj.write(str(obj))
57
+
58
+ json = DummyJson()
10
59
 
11
60
  # Define markers for script results
12
61
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
@@ -19,6 +68,15 @@ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
19
68
  RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
20
69
  SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
21
70
 
71
+ # Print key information at start
72
+ print("Session status check script starting")
73
+ print("tempdir: {0}".format(tempfile.gettempdir()))
74
+ print("SESSION_DIR: {0}".format(SESSION_DIR))
75
+ print("SESSION_STATE_FILE: {0}".format(SESSION_STATE_FILE))
76
+ print("COMMAND_FILE: {0}".format(COMMAND_FILE))
77
+ print("RESULT_FILE: {0}".format(RESULT_FILE))
78
+ print("SESSION_LOCK_FILE: {0}".format(SESSION_LOCK_FILE))
79
+
22
80
  def check_session_status():
23
81
  """Check the status of a CODESYS session"""
24
82
  try:
@@ -5,16 +5,71 @@ try:
5
5
  import json
6
6
  except ImportError:
7
7
  # For IronPython 2.7, we need to use simplejson instead
8
- import simplejson as json
8
+ try:
9
+ import simplejson as json
10
+ except ImportError:
11
+ print("Neither json nor simplejson could be imported. Check IronPython installation.")
12
+ # Provide a minimal fallback implementation for basic needs
13
+ class DummyJson:
14
+ @staticmethod
15
+ def load(file_obj):
16
+ content = file_obj.read()
17
+ # Very basic parsing for our limited needs
18
+ if content.strip().startswith("{") and content.strip().endswith("}"):
19
+ result = {}
20
+ parts = content.strip()[1:-1].split(",")
21
+ for part in parts:
22
+ if ":" in part:
23
+ k, v = part.split(":", 1)
24
+ k = k.strip().strip('"\'')
25
+ v = v.strip().strip('"\'')
26
+ result[k] = v
27
+ return result
28
+ return {}
29
+
30
+ @staticmethod
31
+ def loads(s):
32
+ if s.strip().startswith("{") and s.strip().endswith("}"):
33
+ result = {}
34
+ parts = s.strip()[1:-1].split(",")
35
+ for part in parts:
36
+ if ":" in part:
37
+ k, v = part.split(":", 1)
38
+ k = k.strip().strip('"\'')
39
+ v = v.strip().strip('"\'')
40
+ result[k] = v
41
+ return result
42
+ return {}
43
+
44
+ @staticmethod
45
+ def dump(obj, file_obj, indent=None):
46
+ if isinstance(obj, dict):
47
+ pairs = []
48
+ for k, v in obj.items():
49
+ if isinstance(v, str):
50
+ pairs.append('"{0}": "{1}"'.format(k, v))
51
+ else:
52
+ pairs.append('"{0}": {1}'.format(k, v))
53
+ file_obj.write("{" + ", ".join(pairs) + "}")
54
+ else:
55
+ file_obj.write(str(obj))
56
+
57
+ json = DummyJson()
9
58
 
10
59
  # Import scriptengine
11
60
  try:
12
61
  import scriptengine as script_engine
62
+ print("Successfully imported scriptengine module")
13
63
  except ImportError:
14
64
  # Log the error and continue - we'll handle this gracefully
15
65
  print("Error importing scriptengine. This script must be run within CODESYS environment.")
16
66
  script_engine = None
17
67
 
68
+ # Print Python and platform info
69
+ print("Python version: {0}.{1}.{2}".format(sys.version_info[0], sys.version_info[1], sys.version_info[2]))
70
+ print("Platform: {0}".format(sys.platform))
71
+ print("Executable: {0}".format(sys.executable))
72
+
18
73
  # Define markers for script results
19
74
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
20
75
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
@@ -181,16 +236,31 @@ def execute_command(command_data):
181
236
  # This allows us to isolate the script execution somewhat
182
237
  local_globals = globals().copy()
183
238
 
184
- # Capture output by redirecting stdout/stderr
185
- import io
186
- from contextlib import redirect_stdout, redirect_stderr
239
+ # Capture output by temporarily replacing stdout/stderr
240
+ # Use custom approach compatible with IronPython 2.7
241
+ try:
242
+ import StringIO as io_module # Python 2.x
243
+ except ImportError:
244
+ import io as io_module # Python 3.x
245
+
246
+ stdout_buffer = io_module.StringIO()
247
+ stderr_buffer = io_module.StringIO()
187
248
 
188
- stdout_buffer = io.StringIO()
189
- stderr_buffer = io.StringIO()
249
+ # Save original stdout and stderr
250
+ old_stdout = sys.stdout
251
+ old_stderr = sys.stderr
190
252
 
191
- with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
253
+ # Replace with our buffers
254
+ sys.stdout = stdout_buffer
255
+ sys.stderr = stderr_buffer
256
+
257
+ try:
192
258
  # Execute the script
193
259
  exec(script, local_globals)
260
+ finally:
261
+ # Restore original stdout and stderr
262
+ sys.stdout = old_stdout
263
+ sys.stderr = old_stderr
194
264
 
195
265
  # Get the captured output
196
266
  stdout_output = stdout_buffer.getvalue()
@@ -255,67 +325,128 @@ def check_command():
255
325
  try:
256
326
  # If command file doesn't exist, return immediately
257
327
  if not os.path.exists(COMMAND_FILE):
328
+ print("Command file does not exist at {0}".format(COMMAND_FILE))
258
329
  return False
259
330
 
260
331
  # Read the command file
261
332
  try:
333
+ print("Reading command file: {0}".format(COMMAND_FILE))
262
334
  with open(COMMAND_FILE, 'r') as f:
263
- command_data = json.load(f)
264
- except json.JSONDecodeError:
265
- # If command file is corrupted, reset it
335
+ file_content = f.read()
336
+ print("Raw command file content: {0}".format(file_content[:100] if len(file_content) > 100 else file_content))
337
+
338
+ # Check if file is empty
339
+ if not file_content.strip():
340
+ print("Command file is empty, resetting")
341
+ command_data = {"status": "ready", "command": None, "timestamp": time.time()}
342
+ with open(COMMAND_FILE, 'w') as fw:
343
+ json.dump(command_data, fw, indent=2)
344
+ return False
345
+
346
+ # Try to parse JSON
347
+ try:
348
+ command_data = json.loads(file_content)
349
+ except Exception as json_err:
350
+ print("Error parsing command file JSON: {0}".format(json_err))
351
+ # If command file is corrupted, reset it
352
+ command_data = {"status": "ready", "command": None, "timestamp": time.time()}
353
+ with open(COMMAND_FILE, 'w') as fw:
354
+ json.dump(command_data, fw, indent=2)
355
+ return False
356
+ except Exception as read_err:
357
+ print("Error reading command file: {0}".format(read_err))
358
+ # Reset the command file
266
359
  command_data = {"status": "ready", "command": None, "timestamp": time.time()}
267
- with open(COMMAND_FILE, 'w') as f:
268
- json.dump(command_data, f, indent=2)
360
+ try:
361
+ with open(COMMAND_FILE, 'w') as fw:
362
+ json.dump(command_data, fw, indent=2)
363
+ except Exception as write_err:
364
+ print("Error writing reset command file: {0}".format(write_err))
269
365
  return False
270
366
 
271
367
  # Check if there's a new command
272
- if command_data.get("status") == "pending" and command_data.get("command"):
273
- # Extract the command
274
- command = command_data.get("command")
275
-
276
- # Mark as processing
277
- command_data["status"] = "processing"
278
- with open(COMMAND_FILE, 'w') as f:
279
- json.dump(command_data, f, indent=2)
280
-
281
- # Update session state to busy
282
- update_session_state("busy")
283
-
284
- # Execute the command
285
- result, stop_requested = execute_command(command)
286
-
287
- # Write the result
288
- result_data = {
289
- "status": "complete",
290
- "result": result,
291
- "timestamp": time.time()
292
- }
293
-
294
- with open(RESULT_FILE, 'w') as f:
295
- json.dump(result_data, f, indent=2)
296
-
297
- # Reset command file
298
- command_data = {
299
- "status": "ready",
300
- "command": None,
301
- "timestamp": time.time()
302
- }
303
-
304
- with open(COMMAND_FILE, 'w') as f:
305
- json.dump(command_data, f, indent=2)
306
-
307
- # Update session state to running again
308
- if not stop_requested:
309
- update_session_state("running")
368
+ status = command_data.get("status")
369
+ command = command_data.get("command")
370
+
371
+ print("Command status: {0}".format(status))
372
+ if command:
373
+ print("Command found: {0}".format(command.get("type", "unknown")))
310
374
 
311
- # Update last activity time
312
- update_last_activity()
375
+ if status == "pending" and command:
376
+ print("Processing pending command...")
313
377
 
314
- return stop_requested
378
+ try:
379
+ # Mark as processing
380
+ command_data["status"] = "processing"
381
+ with open(COMMAND_FILE, 'w') as f:
382
+ json.dump(command_data, f, indent=2)
383
+ print("Updated command status to 'processing'")
384
+
385
+ # Update session state to busy
386
+ update_session_state("busy")
387
+
388
+ # Execute the command
389
+ print("Executing command: {0}".format(command.get("type", "unknown")))
390
+ result, stop_requested = execute_command(command)
391
+ print("Command execution complete, stop_requested: {0}".format(stop_requested))
392
+
393
+ # Write the result
394
+ result_data = {
395
+ "status": "complete",
396
+ "result": result,
397
+ "timestamp": time.time()
398
+ }
399
+
400
+ print("Writing result to {0}".format(RESULT_FILE))
401
+ with open(RESULT_FILE, 'w') as f:
402
+ json.dump(result_data, f, indent=2)
403
+
404
+ # Reset command file
405
+ command_data = {
406
+ "status": "ready",
407
+ "command": None,
408
+ "timestamp": time.time()
409
+ }
410
+
411
+ print("Resetting command file")
412
+ with open(COMMAND_FILE, 'w') as f:
413
+ json.dump(command_data, f, indent=2)
414
+
415
+ # Update session state to running again
416
+ if not stop_requested:
417
+ update_session_state("running")
418
+
419
+ # Update last activity time
420
+ update_last_activity()
421
+
422
+ return stop_requested
423
+ except Exception as cmd_err:
424
+ print("Error processing command: {0}".format(cmd_err))
425
+ traceback.print_exc()
426
+
427
+ # Try to reset the command file
428
+ try:
429
+ command_data = {
430
+ "status": "ready",
431
+ "command": None,
432
+ "timestamp": time.time()
433
+ }
434
+ with open(COMMAND_FILE, 'w') as f:
435
+ json.dump(command_data, f, indent=2)
436
+ except Exception as reset_err:
437
+ print("Error resetting command file: {0}".format(reset_err))
438
+
439
+ # Try to restore session state
440
+ try:
441
+ update_session_state("running")
442
+ except Exception as state_err:
443
+ print("Error updating session state: {0}".format(state_err))
444
+
445
+ return False
315
446
 
316
447
  return False
317
448
  except Exception as e:
318
- print("Error checking command: {0}".format(e))
449
+ print("Error in check_command: {0}".format(e))
319
450
  traceback.print_exc()
320
451
  return False
321
452
 
@@ -356,19 +487,61 @@ def command_loop():
356
487
  """Main loop to check for and execute commands"""
357
488
  stop_requested = False
358
489
  last_state_update = time.time()
490
+ loop_count = 0
491
+ last_heartbeat = time.time()
359
492
 
360
493
  print("Starting command loop...")
361
494
  print("Command file: {0}".format(COMMAND_FILE))
362
495
  print("Result file: {0}".format(RESULT_FILE))
496
+ print("Session lock file: {0}".format(SESSION_LOCK_FILE))
497
+ print("Session state file: {0}".format(SESSION_STATE_FILE))
498
+ print("Poll interval: {0} seconds".format(POLL_INTERVAL))
499
+ print("Max inactivity time: {0} seconds".format(MAX_INACTIVITY_TIME))
500
+
501
+ # Set up initial session state files
502
+ try:
503
+ # Create initial command file if it doesn't exist
504
+ if not os.path.exists(COMMAND_FILE):
505
+ with open(COMMAND_FILE, 'w') as f:
506
+ json.dump({
507
+ "status": "ready",
508
+ "command": None,
509
+ "timestamp": time.time()
510
+ }, f, indent=2)
511
+ print("Created initial command file")
512
+
513
+ # Create initial result file if it doesn't exist
514
+ if not os.path.exists(RESULT_FILE):
515
+ with open(RESULT_FILE, 'w') as f:
516
+ json.dump({
517
+ "status": "none",
518
+ "result": None,
519
+ "timestamp": time.time()
520
+ }, f, indent=2)
521
+ print("Created initial result file")
522
+ except Exception as setup_err:
523
+ print("Error setting up initial files: {0}".format(setup_err))
524
+ traceback.print_exc()
363
525
 
364
526
  while not stop_requested:
365
527
  try:
528
+ loop_count += 1
529
+ current_time = time.time()
530
+
366
531
  # Update session lock file to indicate we're alive
367
532
  with open(SESSION_LOCK_FILE, 'w') as f:
368
- f.write(str(time.time()))
533
+ f.write(str(current_time))
534
+
535
+ # Print heartbeat message every 60 seconds
536
+ if current_time - last_heartbeat > 60:
537
+ print("Command loop heartbeat - still alive (iteration {0})".format(loop_count))
538
+ last_heartbeat = current_time
369
539
 
370
540
  # Check for new commands
541
+ print("Checking for commands...")
371
542
  stop_requested = check_command()
543
+ if stop_requested:
544
+ print("Stop was requested by a command")
372
545
 
373
546
  # Check for inactivity timeout
374
547
  if check_inactivity_timeout():
@@ -377,8 +550,8 @@ def command_loop():
377
550
  stop_requested = True
378
551
 
379
552
  # Periodically update session state (every 30 seconds)
380
- current_time = time.time()
381
553
  if (current_time - last_state_update) > 30:
554
+ print("Periodic session state update")
382
555
  update_session_state()
383
556
  last_state_update = current_time
384
557
 
@@ -388,10 +561,10 @@ def command_loop():
388
561
  except Exception as e:
389
562
  print("Error in command loop: {0}".format(e))
390
563
  traceback.print_exc()
391
- # Sleep for a bit
564
+ # Sleep for a bit before retrying
392
565
  time.sleep(POLL_INTERVAL)
393
566
 
394
- print("Command loop stopped.")
567
+ print("Command loop stopped after {0} iterations".format(loop_count))
395
568
 
396
569
  # Main script
397
570
  def main():
@@ -6,7 +6,61 @@ try:
6
6
  import json
7
7
  except ImportError:
8
8
  # For IronPython 2.7, we need to use simplejson instead
9
- import simplejson as json
9
+ try:
10
+ import simplejson as json
11
+ except ImportError:
12
+ print("Neither json nor simplejson could be imported. Check IronPython installation.")
13
+ # Provide a minimal fallback implementation for basic needs
14
+ class DummyJson:
15
+ @staticmethod
16
+ def load(file_obj):
17
+ content = file_obj.read()
18
+ # Very basic parsing for our limited needs
19
+ if content.strip().startswith("{") and content.strip().endswith("}"):
20
+ result = {}
21
+ parts = content.strip()[1:-1].split(",")
22
+ for part in parts:
23
+ if ":" in part:
24
+ k, v = part.split(":", 1)
25
+ k = k.strip().strip('"\'')
26
+ v = v.strip().strip('"\'')
27
+ result[k] = v
28
+ return result
29
+ return {}
30
+
31
+ @staticmethod
32
+ def loads(s):
33
+ if s.strip().startswith("{") and s.strip().endswith("}"):
34
+ result = {}
35
+ parts = s.strip()[1:-1].split(",")
36
+ for part in parts:
37
+ if ":" in part:
38
+ k, v = part.split(":", 1)
39
+ k = k.strip().strip('"\'')
40
+ v = v.strip().strip('"\'')
41
+ result[k] = v
42
+ return result
43
+ return {}
44
+
45
+ @staticmethod
46
+ def dump(obj, file_obj, indent=None):
47
+ if isinstance(obj, dict):
48
+ pairs = []
49
+ for k, v in obj.items():
50
+ if isinstance(v, str):
51
+ pairs.append('"{0}": "{1}"'.format(k, v))
52
+ else:
53
+ pairs.append('"{0}": {1}'.format(k, v))
54
+ file_obj.write("{" + ", ".join(pairs) + "}")
55
+ else:
56
+ file_obj.write(str(obj))
57
+
58
+ json = DummyJson()
59
+
60
+ # Print Python and platform info
61
+ print("Python version: {0}.{1}.{2}".format(sys.version_info[0], sys.version_info[1], sys.version_info[2]))
62
+ print("Platform: {0}".format(sys.platform))
63
+ print("Executable: {0}".format(sys.executable))
10
64
 
11
65
  # Define markers for script results
12
66
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
@@ -19,6 +73,15 @@ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
19
73
  RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
20
74
  SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
21
75
 
76
+ # Print key information at start
77
+ print("Session stop script starting")
78
+ print("tempdir: {0}".format(tempfile.gettempdir()))
79
+ print("SESSION_DIR: {0}".format(SESSION_DIR))
80
+ print("SESSION_STATE_FILE: {0}".format(SESSION_STATE_FILE))
81
+ print("COMMAND_FILE: {0}".format(COMMAND_FILE))
82
+ print("RESULT_FILE: {0}".format(RESULT_FILE))
83
+ print("SESSION_LOCK_FILE: {0}".format(SESSION_LOCK_FILE))
84
+
22
85
  def stop_session():
23
86
  """Stop a running CODESYS session"""
24
87
  try:
@@ -122,7 +122,8 @@ class CodesysSessionManager extends events_1.EventEmitter {
122
122
  this.idleTimeoutMs = idleTimeoutMs;
123
123
  this.sessionId = (0, uuid_1.v4)();
124
124
  // Set up session directory and files
125
- this.sessionDir = path.join(SESSION_DIR, this.sessionId);
125
+ // IMPORTANT: Don't use a subdirectory - Python scripts expect files directly in SESSION_DIR
126
+ this.sessionDir = SESSION_DIR;
126
127
  this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
127
128
  this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
128
129
  this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
@@ -442,16 +443,27 @@ class CodesysSessionManager extends events_1.EventEmitter {
442
443
  }, 2000); // Give it 2 seconds to terminate gracefully
443
444
  });
444
445
  }
445
- // Clean up session directory
446
+ // Clean up session files (not entire directory)
446
447
  try {
447
448
  if (fs.existsSync(this.sessionDir)) {
448
- console.error(`SESSION_MANAGER: Cleaning up session directory: ${this.sessionDir}`);
449
- // Use recursive: true once we're on Node.js 14+
450
- this.recursiveRemoveDir(this.sessionDir);
449
+ console.error(`SESSION_MANAGER: Cleaning up session files in: ${this.sessionDir}`);
450
+ // Remove only the session-related files, not the entire directory
451
+ const filesToRemove = [
452
+ this.commandFilePath,
453
+ this.resultFilePath,
454
+ this.stateFilePath,
455
+ this.lockFilePath
456
+ ];
457
+ for (const filePath of filesToRemove) {
458
+ if (fs.existsSync(filePath)) {
459
+ fs.unlinkSync(filePath);
460
+ console.error(`SESSION_MANAGER: Removed session file: ${filePath}`);
461
+ }
462
+ }
451
463
  }
452
464
  }
453
465
  catch (error) {
454
- console.error(`SESSION_MANAGER: Error cleaning up session directory: ${error}`);
466
+ console.error(`SESSION_MANAGER: Error cleaning up session files: ${error}`);
455
467
  }
456
468
  this.codesysProcess = null;
457
469
  this.sessionState = exports.SessionState.STOPPED;
@@ -524,8 +536,6 @@ class CodesysSessionManager extends events_1.EventEmitter {
524
536
  }
525
537
  // Reset idle timeout since we're about to do work
526
538
  this.resetIdleTimeout();
527
- // Mark the session as busy
528
- this.sessionState = exports.SessionState.BUSY;
529
539
  // Check if the command file exists
530
540
  if (!fs.existsSync(this.commandFilePath)) {
531
541
  this.sessionState = exports.SessionState.ERROR;
@@ -536,6 +546,50 @@ class CodesysSessionManager extends events_1.EventEmitter {
536
546
  this.sessionState = exports.SessionState.ERROR;
537
547
  throw new Error(`Result file not found at ${this.resultFilePath}`);
538
548
  }
549
+ // IMPORTANT: Ensure only one script is executed at a time by checking if other scripts are pending
550
+ if (this.pendingScripts.size > 0) {
551
+ console.error(`SESSION_MANAGER: Waiting for ${this.pendingScripts.size} pending scripts to complete before executing new script`);
552
+ // Wait for all pending scripts to complete
553
+ yield new Promise((resolve) => {
554
+ const checkInterval = setInterval(() => {
555
+ if (this.pendingScripts.size === 0) {
556
+ clearInterval(checkInterval);
557
+ resolve();
558
+ }
559
+ }, 100);
560
+ });
561
+ console.error('SESSION_MANAGER: All pending scripts completed, proceeding with new script execution');
562
+ }
563
+ // Mark the session as busy
564
+ this.sessionState = exports.SessionState.BUSY;
565
+ // Read current command file to ensure we're not overwriting an in-progress command
566
+ try {
567
+ const currentCommandData = JSON.parse(fs.readFileSync(this.commandFilePath, 'utf8'));
568
+ if (currentCommandData.status === 'pending' || currentCommandData.status === 'processing') {
569
+ console.error(`SESSION_MANAGER: Found pending command in file, waiting for it to complete`);
570
+ // Wait for the command to complete (status to be 'ready')
571
+ yield new Promise((resolve) => {
572
+ const checkInterval = setInterval(() => {
573
+ try {
574
+ const checkData = JSON.parse(fs.readFileSync(this.commandFilePath, 'utf8'));
575
+ if (checkData.status === 'ready') {
576
+ clearInterval(checkInterval);
577
+ resolve();
578
+ }
579
+ }
580
+ catch (err) {
581
+ // If we can't read the file, just continue
582
+ console.error(`SESSION_MANAGER: Error reading command file during wait: ${err}`);
583
+ }
584
+ }, 100);
585
+ });
586
+ console.error('SESSION_MANAGER: Previous command completed, proceeding with new command');
587
+ }
588
+ }
589
+ catch (err) {
590
+ console.error(`SESSION_MANAGER: Error reading current command file: ${err}`);
591
+ // If we can't read the file, we'll try to proceed anyway
592
+ }
539
593
  // Return a promise that will be resolved when script execution completes
540
594
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
541
595
  try {
@@ -584,7 +638,8 @@ class CodesysSessionManager extends events_1.EventEmitter {
584
638
  timestamp: Date.now()
585
639
  };
586
640
  // Send the command by writing to the command file
587
- fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData));
641
+ console.error(`SESSION_MANAGER: Writing command to file ${this.commandFilePath}`);
642
+ fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData, null, 2));
588
643
  console.error(`SESSION_MANAGER: Command written to file`);
589
644
  // Start reading the result file
590
645
  this.readResultFile(scriptId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {