@codesys/mcp-toolkit 1.2.4 → 1.3.0

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.
@@ -1,168 +1,132 @@
1
- import sys, os, json, time, tempfile
1
+ import sys, os, json, time
2
+ import tempfile
2
3
 
3
4
  # Define markers for script results
4
5
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
6
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
7
 
7
- # Session state file and pipes
8
+ # Session state and command files
8
9
  SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
10
  SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
10
- COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
11
- RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
11
+ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
12
+ RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
13
+ SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
12
14
 
13
15
  def check_session_status():
14
- """Check the status of a CODESYS session by reading the state file"""
16
+ """Check the status of a CODESYS session"""
15
17
  try:
16
18
  # Check if session directory exists
17
19
  if not os.path.exists(SESSION_DIR):
18
- return {
19
- "session_exists": False,
20
- "active": False,
21
- "message": "No session directory found"
22
- }
23
-
24
- # Check if state file exists
20
+ print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session directory not found.")
21
+ return False
22
+
23
+ # Check if session state file exists
25
24
  if not os.path.exists(SESSION_STATE_FILE):
26
- return {
27
- "session_exists": True,
28
- "active": False,
29
- "message": "Session directory exists but no state file found"
30
- }
31
-
32
- # Read the state file
25
+ print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session state file not found.")
26
+ return False
27
+
28
+ # Read session state
33
29
  with open(SESSION_STATE_FILE, 'r') as f:
34
- session_state = json.load(f)
35
-
36
- # Check if pipes exist
37
- pipes_exist = os.path.exists(COMMAND_PIPE) and os.path.exists(RESULT_PIPE)
38
-
39
- # Get session duration if available
40
- start_time = session_state.get("start_time", 0)
41
- if start_time > 0:
42
- current_time = time.time()
43
- duration_secs = int(current_time - start_time)
44
- hours, remainder = divmod(duration_secs, 3600)
45
- minutes, seconds = divmod(remainder, 60)
46
- duration = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
47
- else:
48
- duration = "unknown"
49
-
50
- # Last update time
51
- last_update = session_state.get("last_update", 0)
52
- last_update_str = "never"
53
- if last_update > 0:
54
- seconds_since_update = int(time.time() - last_update)
55
- if seconds_since_update < 60:
56
- last_update_str = f"{seconds_since_update} seconds ago"
57
- elif seconds_since_update < 3600:
58
- last_update_str = f"{seconds_since_update // 60} minutes ago"
59
- else:
60
- last_update_str = f"{seconds_since_update // 3600} hours ago"
61
-
62
- return {
63
- "session_exists": True,
64
- "active": session_state.get("active", False),
65
- "project_open": session_state.get("project_open", False),
66
- "project_name": session_state.get("project_name", "unknown"),
67
- "project_path": session_state.get("project_path", "unknown"),
68
- "duration": duration,
69
- "pipes_exist": pipes_exist,
70
- "last_update": last_update_str,
71
- "message": "Session information retrieved successfully"
72
- }
73
- except Exception as e:
74
- return {
75
- "session_exists": True,
76
- "active": False,
77
- "error": str(e),
78
- "message": f"Error reading session state: {e}"
79
- }
30
+ try:
31
+ session_state = json.load(f)
32
+ except json.JSONDecodeError:
33
+ print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
34
+ return False
80
35
 
81
- def ping_session():
82
- """Attempt to ping the session with a simple command to verify it's responsive"""
83
- try:
84
- if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
85
- return False
86
-
87
- ping_command = {
88
- "id": f"ping_{time.time()}",
89
- "type": "execute_script",
90
- "script": "print('SCRIPT_SUCCESS: Ping successful')"
91
- }
36
+ # Check if session is active
37
+ active = session_state.get('active', False)
38
+ status = session_state.get('status', 'unknown')
92
39
 
93
- # Set a timeout for the ping operation
94
- timeout = 5 # seconds
95
-
96
- # Use non-blocking I/O to handle timeouts
97
- import select
98
-
99
- # Send the ping command
100
- with open(COMMAND_PIPE, 'w') as cmd_pipe:
101
- cmd_pipe.write(json.dumps(ping_command))
40
+ # Check session lock file to see if it's been updated recently
41
+ if os.path.exists(SESSION_LOCK_FILE):
42
+ try:
43
+ with open(SESSION_LOCK_FILE, 'r') as f:
44
+ lock_time = float(f.read().strip())
45
+ current_time = time.time()
46
+ time_diff = current_time - lock_time
47
+
48
+ # If lock file hasn't been updated in 30 seconds, session might be dead
49
+ if time_diff > 30:
50
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session appears to be inactive (lock file stale by {time_diff:.1f} seconds)")
51
+ # But we'll leave the active flag as is, since it might just be busy
52
+ except Exception as e:
53
+ print(f"Error reading lock file: {e}")
102
54
 
103
- # Wait for the result pipe to be readable with timeout
104
- res_fd = os.open(RESULT_PIPE, os.O_RDONLY | os.O_NONBLOCK)
105
- ready, _, _ = select.select([res_fd], [], [], timeout)
55
+ # Execute a ping test by sending a simple command
56
+ if active and status in ["running", "busy"]:
57
+ # Try to ping the session
58
+ try:
59
+ # Check if command file exists
60
+ if os.path.exists(COMMAND_FILE):
61
+ # Create ping command
62
+ ping_command = {
63
+ "id": f"ping_{int(time.time())}",
64
+ "type": "execute_script",
65
+ "script": "print('SCRIPT_SUCCESS: Ping successful')",
66
+ "timestamp": time.time()
67
+ }
68
+
69
+ # Write command to file
70
+ command_data = {
71
+ "status": "pending",
72
+ "command": ping_command,
73
+ "timestamp": time.time()
74
+ }
75
+
76
+ with open(COMMAND_FILE, 'w') as f:
77
+ json.dump(command_data, f)
78
+
79
+ # Wait for result (with short timeout)
80
+ timeout = 5 # seconds
81
+ start_time = time.time()
82
+ ping_success = False
83
+
84
+ while not ping_success and (time.time() - start_time) < timeout:
85
+ # Check if result file exists
86
+ if not os.path.exists(RESULT_FILE):
87
+ time.sleep(0.5)
88
+ continue
89
+
90
+ try:
91
+ with open(RESULT_FILE, 'r') as f:
92
+ result_data = json.load(f)
93
+
94
+ if result_data.get("status") == "complete" and result_data.get("result"):
95
+ result = result_data.get("result")
96
+ if result.get('id') == ping_command['id'] and result.get('success', False):
97
+ ping_success = True
98
+ break
99
+ except Exception as e:
100
+ print(f"Error reading result: {e}")
101
+ time.sleep(0.5)
102
+
103
+ if not ping_success:
104
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session appears to be unresponsive to ping")
105
+ return False
106
+ except Exception as e:
107
+ print(f"Error during ping test: {e}")
106
108
 
107
- if not ready:
108
- os.close(res_fd)
109
- return False
110
-
111
- # Read the result
112
- result_json = os.read(res_fd, 4096).decode('utf-8').strip()
113
- os.close(res_fd)
109
+ # Build status message
110
+ project_info = ""
111
+ if session_state.get('project_open', False):
112
+ project_name = session_state.get('project_name', 'Unknown')
113
+ project_path = session_state.get('project_path', 'Unknown')
114
+ project_info = f", Project: {project_name} ({project_path})"
114
115
 
115
- if result_json:
116
- try:
117
- result = json.loads(result_json)
118
- return result.get("success", False)
119
- except json.JSONDecodeError:
120
- return False
116
+ if active and status in ["running", "busy"]:
117
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session is active (Status: {status}{project_info})")
118
+ return True
121
119
  else:
120
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})")
122
121
  return False
123
-
124
- except Exception as e:
125
- print(f"Error pinging session: {e}")
126
- return False
127
122
 
128
- def main():
129
- try:
130
- # Get session status
131
- status = check_session_status()
132
-
133
- # Print status information
134
- print(f"Session Exists: {status.get('session_exists', False)}")
135
- print(f"Session Active: {status.get('active', False)}")
136
-
137
- # If session exists, print more details
138
- if status.get("session_exists", False):
139
- if status.get("active", False):
140
- # Try to ping the session
141
- is_responsive = ping_session()
142
- print(f"Session Responsive: {is_responsive}")
143
-
144
- # Print project information if available
145
- if "project_open" in status:
146
- print(f"Project Open: {status['project_open']}")
147
- if "project_name" in status:
148
- print(f"Project Name: {status['project_name']}")
149
- if "project_path" in status:
150
- print(f"Project Path: {status['project_path']}")
151
- if "duration" in status:
152
- print(f"Session Duration: {status['duration']}")
153
- if "last_update" in status:
154
- print(f"Last Activity: {status['last_update']}")
155
- else:
156
- print("Session is not active (stopped or crashed)")
157
-
158
- # Print the final result marker
159
- print(f"{SCRIPT_SUCCESS_MARKER}: Session status check complete")
160
- sys.exit(0)
161
-
162
123
  except Exception as e:
163
- print(f"Error checking session status: {e}")
164
- print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
165
- sys.exit(1)
124
+ print(f"{SCRIPT_ERROR_MARKER}: Error checking session status - {str(e)}")
125
+ return False
166
126
 
127
+ # Main script
167
128
  if __name__ == "__main__":
168
- main()
129
+ if check_session_status():
130
+ sys.exit(0)
131
+ else:
132
+ sys.exit(1)
@@ -1,49 +1,60 @@
1
- import sys, os, threading, json, time, tempfile
1
+ import sys, os, json, time, tempfile
2
2
  import scriptengine as script_engine
3
3
 
4
4
  # Define markers for script results
5
5
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
6
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
7
7
 
8
- # Session state file and pipes
8
+ # Session state and command files
9
9
  SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
10
10
  SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
11
- COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
12
- RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
11
+ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
12
+ RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
13
+ SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
14
+ POLL_INTERVAL = 0.5 # seconds
13
15
 
14
16
  def setup_session_dir():
15
- """Create the session directory and pipes if they don't exist"""
17
+ """Create the session directory and initial files"""
16
18
  try:
17
19
  # Create session directory
18
20
  if not os.path.exists(SESSION_DIR):
19
21
  os.makedirs(SESSION_DIR)
20
22
 
21
- # Create named pipes if they don't exist
22
- if not os.path.exists(COMMAND_PIPE):
23
- os.mkfifo(COMMAND_PIPE)
24
-
25
- if not os.path.exists(RESULT_PIPE):
26
- os.mkfifo(RESULT_PIPE)
27
-
28
23
  # Initialize session state
29
24
  session_state = {
30
25
  "active": True,
26
+ "session_id": str(time.time()),
31
27
  "start_time": time.time(),
28
+ "status": "running",
32
29
  "project_open": False,
33
30
  "project_name": "",
34
31
  "project_path": ""
35
32
  }
36
33
 
34
+ # Write session state
37
35
  with open(SESSION_STATE_FILE, 'w') as f:
38
36
  json.dump(session_state, f)
39
37
 
38
+ # Create empty command file
39
+ with open(COMMAND_FILE, 'w') as f:
40
+ json.dump({"status": "ready", "command": None, "timestamp": time.time()}, f)
41
+
42
+ # Create empty result file
43
+ with open(RESULT_FILE, 'w') as f:
44
+ json.dump({"status": "none", "result": None, "timestamp": time.time()}, f)
45
+
46
+ # Create lock file
47
+ with open(SESSION_LOCK_FILE, 'w') as f:
48
+ f.write(str(time.time()))
49
+
50
+ print(f"Session directory setup at: {SESSION_DIR}")
40
51
  return True
41
52
  except Exception as e:
42
53
  print(f"Failed to set up session directory: {e}")
43
54
  print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
44
55
  return False
45
56
 
46
- def update_session_state():
57
+ def update_session_state(status="running"):
47
58
  """Update the session state file with current CODESYS state"""
48
59
  try:
49
60
  # Get current state
@@ -75,6 +86,7 @@ def update_session_state():
75
86
  session_state["project_name"] = project_name
76
87
  session_state["project_path"] = project_path
77
88
  session_state["last_update"] = time.time()
89
+ session_state["status"] = status
78
90
 
79
91
  # Write updated state
80
92
  with open(SESSION_STATE_FILE, 'w') as f:
@@ -86,9 +98,9 @@ def update_session_state():
86
98
  return False
87
99
 
88
100
  def execute_command(command_data):
89
- """Execute a command from the command pipe"""
101
+ """Execute a command"""
90
102
  try:
91
- cmd_type = command_data.get("type", "")
103
+ cmd_type = command_data.get("type", "execute_script")
92
104
  script = command_data.get("script", "")
93
105
  cmd_id = command_data.get("id", "unknown")
94
106
 
@@ -140,15 +152,8 @@ def execute_command(command_data):
140
152
  result["success"] = True
141
153
  result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
142
154
 
143
- # Update session state to mark as inactive
144
- with open(SESSION_STATE_FILE, 'r') as f:
145
- session_state = json.load(f)
146
-
147
- session_state["active"] = False
148
- session_state["end_time"] = time.time()
149
-
150
- with open(SESSION_STATE_FILE, 'w') as f:
151
- json.dump(session_state, f)
155
+ # Update session state to mark as stopping
156
+ update_session_state("stopping")
152
157
 
153
158
  # Return the result and signal to stop the loop
154
159
  return result, True
@@ -171,54 +176,84 @@ def execute_command(command_data):
171
176
  "error": str(e)
172
177
  }, False
173
178
 
179
+ def check_command():
180
+ """Check if there is a new command to execute"""
181
+ try:
182
+ # Read the command file
183
+ with open(COMMAND_FILE, 'r') as f:
184
+ command_data = json.load(f)
185
+
186
+ # Check if there's a new command
187
+ if command_data.get("status") == "pending" and command_data.get("command"):
188
+ # Extract the command
189
+ command = command_data.get("command")
190
+
191
+ # Mark as processing
192
+ command_data["status"] = "processing"
193
+ with open(COMMAND_FILE, 'w') as f:
194
+ json.dump(command_data, f)
195
+
196
+ # Execute the command
197
+ result, stop_requested = execute_command(command)
198
+
199
+ # Write the result
200
+ result_data = {
201
+ "status": "complete",
202
+ "result": result,
203
+ "timestamp": time.time()
204
+ }
205
+
206
+ with open(RESULT_FILE, 'w') as f:
207
+ json.dump(result_data, f)
208
+
209
+ # Reset command file
210
+ command_data = {
211
+ "status": "ready",
212
+ "command": None,
213
+ "timestamp": time.time()
214
+ }
215
+
216
+ with open(COMMAND_FILE, 'w') as f:
217
+ json.dump(command_data, f)
218
+
219
+ return stop_requested
220
+
221
+ return False
222
+ except Exception as e:
223
+ print(f"Error checking command: {e}")
224
+ return False
225
+
174
226
  def command_loop():
175
- """Main loop to listen for and execute commands"""
227
+ """Main loop to check for and execute commands"""
176
228
  stop_requested = False
177
229
 
178
230
  print(f"Starting command loop...")
179
- print(f"Command pipe: {COMMAND_PIPE}")
180
- print(f"Result pipe: {RESULT_PIPE}")
231
+ print(f"Command file: {COMMAND_FILE}")
232
+ print(f"Result file: {RESULT_FILE}")
181
233
 
182
234
  while not stop_requested:
183
235
  try:
184
- # Open the command pipe for reading (blocks until data is available)
185
- with open(COMMAND_PIPE, 'r') as cmd_pipe:
186
- # Read the command
187
- command_json = cmd_pipe.read().strip()
188
-
189
- if command_json:
190
- print(f"Received command: {command_json[:100]}...")
191
- try:
192
- command_data = json.loads(command_json)
193
- result, stop_requested = execute_command(command_data)
194
-
195
- # Write the result to the result pipe
196
- with open(RESULT_PIPE, 'w') as res_pipe:
197
- res_pipe.write(json.dumps(result))
198
-
199
- except json.JSONDecodeError as e:
200
- # Invalid JSON
201
- print(f"Invalid command JSON: {e}")
202
- error_result = {
203
- "id": "unknown",
204
- "success": False,
205
- "output": f"{SCRIPT_ERROR_MARKER}: Invalid command format",
206
- "error": str(e)
207
- }
208
- with open(RESULT_PIPE, 'w') as res_pipe:
209
- res_pipe.write(json.dumps(error_result))
236
+ # Update session lock file to indicate we're alive
237
+ with open(SESSION_LOCK_FILE, 'w') as f:
238
+ f.write(str(time.time()))
239
+
240
+ # Check for new commands
241
+ stop_requested = check_command()
242
+
243
+ # Sleep for a bit
244
+ time.sleep(POLL_INTERVAL)
210
245
 
211
246
  except Exception as e:
212
247
  print(f"Error in command loop: {e}")
213
- # Short delay to prevent CPU spinning in case of repeated errors
214
- time.sleep(0.5)
248
+ # Sleep for a bit
249
+ time.sleep(POLL_INTERVAL)
215
250
 
216
251
  print("Command loop stopped.")
217
252
 
218
253
  # Main script
219
254
  def main():
220
255
  try:
221
- # Setup session directory and pipes
256
+ # Setup session directory and files
222
257
  if not setup_session_dir():
223
258
  sys.exit(1)
224
259
 
@@ -229,6 +264,9 @@ def main():
229
264
  print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
230
265
  command_loop()
231
266
 
267
+ # Update final state
268
+ update_session_state("stopped")
269
+
232
270
  print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
233
271
  sys.exit(0)
234
272
  except Exception as e:
@@ -1,94 +1,117 @@
1
- import sys, os, json, time, tempfile
1
+ import sys, os, json, time
2
+ import tempfile
2
3
 
3
4
  # Define markers for script results
4
5
  SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
6
  SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
7
 
7
- # Session state file and pipes
8
+ # Session state and command files
8
9
  SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
10
  SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
10
- COMMAND_PIPE = os.path.join(SESSION_DIR, "command_pipe")
11
- RESULT_PIPE = os.path.join(SESSION_DIR, "result_pipe")
11
+ COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
12
+ RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
13
+ SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
12
14
 
13
- def check_session_active():
14
- """Check if a session is active by reading the state file"""
15
+ def stop_session():
16
+ """Stop a running CODESYS session"""
15
17
  try:
18
+ # Check if session directory exists
19
+ if not os.path.exists(SESSION_DIR):
20
+ print(f"{SCRIPT_ERROR_MARKER}: No session directory found at {SESSION_DIR}")
21
+ return False
22
+
23
+ # Check if session state file exists
16
24
  if not os.path.exists(SESSION_STATE_FILE):
17
- print(f"No session state file found at {SESSION_STATE_FILE}")
25
+ print(f"{SCRIPT_ERROR_MARKER}: No session state file found at {SESSION_STATE_FILE}")
18
26
  return False
19
-
27
+
28
+ # Read session state
20
29
  with open(SESSION_STATE_FILE, 'r') as f:
21
- session_state = json.load(f)
22
-
23
- return session_state.get("active", False)
24
- except Exception as e:
25
- print(f"Error checking session state: {e}")
26
- return False
30
+ try:
31
+ session_state = json.load(f)
32
+ except json.JSONDecodeError:
33
+ print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
34
+ return False
27
35
 
28
- def send_stop_command():
29
- """Send the stop command to the running session"""
30
- try:
31
- if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
32
- print(f"Command or result pipes not found")
36
+ # Check if session is active
37
+ if not session_state.get('active', False):
38
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session is already stopped")
39
+ return True
40
+
41
+ # Check if command file exists
42
+ if not os.path.exists(COMMAND_FILE):
43
+ print(f"{SCRIPT_ERROR_MARKER}: Command file not found at {COMMAND_FILE}")
33
44
  return False
34
-
45
+
46
+ # Create stop command
35
47
  stop_command = {
36
- "id": f"stop_{time.time()}",
48
+ "id": f"stop_{int(time.time())}",
37
49
  "type": "stop",
38
- "script": ""
50
+ "timestamp": time.time()
39
51
  }
40
-
41
- # Send the stop command
42
- with open(COMMAND_PIPE, 'w') as cmd_pipe:
43
- cmd_pipe.write(json.dumps(stop_command))
44
-
45
- # Wait for the result
46
- with open(RESULT_PIPE, 'r') as res_pipe:
47
- result_json = res_pipe.read().strip()
48
-
49
- if result_json:
52
+
53
+ # Write command to file
54
+ command_data = {
55
+ "status": "pending",
56
+ "command": stop_command,
57
+ "timestamp": time.time()
58
+ }
59
+
60
+ with open(COMMAND_FILE, 'w') as f:
61
+ json.dump(command_data, f)
62
+
63
+ print(f"Stop command sent to session")
64
+
65
+ # Wait for result (with timeout)
66
+ timeout = 10 # seconds
67
+ start_time = time.time()
68
+ result_received = False
69
+
70
+ while not result_received and (time.time() - start_time) < timeout:
71
+ # Check if result file exists
72
+ if not os.path.exists(RESULT_FILE):
73
+ time.sleep(0.5)
74
+ continue
75
+
50
76
  try:
51
- result = json.loads(result_json)
52
- return result.get("success", False)
53
- except json.JSONDecodeError:
54
- print(f"Invalid result JSON: {result_json}")
55
- return False
56
- else:
57
- print("No result received")
58
- return False
59
-
60
- except Exception as e:
61
- print(f"Error sending stop command: {e}")
62
- return False
77
+ with open(RESULT_FILE, 'r') as f:
78
+ result_data = json.load(f)
79
+
80
+ if result_data.get("status") == "complete" and result_data.get("result"):
81
+ result = result_data.get("result")
82
+ if result.get('id') == stop_command['id']:
83
+ result_received = True
84
+ print(f"Session stop result: {result.get('output', 'No output')}")
85
+ break
86
+ except Exception as e:
87
+ print(f"Error reading result: {e}")
88
+ time.sleep(0.5)
63
89
 
64
- def main():
65
- try:
66
- # Check if session is active
67
- if not check_session_active():
68
- print(f"{SCRIPT_SUCCESS_MARKER}: No active session found")
69
- sys.exit(0)
70
-
71
- # Send stop command
72
- print("Sending stop command to CODESYS session...")
73
- if send_stop_command():
74
- print(f"{SCRIPT_SUCCESS_MARKER}: Session stop command sent successfully")
75
-
76
- # Give the session a moment to clean up
77
- time.sleep(1)
90
+ # Update session state to indicate it's stopped
91
+ with open(SESSION_STATE_FILE, 'r') as f:
92
+ session_state = json.load(f)
78
93
 
79
- # Verify session state has been updated
80
- if not check_session_active():
81
- print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
82
- else:
83
- print(f"{SCRIPT_SUCCESS_MARKER}: Stop command accepted, but session may still be shutting down")
94
+ session_state['active'] = False
95
+ session_state['status'] = "stopped"
96
+ session_state['end_time'] = time.time()
97
+
98
+ with open(SESSION_STATE_FILE, 'w') as f:
99
+ json.dump(session_state, f)
100
+
101
+ if result_received:
102
+ print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
84
103
  else:
85
- print(f"{SCRIPT_ERROR_MARKER}: Failed to stop session")
86
- sys.exit(1)
87
-
104
+ print(f"{SCRIPT_ERROR_MARKER}: Timeout waiting for session to stop")
105
+
106
+ return result_received
107
+
88
108
  except Exception as e:
89
- print(f"Error stopping session: {e}")
90
- print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
91
- sys.exit(1)
109
+ print(f"{SCRIPT_ERROR_MARKER}: Error stopping session - {str(e)}")
110
+ return False
92
111
 
112
+ # Main script
93
113
  if __name__ == "__main__":
94
- main()
114
+ if stop_session():
115
+ sys.exit(0)
116
+ else:
117
+ sys.exit(1)
@@ -63,11 +63,12 @@ const scriptLoader_1 = require("./scriptLoader");
63
63
  const DEFAULT_SCRIPT_TIMEOUT = 60000; // 60 seconds
64
64
  const DEFAULT_SESSION_IDLE_TIMEOUT = 300000; // 5 minutes
65
65
  const DEFAULT_SESSION_POLL_INTERVAL = 1000; // 1 second
66
- // Session directory and pipe names
66
+ // Session directory and file names
67
67
  const SESSION_DIR = path.join(os.tmpdir(), 'codesys_session');
68
- const COMMAND_PIPE = 'command_pipe';
69
- const RESULT_PIPE = 'result_pipe';
68
+ const COMMAND_FILE = 'command.json';
69
+ const RESULT_FILE = 'result.json';
70
70
  const STATE_FILE = 'session_state.json';
71
+ const SESSION_LOCK_FILE = 'session.lock';
71
72
  // Define expected success/error markers from the Python scripts
72
73
  const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
73
74
  const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
@@ -120,11 +121,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
120
121
  this.codesysProfileName = codesysProfileName;
121
122
  this.idleTimeoutMs = idleTimeoutMs;
122
123
  this.sessionId = (0, uuid_1.v4)();
123
- // Set up session directory and pipes
124
+ // Set up session directory and files
124
125
  this.sessionDir = path.join(SESSION_DIR, this.sessionId);
125
- this.commandPipePath = path.join(this.sessionDir, COMMAND_PIPE);
126
- this.resultPipePath = path.join(this.sessionDir, RESULT_PIPE);
126
+ this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
127
+ this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
127
128
  this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
129
+ this.lockFilePath = path.join(this.sessionDir, SESSION_LOCK_FILE);
128
130
  // Log status changes through event emitter
129
131
  this.on(exports.SESSION_EVENTS.STARTED, () => {
130
132
  console.error(`SESSION_MANAGER: Session started successfully`);
@@ -247,8 +249,8 @@ class CodesysSessionManager extends events_1.EventEmitter {
247
249
  // Interpolate parameters
248
250
  const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
249
251
  SESSION_DIR: this.sessionDir,
250
- COMMAND_PIPE: COMMAND_PIPE,
251
- RESULT_PIPE: RESULT_PIPE,
252
+ COMMAND_FILE: COMMAND_FILE,
253
+ RESULT_FILE: RESULT_FILE,
252
254
  STATE_FILE: STATE_FILE,
253
255
  SESSION_ID: this.sessionId,
254
256
  });
@@ -319,10 +321,10 @@ class CodesysSessionManager extends events_1.EventEmitter {
319
321
  const startTime = Date.now();
320
322
  while (Date.now() - startTime < timeoutMs) {
321
323
  try {
322
- // Check if pipes exist
323
- const commandPipeExists = fs.existsSync(this.commandPipePath);
324
- const resultPipeExists = fs.existsSync(this.resultPipePath);
325
- if (commandPipeExists && resultPipeExists) {
324
+ // Check if files exist
325
+ const commandFileExists = fs.existsSync(this.commandFilePath);
326
+ const resultFileExists = fs.existsSync(this.resultFilePath);
327
+ if (commandFileExists && resultFileExists) {
326
328
  // Read state file to check if session is ready
327
329
  const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
328
330
  const stateData = JSON.parse(stateContent);
@@ -366,14 +368,14 @@ class CodesysSessionManager extends events_1.EventEmitter {
366
368
  }
367
369
  try {
368
370
  // Check if we have a running session
369
- if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandPipePath)) {
371
+ if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandFilePath)) {
370
372
  // Load the stop_session script template
371
373
  const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('stop_session');
372
374
  // Interpolate parameters
373
375
  const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
374
376
  SESSION_DIR: this.sessionDir,
375
- COMMAND_PIPE: COMMAND_PIPE,
376
- RESULT_PIPE: RESULT_PIPE,
377
+ COMMAND_FILE: COMMAND_FILE,
378
+ RESULT_FILE: RESULT_FILE,
377
379
  STATE_FILE: STATE_FILE,
378
380
  SESSION_ID: this.sessionId,
379
381
  });
@@ -524,15 +526,15 @@ class CodesysSessionManager extends events_1.EventEmitter {
524
526
  this.resetIdleTimeout();
525
527
  // Mark the session as busy
526
528
  this.sessionState = exports.SessionState.BUSY;
527
- // Check if the command pipe exists
528
- if (!fs.existsSync(this.commandPipePath)) {
529
+ // Check if the command file exists
530
+ if (!fs.existsSync(this.commandFilePath)) {
529
531
  this.sessionState = exports.SessionState.ERROR;
530
- throw new Error(`Command pipe not found at ${this.commandPipePath}`);
532
+ throw new Error(`Command file not found at ${this.commandFilePath}`);
531
533
  }
532
- // Check if the result pipe exists
533
- if (!fs.existsSync(this.resultPipePath)) {
534
+ // Check if the result file exists
535
+ if (!fs.existsSync(this.resultFilePath)) {
534
536
  this.sessionState = exports.SessionState.ERROR;
535
- throw new Error(`Result pipe not found at ${this.resultPipePath}`);
537
+ throw new Error(`Result file not found at ${this.resultFilePath}`);
536
538
  }
537
539
  // Return a promise that will be resolved when script execution completes
538
540
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
@@ -571,15 +573,21 @@ class CodesysSessionManager extends events_1.EventEmitter {
571
573
  // Create the command to send
572
574
  const command = {
573
575
  id: scriptId,
576
+ type: "execute_script",
574
577
  script: scriptContent,
575
578
  timestamp: Date.now()
576
579
  };
577
- // Send the command
578
- const commandString = JSON.stringify(command) + '\n';
579
- fs.writeFileSync(this.commandPipePath, commandString);
580
- console.error(`SESSION_MANAGER: Command sent to pipe`);
581
- // Start reading the result pipe
582
- this.readResultPipe(scriptId);
580
+ // Prepare the command data
581
+ const commandData = {
582
+ status: "pending",
583
+ command: command,
584
+ timestamp: Date.now()
585
+ };
586
+ // Send the command by writing to the command file
587
+ fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData));
588
+ console.error(`SESSION_MANAGER: Command written to file`);
589
+ // Start reading the result file
590
+ this.readResultFile(scriptId);
583
591
  }
584
592
  catch (error) {
585
593
  console.error(`SESSION_MANAGER: Error executing script: ${error.message}`);
@@ -599,12 +607,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
599
607
  });
600
608
  }
601
609
  /**
602
- * Read the result pipe for a specific script execution
610
+ * Read the result file for a specific script execution
603
611
  * @param scriptId The script ID to read results for
604
612
  */
605
- readResultPipe(scriptId) {
606
- console.error(`SESSION_MANAGER: Starting to read result pipe for script ${scriptId}`);
607
- // We'll use a polling approach to read from the pipe
613
+ readResultFile(scriptId) {
614
+ console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
615
+ // We'll use a polling approach to read from the file
608
616
  const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
609
617
  try {
610
618
  if (!this.pendingScripts.has(scriptId)) {
@@ -612,21 +620,19 @@ class CodesysSessionManager extends events_1.EventEmitter {
612
620
  clearInterval(pollInterval);
613
621
  return;
614
622
  }
615
- // Check if the result pipe exists
616
- if (!fs.existsSync(this.resultPipePath)) {
617
- console.error(`SESSION_MANAGER: Result pipe not found at ${this.resultPipePath}`);
623
+ // Check if the result file exists
624
+ if (!fs.existsSync(this.resultFilePath)) {
625
+ console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath}`);
618
626
  return;
619
627
  }
620
- // Read the result pipe
621
- const resultContent = yield (0, promises_1.readFile)(this.resultPipePath, 'utf8');
622
- // Process all lines
623
- const lines = resultContent.split('\n');
624
- for (const line of lines) {
625
- if (!line.trim())
626
- continue;
627
- try {
628
- const result = JSON.parse(line);
629
- // Check if this result is for our script
628
+ // Read the result file
629
+ const resultContent = yield (0, promises_1.readFile)(this.resultFilePath, 'utf8');
630
+ try {
631
+ // Parse the result data
632
+ const resultData = JSON.parse(resultContent);
633
+ // Check if the result is complete and for our script
634
+ if (resultData.status === 'complete' && resultData.result) {
635
+ const result = resultData.result;
630
636
  if (result.id === scriptId) {
631
637
  console.error(`SESSION_MANAGER: Found result for script ${scriptId}`);
632
638
  // Remove from pending scripts
@@ -647,14 +653,13 @@ class CodesysSessionManager extends events_1.EventEmitter {
647
653
  return;
648
654
  }
649
655
  }
650
- catch (parseError) {
651
- console.error(`SESSION_MANAGER: Error parsing result line: ${parseError}`);
652
- // Continue with the next line
653
- }
656
+ }
657
+ catch (parseError) {
658
+ console.error(`SESSION_MANAGER: Error parsing result data: ${parseError}`);
654
659
  }
655
660
  }
656
661
  catch (error) {
657
- console.error(`SESSION_MANAGER: Error reading result pipe: ${error}`);
662
+ console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
658
663
  }
659
664
  }), 100); // Poll every 100ms
660
665
  // Set a timeout to stop polling after the script timeout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {