@codesys/mcp-toolkit 1.2.1 → 1.2.4

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.
@@ -0,0 +1,240 @@
1
+ import sys, os, threading, json, time, tempfile
2
+ import scriptengine as script_engine
3
+
4
+ # Define markers for script results
5
+ SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
6
+ SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
7
+
8
+ # Session state file and pipes
9
+ SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
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")
13
+
14
+ def setup_session_dir():
15
+ """Create the session directory and pipes if they don't exist"""
16
+ try:
17
+ # Create session directory
18
+ if not os.path.exists(SESSION_DIR):
19
+ os.makedirs(SESSION_DIR)
20
+
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
+ # Initialize session state
29
+ session_state = {
30
+ "active": True,
31
+ "start_time": time.time(),
32
+ "project_open": False,
33
+ "project_name": "",
34
+ "project_path": ""
35
+ }
36
+
37
+ with open(SESSION_STATE_FILE, 'w') as f:
38
+ json.dump(session_state, f)
39
+
40
+ return True
41
+ except Exception as e:
42
+ print(f"Failed to set up session directory: {e}")
43
+ print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
44
+ return False
45
+
46
+ def update_session_state():
47
+ """Update the session state file with current CODESYS state"""
48
+ try:
49
+ # Get current state
50
+ project_open = False
51
+ project_name = "No project open"
52
+ project_path = "N/A"
53
+
54
+ primary_project = script_engine.projects.primary
55
+ if primary_project:
56
+ project_open = True
57
+ try:
58
+ project_path = os.path.normcase(os.path.abspath(primary_project.path))
59
+ try:
60
+ project_name = primary_project.get_name()
61
+ if not project_name:
62
+ project_name = f"Unnamed (path: {os.path.basename(project_path)})"
63
+ except:
64
+ project_name = f"Unnamed (path: {os.path.basename(project_path)})"
65
+ except Exception as e_path:
66
+ project_path = f"N/A (Error: {e_path})"
67
+ project_name = "Unnamed (Path Error)"
68
+
69
+ # Read current state file
70
+ with open(SESSION_STATE_FILE, 'r') as f:
71
+ session_state = json.load(f)
72
+
73
+ # Update state
74
+ session_state["project_open"] = project_open
75
+ session_state["project_name"] = project_name
76
+ session_state["project_path"] = project_path
77
+ session_state["last_update"] = time.time()
78
+
79
+ # Write updated state
80
+ with open(SESSION_STATE_FILE, 'w') as f:
81
+ json.dump(session_state, f)
82
+
83
+ return True
84
+ except Exception as e:
85
+ print(f"Failed to update session state: {e}")
86
+ return False
87
+
88
+ def execute_command(command_data):
89
+ """Execute a command from the command pipe"""
90
+ try:
91
+ cmd_type = command_data.get("type", "")
92
+ script = command_data.get("script", "")
93
+ cmd_id = command_data.get("id", "unknown")
94
+
95
+ result = {
96
+ "id": cmd_id,
97
+ "success": False,
98
+ "output": "",
99
+ "error": ""
100
+ }
101
+
102
+ if cmd_type == "execute_script" and script:
103
+ # Execute the script in the current CODESYS context
104
+ try:
105
+ # Capture output by redirecting stdout/stderr
106
+ import io
107
+ from contextlib import redirect_stdout, redirect_stderr
108
+
109
+ stdout_buffer = io.StringIO()
110
+ stderr_buffer = io.StringIO()
111
+
112
+ with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
113
+ # Execute the script
114
+ exec(script, globals())
115
+
116
+ # Get the captured output
117
+ stdout_output = stdout_buffer.getvalue()
118
+ stderr_output = stderr_buffer.getvalue()
119
+
120
+ # Check for success/error markers
121
+ if SCRIPT_SUCCESS_MARKER in stdout_output or SCRIPT_SUCCESS_MARKER in stderr_output:
122
+ result["success"] = True
123
+ elif SCRIPT_ERROR_MARKER in stdout_output or SCRIPT_ERROR_MARKER in stderr_output:
124
+ result["success"] = False
125
+ else:
126
+ # No explicit markers, assume success
127
+ result["success"] = True
128
+
129
+ result["output"] = stdout_output
130
+ if stderr_output:
131
+ result["error"] = stderr_output
132
+
133
+ except Exception as e:
134
+ result["success"] = False
135
+ result["error"] = f"Error executing script: {str(e)}"
136
+ result["output"] = f"{SCRIPT_ERROR_MARKER}: {str(e)}"
137
+
138
+ elif cmd_type == "stop":
139
+ # Stop command received
140
+ result["success"] = True
141
+ result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
142
+
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)
152
+
153
+ # Return the result and signal to stop the loop
154
+ return result, True
155
+
156
+ else:
157
+ result["success"] = False
158
+ result["error"] = f"Unknown command type: {cmd_type}"
159
+ result["output"] = f"{SCRIPT_ERROR_MARKER}: Unknown command type: {cmd_type}"
160
+
161
+ # Update session state after command execution
162
+ update_session_state()
163
+
164
+ return result, False
165
+
166
+ except Exception as e:
167
+ return {
168
+ "id": command_data.get("id", "unknown"),
169
+ "success": False,
170
+ "output": f"{SCRIPT_ERROR_MARKER}: Command execution failed",
171
+ "error": str(e)
172
+ }, False
173
+
174
+ def command_loop():
175
+ """Main loop to listen for and execute commands"""
176
+ stop_requested = False
177
+
178
+ print(f"Starting command loop...")
179
+ print(f"Command pipe: {COMMAND_PIPE}")
180
+ print(f"Result pipe: {RESULT_PIPE}")
181
+
182
+ while not stop_requested:
183
+ 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))
210
+
211
+ except Exception as e:
212
+ print(f"Error in command loop: {e}")
213
+ # Short delay to prevent CPU spinning in case of repeated errors
214
+ time.sleep(0.5)
215
+
216
+ print("Command loop stopped.")
217
+
218
+ # Main script
219
+ def main():
220
+ try:
221
+ # Setup session directory and pipes
222
+ if not setup_session_dir():
223
+ sys.exit(1)
224
+
225
+ # Update initial session state
226
+ update_session_state()
227
+
228
+ # Start command loop
229
+ print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
230
+ command_loop()
231
+
232
+ print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
233
+ sys.exit(0)
234
+ except Exception as e:
235
+ print(f"Error in session: {e}")
236
+ print(f"{SCRIPT_ERROR_MARKER}: Session failed - {str(e)}")
237
+ sys.exit(1)
238
+
239
+ if __name__ == "__main__":
240
+ main()
@@ -0,0 +1,94 @@
1
+ import sys, os, json, time, tempfile
2
+
3
+ # Define markers for script results
4
+ SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
5
+ SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
6
+
7
+ # Session state file and pipes
8
+ SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
9
+ 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")
12
+
13
+ def check_session_active():
14
+ """Check if a session is active by reading the state file"""
15
+ try:
16
+ if not os.path.exists(SESSION_STATE_FILE):
17
+ print(f"No session state file found at {SESSION_STATE_FILE}")
18
+ return False
19
+
20
+ 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
27
+
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")
33
+ return False
34
+
35
+ stop_command = {
36
+ "id": f"stop_{time.time()}",
37
+ "type": "stop",
38
+ "script": ""
39
+ }
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:
50
+ 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
63
+
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)
78
+
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")
84
+ else:
85
+ print(f"{SCRIPT_ERROR_MARKER}: Failed to stop session")
86
+ sys.exit(1)
87
+
88
+ except Exception as e:
89
+ print(f"Error stopping session: {e}")
90
+ print(f"{SCRIPT_ERROR_MARKER}: {str(e)}")
91
+ sys.exit(1)
92
+
93
+ if __name__ == "__main__":
94
+ main()