@codesys/mcp-toolkit 1.4.0 → 1.4.1

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,14 +5,19 @@ 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.1] - 2025-05-02
9
+ ### Fixed
10
+ - Fixed IronPython 2.7 compatibility for session management scripts
11
+ - Replaced f-strings with str.format() method calls for IronPython compatibility
12
+ - Added fallback import for json module (using simplejson for IronPython)
13
+ - Improved error handling for session scripts
14
+ - Re-enabled session management feature in codesys_interop.ts
15
+
8
16
  ## [1.4.0] - 2025-05-02
9
17
  ### Added
10
- - Implemented persistent session management functionality
11
- - Added support for keeping CODESYS open between commands
12
- - Created file-based IPC for script execution in existing CODESYS instance
13
- - Improved session reliability with health checks and automatic recovery
14
- - Added session timeout after 10 minutes of inactivity
15
- - Created detailed documentation for session management features
18
+ - Implemented initial framework for session management
19
+ - Added foundation for file-based IPC between Node.js and CODESYS instances
20
+ - Created documentation structure for session management capabilities
16
21
 
17
22
  ## [1.3.0] - 2025-05-02
18
23
  ### Changed
@@ -92,7 +92,7 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName,
92
92
  throw new Error(`CODESYS executable not found at provided path: ${codesysExePath}`);
93
93
  // --- End Pre-checks ---
94
94
  // Enable or disable session management
95
- const SESSION_FEATURE_DISABLED = false;
95
+ const SESSION_FEATURE_DISABLED = false; // Session feature enabled
96
96
  // --- Check if we should use the persistent session ---
97
97
  if (!SESSION_FEATURE_DISABLED && sessionManager) {
98
98
  const sessionState = sessionManager.getState();
@@ -1,6 +1,13 @@
1
- import sys, os, json, time, traceback
1
+ import sys, os, time, traceback
2
2
  import tempfile
3
3
 
4
+ # Import JSON handling for IronPython 2.7 compatibility
5
+ try:
6
+ import json
7
+ except ImportError:
8
+ # For IronPython 2.7, we need to use simplejson instead
9
+ import simplejson as json
10
+
4
11
  # Define markers for script results
5
12
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
13
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
@@ -17,12 +24,12 @@ def check_session_status():
17
24
  try:
18
25
  # Check if session directory exists
19
26
  if not os.path.exists(SESSION_DIR):
20
- print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session directory not found.")
27
+ print("{0}: No session active. Session directory not found.".format(SCRIPT_SUCCESS_MARKER))
21
28
  return False
22
29
 
23
30
  # Check if session state file exists
24
31
  if not os.path.exists(SESSION_STATE_FILE):
25
- print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session state file not found.")
32
+ print("{0}: No session active. Session state file not found.".format(SCRIPT_SUCCESS_MARKER))
26
33
  return False
27
34
 
28
35
  # Read session state
@@ -30,7 +37,7 @@ def check_session_status():
30
37
  with open(SESSION_STATE_FILE, 'r') as f:
31
38
  session_state = json.load(f)
32
39
  except json.JSONDecodeError:
33
- print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
40
+ print("{0}: Invalid session state file".format(SCRIPT_ERROR_MARKER))
34
41
  return False
35
42
 
36
43
  # Check if session is active
@@ -39,7 +46,7 @@ def check_session_status():
39
46
 
40
47
  # If the status indicates the session is not active, return immediately
41
48
  if not active or status in ['stopped', 'error', 'stopping', 'timeout']:
42
- print(f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})")
49
+ print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
43
50
  return False
44
51
 
45
52
  # Check session lock file to see if it's been updated recently
@@ -53,10 +60,10 @@ def check_session_status():
53
60
 
54
61
  # If lock file hasn't been updated in 30 seconds, session might be dead
55
62
  if time_diff > 30:
56
- print(f"WARNING: Session lock file is stale by {time_diff:.1f} seconds")
63
+ print("WARNING: Session lock file is stale by {0:.1f} seconds".format(time_diff))
57
64
  lock_is_stale = True
58
65
  except Exception as e:
59
- print(f"Error reading lock file: {e}")
66
+ print("Error reading lock file: {0}".format(e))
60
67
  lock_is_stale = True
61
68
  else:
62
69
  print("WARNING: Session lock file not found")
@@ -74,7 +81,7 @@ def check_session_status():
74
81
 
75
82
  # Create ping command
76
83
  ping_command = {
77
- "id": f"ping_{int(time.time())}",
84
+ "id": "ping_{0}".format(int(time.time())),
78
85
  "type": "ping", # Use specific ping command type
79
86
  "timestamp": time.time()
80
87
  }
@@ -110,11 +117,11 @@ def check_session_status():
110
117
  print("Ping successful, session is responsive")
111
118
  break
112
119
  except Exception as e:
113
- print(f"Error reading result: {e}")
120
+ print("Error reading result: {0}".format(e))
114
121
  time.sleep(0.5)
115
122
 
116
123
  if not ping_success:
117
- print(f"WARNING: Session did not respond to ping within {timeout} seconds")
124
+ print("WARNING: Session did not respond to ping within {0} seconds".format(timeout))
118
125
 
119
126
  # Reset command file since ping failed
120
127
  try:
@@ -129,11 +136,11 @@ def check_session_status():
129
136
 
130
137
  print("Reset command file after failed ping")
131
138
  except Exception as e:
132
- print(f"Error resetting command file: {e}")
139
+ print("Error resetting command file: {0}".format(e))
133
140
  else:
134
141
  print("WARNING: Command file not found, cannot ping session")
135
142
  except Exception as e:
136
- print(f"Error during ping test: {e}")
143
+ print("Error during ping test: {0}".format(e))
137
144
  traceback.print_exc()
138
145
 
139
146
  # Make final determination based on all checks
@@ -144,21 +151,21 @@ def check_session_status():
144
151
  if session_state.get('project_open', False):
145
152
  project_name = session_state.get('project_name', 'Unknown')
146
153
  project_path = session_state.get('project_path', 'Unknown')
147
- project_info = f", Project: {project_name} ({project_path})"
154
+ project_info = ", Project: {0} ({1})".format(project_name, project_path)
148
155
 
149
156
  # Include session info
150
157
  session_id = session_state.get('session_id', 'Unknown')
151
158
  start_time = session_state.get('start_time', 0)
152
159
  uptime = time.time() - start_time if start_time else 0
153
- uptime_str = f"{uptime:.0f} seconds" if uptime < 60 else f"{uptime/60:.1f} minutes"
160
+ uptime_str = "{0:.0f} seconds".format(uptime) if uptime < 60 else "{0:.1f} minutes".format(uptime/60)
154
161
 
155
162
  if session_active:
156
- print(f"{SCRIPT_SUCCESS_MARKER}: Session is active (ID: {session_id}, Status: {status}, Uptime: {uptime_str}{project_info})")
163
+ print("{0}: Session is active (ID: {1}, Status: {2}, Uptime: {3}{4})".format(SCRIPT_SUCCESS_MARKER, session_id, status, uptime_str, project_info))
157
164
  return True
158
165
  else:
159
166
  # If we get here, session may be in a state where files exist but process is dead
160
167
  if active and status in ["running", "busy"] and lock_is_stale and not ping_success:
161
- print(f"{SCRIPT_SUCCESS_MARKER}: Session appears to be zombie (ID: {session_id}, Status: {status} but unresponsive)")
168
+ print("{0}: Session appears to be zombie (ID: {1}, Status: {2} but unresponsive)".format(SCRIPT_SUCCESS_MARKER, session_id, status))
162
169
  # Update session state to error so it will be considered inactive
163
170
  try:
164
171
  session_state['status'] = 'error'
@@ -170,14 +177,14 @@ def check_session_status():
170
177
 
171
178
  print("Updated session state to error due to unresponsiveness")
172
179
  except Exception as e:
173
- print(f"Error updating session state: {e}")
180
+ print("Error updating session state: {0}".format(e))
174
181
  else:
175
- print(f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})")
182
+ print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
176
183
 
177
184
  return False
178
185
 
179
186
  except Exception as e:
180
- print(f"{SCRIPT_ERROR_MARKER}: Error checking session status - {str(e)}")
187
+ print("{0}: Error checking session status - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
181
188
  traceback.print_exc()
182
189
  return False
183
190
 
@@ -1,5 +1,19 @@
1
- import sys, os, json, time, tempfile, traceback
2
- import scriptengine as script_engine
1
+ import sys, os, time, tempfile, traceback
2
+
3
+ # Import JSON handling for IronPython 2.7 compatibility
4
+ try:
5
+ import json
6
+ except ImportError:
7
+ # For IronPython 2.7, we need to use simplejson instead
8
+ import simplejson as json
9
+
10
+ # Import scriptengine
11
+ try:
12
+ import scriptengine as script_engine
13
+ except ImportError:
14
+ # Log the error and continue - we'll handle this gracefully
15
+ print("Error importing scriptengine. This script must be run within CODESYS environment.")
16
+ script_engine = None
3
17
 
4
18
  # Define markers for script results
5
19
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
@@ -25,7 +39,7 @@ def setup_session_dir():
25
39
  try:
26
40
  with open(SESSION_STATE_FILE, 'r') as f:
27
41
  old_state = json.load(f)
28
- print(f"Cleaning up previous session (ID: {old_state.get('session_id', 'unknown')})")
42
+ print("Cleaning up previous session (ID: {0})".format(old_state.get('session_id', 'unknown')))
29
43
  except:
30
44
  pass
31
45
 
@@ -36,9 +50,9 @@ def setup_session_dir():
36
50
  if os.path.isfile(filepath):
37
51
  os.unlink(filepath)
38
52
  except Exception as e:
39
- print(f"Error removing file {filepath}: {e}")
53
+ print("Error removing file {0}: {1}".format(filepath, e))
40
54
  except Exception as e:
41
- print(f"Error cleaning up previous session: {e}")
55
+ print("Error cleaning up previous session: {0}".format(e))
42
56
 
43
57
  # Create session directory
44
58
  if not os.path.exists(SESSION_DIR):
@@ -74,12 +88,12 @@ def setup_session_dir():
74
88
  with open(SESSION_LOCK_FILE, 'w') as f:
75
89
  f.write(str(time.time()))
76
90
 
77
- print(f"Session directory setup at: {SESSION_DIR}")
78
- print(f"Session ID: {session_id}")
91
+ print("Session directory setup at: {0}".format(SESSION_DIR))
92
+ print("Session ID: {0}".format(session_id))
79
93
  return True
80
94
  except Exception as e:
81
- print(f"Failed to set up session directory: {e}")
82
- print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
95
+ print("Failed to set up session directory: {0}".format(e))
96
+ print("{0}: Session setup failed - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
83
97
  return False
84
98
 
85
99
  def update_session_state(status="running"):
@@ -90,20 +104,29 @@ def update_session_state(status="running"):
90
104
  project_name = "No project open"
91
105
  project_path = "N/A"
92
106
 
93
- primary_project = script_engine.projects.primary
94
- if primary_project:
95
- project_open = True
107
+ # Check if script_engine was properly imported
108
+ if script_engine is None:
109
+ print("Warning: scriptengine not available, cannot get project info")
110
+ else:
111
+ # Try to get primary project
96
112
  try:
97
- project_path = os.path.normcase(os.path.abspath(primary_project.path))
98
- try:
99
- project_name = primary_project.get_name()
100
- if not project_name:
101
- project_name = f"Unnamed (path: {os.path.basename(project_path)})"
102
- except:
103
- project_name = f"Unnamed (path: {os.path.basename(project_path)})"
104
- except Exception as e_path:
105
- project_path = f"N/A (Error: {e_path})"
106
- project_name = "Unnamed (Path Error)"
113
+ primary_project = script_engine.projects.primary
114
+ if primary_project:
115
+ project_open = True
116
+ try:
117
+ project_path = os.path.normcase(os.path.abspath(primary_project.path))
118
+ try:
119
+ project_name = primary_project.get_name()
120
+ if not project_name:
121
+ project_name = "Unnamed (path: {0})".format(os.path.basename(project_path))
122
+ except:
123
+ project_name = "Unnamed (path: {0})".format(os.path.basename(project_path))
124
+ except Exception as e_path:
125
+ project_path = "N/A (Error: {0})".format(str(e_path))
126
+ project_name = "Unnamed (Path Error)"
127
+ except Exception as e:
128
+ print("Error accessing script_engine.projects: {0}".format(str(e)))
129
+ traceback.print_exc()
107
130
 
108
131
  # Read current state file
109
132
  try:
@@ -133,7 +156,7 @@ def update_session_state(status="running"):
133
156
 
134
157
  return True
135
158
  except Exception as e:
136
- print(f"Failed to update session state: {e}")
159
+ print("Failed to update session state: {0}".format(e))
137
160
  traceback.print_exc()
138
161
  return False
139
162
 
@@ -189,13 +212,13 @@ def execute_command(command_data):
189
212
  except Exception as e:
190
213
  traceback.print_exc()
191
214
  result["success"] = False
192
- result["error"] = f"Error executing script: {str(e)}\n{traceback.format_exc()}"
193
- result["output"] = f"{SCRIPT_ERROR_MARKER}: {str(e)}"
215
+ result["error"] = "Error executing script: {0}\n{1}".format(str(e), traceback.format_exc())
216
+ result["output"] = "{0}: {1}".format(SCRIPT_ERROR_MARKER, str(e))
194
217
 
195
218
  elif cmd_type == "stop":
196
219
  # Stop command received
197
220
  result["success"] = True
198
- result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
221
+ result["output"] = "{0}: Session stopping".format(SCRIPT_SUCCESS_MARKER)
199
222
 
200
223
  # Update session state to mark as stopping
201
224
  update_session_state("stopping")
@@ -206,12 +229,12 @@ def execute_command(command_data):
206
229
  elif cmd_type == "ping":
207
230
  # Simple ping command to check if session is alive
208
231
  result["success"] = True
209
- result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session is active"
232
+ result["output"] = "{0}: Session is active".format(SCRIPT_SUCCESS_MARKER)
210
233
 
211
234
  else:
212
235
  result["success"] = False
213
- result["error"] = f"Unknown command type: {cmd_type}"
214
- result["output"] = f"{SCRIPT_ERROR_MARKER}: Unknown command type: {cmd_type}"
236
+ result["error"] = "Unknown command type: {0}".format(cmd_type)
237
+ result["output"] = "{0}: Unknown command type: {1}".format(SCRIPT_ERROR_MARKER, cmd_type)
215
238
 
216
239
  # Update session state after command execution
217
240
  update_session_state()
@@ -223,8 +246,8 @@ def execute_command(command_data):
223
246
  return {
224
247
  "id": command_data.get("id", "unknown"),
225
248
  "success": False,
226
- "output": f"{SCRIPT_ERROR_MARKER}: Command execution failed",
227
- "error": f"{str(e)}\n{traceback.format_exc()}"
249
+ "output": "{0}: Command execution failed".format(SCRIPT_ERROR_MARKER),
250
+ "error": "{0}\n{1}".format(str(e), traceback.format_exc())
228
251
  }, False
229
252
 
230
253
  def check_command():
@@ -292,7 +315,7 @@ def check_command():
292
315
 
293
316
  return False
294
317
  except Exception as e:
295
- print(f"Error checking command: {e}")
318
+ print("Error checking command: {0}".format(e))
296
319
  traceback.print_exc()
297
320
  return False
298
321
 
@@ -308,7 +331,7 @@ def update_last_activity():
308
331
  with open(SESSION_STATE_FILE, 'w') as f:
309
332
  json.dump(state, f, indent=2)
310
333
  except Exception as e:
311
- print(f"Error updating last activity: {e}")
334
+ print("Error updating last activity: {0}".format(e))
312
335
 
313
336
  def check_inactivity_timeout():
314
337
  """Check if session has been inactive for too long"""
@@ -321,12 +344,12 @@ def check_inactivity_timeout():
321
344
  current_time = time.time()
322
345
 
323
346
  if (current_time - last_activity) > MAX_INACTIVITY_TIME:
324
- print(f"Session inactive for {current_time - last_activity:.1f} seconds, shutting down")
347
+ print("Session inactive for {0:.1f} seconds, shutting down".format(current_time - last_activity))
325
348
  return True
326
349
 
327
350
  return False
328
351
  except Exception as e:
329
- print(f"Error checking inactivity timeout: {e}")
352
+ print("Error checking inactivity timeout: {0}".format(e))
330
353
  return False
331
354
 
332
355
  def command_loop():
@@ -334,9 +357,9 @@ def command_loop():
334
357
  stop_requested = False
335
358
  last_state_update = time.time()
336
359
 
337
- print(f"Starting command loop...")
338
- print(f"Command file: {COMMAND_FILE}")
339
- print(f"Result file: {RESULT_FILE}")
360
+ print("Starting command loop...")
361
+ print("Command file: {0}".format(COMMAND_FILE))
362
+ print("Result file: {0}".format(RESULT_FILE))
340
363
 
341
364
  while not stop_requested:
342
365
  try:
@@ -363,7 +386,7 @@ def command_loop():
363
386
  time.sleep(POLL_INTERVAL)
364
387
 
365
388
  except Exception as e:
366
- print(f"Error in command loop: {e}")
389
+ print("Error in command loop: {0}".format(e))
367
390
  traceback.print_exc()
368
391
  # Sleep for a bit
369
392
  time.sleep(POLL_INTERVAL)
@@ -381,17 +404,17 @@ def main():
381
404
  update_session_state()
382
405
 
383
406
  # Start command loop
384
- print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
407
+ print("{0}: CODESYS session started".format(SCRIPT_SUCCESS_MARKER))
385
408
  command_loop()
386
409
 
387
410
  # Update final state
388
411
  update_session_state("stopped")
389
412
 
390
- print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
413
+ print("{0}: CODESYS session stopped cleanly".format(SCRIPT_SUCCESS_MARKER))
391
414
  sys.exit(0)
392
415
  except Exception as e:
393
- print(f"Error in session: {e}")
394
- print(f"{SCRIPT_ERROR_MARKER}: Session failed - {str(e)}")
416
+ print("Error in session: {0}".format(e))
417
+ print("{0}: Session failed - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
395
418
  traceback.print_exc()
396
419
  sys.exit(1)
397
420
 
@@ -1,6 +1,13 @@
1
- import sys, os, json, time, traceback
1
+ import sys, os, time, traceback
2
2
  import tempfile
3
3
 
4
+ # Import JSON handling for IronPython 2.7 compatibility
5
+ try:
6
+ import json
7
+ except ImportError:
8
+ # For IronPython 2.7, we need to use simplejson instead
9
+ import simplejson as json
10
+
4
11
  # Define markers for script results
5
12
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
13
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
@@ -17,12 +24,12 @@ def stop_session():
17
24
  try:
18
25
  # Check if session directory exists
19
26
  if not os.path.exists(SESSION_DIR):
20
- print(f"{SCRIPT_ERROR_MARKER}: No session directory found at {SESSION_DIR}")
27
+ print("{0}: No session directory found at {1}".format(SCRIPT_ERROR_MARKER, SESSION_DIR))
21
28
  return False
22
29
 
23
30
  # Check if session state file exists
24
31
  if not os.path.exists(SESSION_STATE_FILE):
25
- print(f"{SCRIPT_ERROR_MARKER}: No session state file found at {SESSION_STATE_FILE}")
32
+ print("{0}: No session state file found at {1}".format(SCRIPT_ERROR_MARKER, SESSION_STATE_FILE))
26
33
  return False
27
34
 
28
35
  # Read session state
@@ -30,17 +37,17 @@ def stop_session():
30
37
  with open(SESSION_STATE_FILE, 'r') as f:
31
38
  session_state = json.load(f)
32
39
  except json.JSONDecodeError:
33
- print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
40
+ print("{0}: Invalid session state file".format(SCRIPT_ERROR_MARKER))
34
41
  return False
35
42
 
36
43
  # Check if session is active
37
44
  if not session_state.get('active', False) or session_state.get('status') == 'stopped':
38
- print(f"{SCRIPT_SUCCESS_MARKER}: Session is already stopped")
45
+ print("{0}: Session is already stopped".format(SCRIPT_SUCCESS_MARKER))
39
46
  return True
40
47
 
41
48
  # Check if command file exists
42
49
  if not os.path.exists(COMMAND_FILE):
43
- print(f"{SCRIPT_ERROR_MARKER}: Command file not found at {COMMAND_FILE}")
50
+ print("{0}: Command file not found at {1}".format(SCRIPT_ERROR_MARKER, COMMAND_FILE))
44
51
  return False
45
52
 
46
53
  # Try to read the lock file to check if session is still alive
@@ -52,13 +59,13 @@ def stop_session():
52
59
 
53
60
  current_time = time.time()
54
61
  if (current_time - lock_time) > 30:
55
- print(f"WARNING: Session lock file is stale ({current_time - lock_time:.1f} seconds old). Session may have crashed.")
62
+ print("WARNING: Session lock file is stale ({0:.1f} seconds old). Session may have crashed.".format(current_time - lock_time))
56
63
  except Exception as e:
57
- print(f"Warning: Error reading lock file: {e}")
64
+ print("Warning: Error reading lock file: {0}".format(e))
58
65
 
59
66
  # Create stop command
60
67
  stop_command = {
61
- "id": f"stop_{int(time.time())}",
68
+ "id": "stop_{0}".format(int(time.time())),
62
69
  "type": "stop",
63
70
  "timestamp": time.time()
64
71
  }
@@ -73,7 +80,7 @@ def stop_session():
73
80
  with open(COMMAND_FILE, 'w') as f:
74
81
  json.dump(command_data, f, indent=2)
75
82
 
76
- print(f"Stop command sent to session")
83
+ print("Stop command sent to session")
77
84
 
78
85
  # Wait for result (with timeout)
79
86
  timeout = 15 # seconds (increased timeout for more reliability)
@@ -94,10 +101,10 @@ def stop_session():
94
101
  result = result_data.get("result")
95
102
  if result.get('id') == stop_command['id']:
96
103
  result_received = True
97
- print(f"Session stop result: {result.get('output', 'No output')}")
104
+ print("Session stop result: {0}".format(result.get('output', 'No output')))
98
105
  break
99
106
  except Exception as e:
100
- print(f"Error reading result: {e}")
107
+ print("Error reading result: {0}".format(e))
101
108
  time.sleep(0.5)
102
109
 
103
110
  # Update session state to indicate it's stopped regardless of result
@@ -112,15 +119,15 @@ def stop_session():
112
119
  with open(SESSION_STATE_FILE, 'w') as f:
113
120
  json.dump(session_state, f, indent=2)
114
121
  except Exception as e:
115
- print(f"Warning: Failed to update session state file: {e}")
122
+ print("Warning: Failed to update session state file: {0}".format(e))
116
123
 
117
124
  if result_received:
118
- print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
125
+ print("{0}: Session stopped successfully".format(SCRIPT_SUCCESS_MARKER))
119
126
  return True
120
127
  else:
121
128
  # If we didn't get a response, the session may be hung
122
129
  # Force a cleanup by removing session files
123
- print(f"Warning: Session did not respond to stop command. Forcing cleanup.")
130
+ print("Warning: Session did not respond to stop command. Forcing cleanup.")
124
131
  try:
125
132
  # Try to remove all session files to ensure a clean state for next session
126
133
  for filename in os.listdir(SESSION_DIR):
@@ -129,16 +136,16 @@ def stop_session():
129
136
  if os.path.isfile(filepath):
130
137
  os.unlink(filepath)
131
138
  except Exception as e:
132
- print(f"Error removing file {filepath}: {e}")
139
+ print("Error removing file {0}: {1}".format(filepath, e))
133
140
 
134
- print(f"{SCRIPT_SUCCESS_MARKER}: Session files cleaned up forcefully")
141
+ print("{0}: Session files cleaned up forcefully".format(SCRIPT_SUCCESS_MARKER))
135
142
  return True
136
143
  except Exception as e:
137
- print(f"{SCRIPT_ERROR_MARKER}: Failed to force cleanup - {str(e)}")
144
+ print("{0}: Failed to force cleanup - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
138
145
  return False
139
146
 
140
147
  except Exception as e:
141
- print(f"{SCRIPT_ERROR_MARKER}: Error stopping session - {str(e)}")
148
+ print("{0}: Error stopping session - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
142
149
  traceback.print_exc()
143
150
  return False
144
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
@@ -0,0 +1,16 @@
1
+ f"{SCRIPT_ERROR_MARKER}: Invalid session state file" | "{0}: Invalid session state file".format(SCRIPT_ERROR_MARKER)
2
+ f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})" | "{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status)
3
+ f"WARNING: Session lock file is stale by {time_diff:.1f} seconds" | "WARNING: Session lock file is stale by {0:.1f} seconds".format(time_diff)
4
+ f"Error reading lock file: {e}" | "Error reading result: {0}".format(e)
5
+ f"ping_{int(time.time())}" | "ping_{0}".format(int(time.time()))
6
+ f"WARNING: Session did not respond to ping within {timeout} seconds" | "WARNING: Session did not respond to ping within {0} seconds".format(timeout)
7
+ f"Error resetting command file: {e}" | "Error resetting command file: {0}".format(e)
8
+ f"Error during ping test: {e}" | "Error during ping test: {0}".format(e)
9
+ f", Project: {project_name} ({project_path})" | ", Project: {0} ({1})".format(project_name, project_path)
10
+ f"{uptime:.0f} seconds" | "{0:.0f} seconds".format(uptime)
11
+ f"{uptime/60:.1f} minutes" | "{0:.1f} minutes".format(uptime/60)
12
+ f"{SCRIPT_SUCCESS_MARKER}: Session is active (ID: {session_id}, Status: {status}, Uptime: {uptime_str}{project_info})" | "{0}: Session is active (ID: {1}, Status: {2}, Uptime: {3}{4})".format(SCRIPT_SUCCESS_MARKER, session_id, status, uptime_str, project_info)
13
+ f"{SCRIPT_SUCCESS_MARKER}: Session appears to be zombie (ID: {session_id}, Status: {status} but unresponsive)" | "{0}: Session appears to be zombie (ID: {1}, Status: {2} but unresponsive)".format(SCRIPT_SUCCESS_MARKER, session_id, status)
14
+ f"Error updating session state: {e}" | "Error updating session state: {0}".format(e)
15
+ f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})" | "{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status)
16
+ f"{SCRIPT_ERROR_MARKER}: Error checking session status - {str(e)}" | "{0}: Error checking session status - {1}".format(SCRIPT_ERROR_MARKER, str(e))
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script to fix f-strings in Python files to make them compatible with IronPython 2.7.
4
+ Converts f-strings to str.format() calls.
5
+ """
6
+
7
+ import re
8
+ import sys
9
+ import os
10
+
11
+ def process_file(filepath):
12
+ print(f"Processing {filepath}")
13
+ with open(filepath, 'r', encoding='utf-8') as f:
14
+ content = f.read()
15
+
16
+ # Find and replace f-strings
17
+ # This is a simplified approach - complex nested expressions might need manual review
18
+ pattern = r'f"([^"]*\{[^}]+\}[^"]*)"'
19
+
20
+ # Count potential matches
21
+ matches = re.findall(pattern, content)
22
+ print(f"Found {len(matches)} potential f-strings")
23
+
24
+ # Function to convert each match
25
+ def replace_fstring(match):
26
+ template_str = match.group(1)
27
+ # Replace {var} with {0}, {1}, etc.
28
+ var_pattern = r'\{([^}:]+)(?::([^}]+))?\}'
29
+ var_matches = re.finditer(var_pattern, template_str)
30
+
31
+ format_args = []
32
+ i = 0
33
+
34
+ new_template = template_str
35
+ for m in var_matches:
36
+ var_name = m.group(1)
37
+ format_spec = m.group(2)
38
+
39
+ if format_spec:
40
+ repl = "{" + str(i) + ":" + format_spec + "}"
41
+ else:
42
+ repl = "{" + str(i) + "}"
43
+
44
+ # Store the argument
45
+ format_args.append(var_name)
46
+
47
+ # Replace in the template
48
+ new_template = new_template.replace(m.group(0), repl, 1)
49
+ i += 1
50
+
51
+ # Build the complete format expression
52
+ args_str = ", ".join(format_args)
53
+ return f'"{new_template}".format({args_str})'
54
+
55
+ # Apply the replacements
56
+ new_content = re.sub(pattern, replace_fstring, content)
57
+
58
+ # Write the changes back
59
+ with open(filepath, 'w', encoding='utf-8') as f:
60
+ f.write(new_content)
61
+
62
+ print(f"Completed processing {filepath}")
63
+
64
+ def main():
65
+ if len(sys.argv) < 2:
66
+ print("Usage: python fix_fstrings.py <file_paths...>")
67
+ sys.exit(1)
68
+
69
+ for file_path in sys.argv[1:]:
70
+ if os.path.exists(file_path):
71
+ process_file(file_path)
72
+ else:
73
+ print(f"Error: File {file_path} not found")
74
+
75
+ if __name__ == "__main__":
76
+ main()