@codesys/mcp-toolkit 1.5.7 → 2.0.0-alpha.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.
- package/CHANGELOG.md +153 -183
- package/LICENSE +21 -21
- package/README.md +224 -107
- package/dist/actions.d.ts +28 -0
- package/dist/actions.js +98 -0
- package/dist/actions.js.map +1 -0
- package/dist/bridge/bridge.d.ts +105 -0
- package/dist/bridge/bridge.js +400 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/codesys/cache.d.ts +48 -0
- package/dist/codesys/cache.js +82 -0
- package/dist/codesys/cache.js.map +1 -0
- package/dist/codesys/changes.d.ts +57 -0
- package/dist/codesys/changes.js +133 -0
- package/dist/codesys/changes.js.map +1 -0
- package/dist/codesys/decl.d.ts +45 -0
- package/dist/codesys/decl.js +251 -0
- package/dist/codesys/decl.js.map +1 -0
- package/dist/codesys/devices.d.ts +34 -0
- package/dist/codesys/devices.js +82 -0
- package/dist/codesys/devices.js.map +1 -0
- package/dist/codesys/diff.d.ts +13 -0
- package/dist/codesys/diff.js +61 -0
- package/dist/codesys/diff.js.map +1 -0
- package/dist/codesys/install.d.ts +40 -0
- package/dist/codesys/install.js +144 -0
- package/dist/codesys/install.js.map +1 -0
- package/dist/codesys/library-browse.d.ts +19 -0
- package/dist/codesys/library-browse.js +57 -0
- package/dist/codesys/library-browse.js.map +1 -0
- package/dist/codesys/outline.d.ts +32 -0
- package/dist/codesys/outline.js +219 -0
- package/dist/codesys/outline.js.map +1 -0
- package/dist/codesys/simulation.d.ts +34 -0
- package/dist/codesys/simulation.js +57 -0
- package/dist/codesys/simulation.js.map +1 -0
- package/dist/codesys/st.d.ts +36 -0
- package/dist/codesys/st.js +69 -0
- package/dist/codesys/st.js.map +1 -0
- package/dist/daemon.d.ts +46 -0
- package/dist/daemon.js +173 -0
- package/dist/daemon.js.map +1 -0
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +33 -0
- package/dist/driver.js.map +1 -0
- package/dist/panel/sim-panel.html +357 -0
- package/dist/panel/viewer.html +126 -0
- package/dist/panel-types.d.ts +20 -0
- package/dist/panel-types.js +3 -0
- package/dist/panel-types.js.map +1 -0
- package/dist/panel.d.ts +27 -0
- package/dist/panel.js +208 -0
- package/dist/panel.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +33 -941
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +103 -0
- package/dist/tools.js +793 -0
- package/dist/tools.js.map +1 -0
- package/dist/viewer.d.ts +14 -0
- package/dist/viewer.js +125 -0
- package/dist/viewer.js.map +1 -0
- package/package.json +37 -21
- package/watcher/mcp_compat.py +147 -0
- package/watcher/mcp_ops.py +1178 -0
- package/watcher/mcp_sim.py +246 -0
- package/watcher/watcher.py +255 -0
- package/.claude/settings.local.json +0 -29
- package/CLAUDE.md +0 -26
- package/CONTRIBUTING.md +0 -72
- package/README.md.enhanced +0 -155
- package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
- package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
- package/dist/bin.js +0 -75
- package/dist/check_session_paths.js +0 -149
- package/dist/codesys_interop.js +0 -435
- package/dist/direct_session_command.js +0 -293
- package/dist/scriptLoader.js +0 -165
- package/dist/scripts/check_status.py +0 -21
- package/dist/scripts/compile_project.py +0 -65
- package/dist/scripts/create_method.py +0 -80
- package/dist/scripts/create_pou.py +0 -102
- package/dist/scripts/create_project.py +0 -116
- package/dist/scripts/create_property.py +0 -78
- package/dist/scripts/ensure_project_open.py +0 -154
- package/dist/scripts/find_object_by_path.py +0 -111
- package/dist/scripts/get_pou_code.py +0 -91
- package/dist/scripts/get_project_structure.py +0 -72
- package/dist/scripts/open_project.py +0 -25
- package/dist/scripts/save_project.py +0 -29
- package/dist/scripts/session_status.py +0 -281
- package/dist/scripts/set_pou_code.py +0 -109
- package/dist/scripts/start_session.py +0 -809
- package/dist/scripts/stop_session.py +0 -324
- package/dist/sessionManager.js +0 -798
- package/docs/configuration.md +0 -141
- package/docs/index.md +0 -26
- package/docs/installation.md +0 -84
- package/docs/session_management.md +0 -110
- package/examples/README.md +0 -78
- package/examples/sample_config.json +0 -32
- package/replacements_session_status.txt +0 -16
- package/scriptDoc.txt +0 -8679
- package/scripts/build.js +0 -25
- package/scripts/fix_fstrings.py +0 -76
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import sys, os, time, traceback
|
|
2
|
-
import tempfile
|
|
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
|
-
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))
|
|
64
|
-
|
|
65
|
-
# Define markers for script results
|
|
66
|
-
SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
|
|
67
|
-
SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
|
|
68
|
-
|
|
69
|
-
# Session state and command files
|
|
70
|
-
SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
|
|
71
|
-
SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
|
|
72
|
-
COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
|
|
73
|
-
RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
|
|
74
|
-
SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
|
|
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
|
-
|
|
85
|
-
def stop_session():
|
|
86
|
-
"""Stop a running CODESYS session"""
|
|
87
|
-
try:
|
|
88
|
-
print("Attempting to stop CODESYS session")
|
|
89
|
-
|
|
90
|
-
# Check if session directory exists
|
|
91
|
-
if not os.path.exists(SESSION_DIR):
|
|
92
|
-
print("{0}: No session directory found at {1}".format(SCRIPT_SUCCESS_MARKER, SESSION_DIR))
|
|
93
|
-
# Consider this a success since there's no session to stop
|
|
94
|
-
return True
|
|
95
|
-
|
|
96
|
-
# Check if session state file exists
|
|
97
|
-
if not os.path.exists(SESSION_STATE_FILE):
|
|
98
|
-
print("{0}: No session state file found at {1}".format(SCRIPT_SUCCESS_MARKER, SESSION_STATE_FILE))
|
|
99
|
-
# No state file means no active session - clear any other potential session files
|
|
100
|
-
try:
|
|
101
|
-
for filename in os.listdir(SESSION_DIR):
|
|
102
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
103
|
-
try:
|
|
104
|
-
if os.path.isfile(filepath):
|
|
105
|
-
os.unlink(filepath)
|
|
106
|
-
print("Removed orphaned session file: {0}".format(filepath))
|
|
107
|
-
except Exception as e:
|
|
108
|
-
print("Error removing file {0}: {1}".format(filepath, e))
|
|
109
|
-
except Exception as e:
|
|
110
|
-
print("Error cleaning session directory: {0}".format(e))
|
|
111
|
-
|
|
112
|
-
return True
|
|
113
|
-
|
|
114
|
-
# Read session state
|
|
115
|
-
try:
|
|
116
|
-
with open(SESSION_STATE_FILE, 'r') as f:
|
|
117
|
-
session_state = json.load(f)
|
|
118
|
-
except Exception as e:
|
|
119
|
-
print("Error reading session state file: {0}".format(e))
|
|
120
|
-
# Force cleanup regardless of the error
|
|
121
|
-
try:
|
|
122
|
-
for filename in os.listdir(SESSION_DIR):
|
|
123
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
124
|
-
try:
|
|
125
|
-
if os.path.isfile(filepath):
|
|
126
|
-
os.unlink(filepath)
|
|
127
|
-
print("Removed session file due to corrupt state: {0}".format(filepath))
|
|
128
|
-
except Exception as rm_err:
|
|
129
|
-
print("Error removing file {0}: {1}".format(filepath, rm_err))
|
|
130
|
-
except Exception as dir_err:
|
|
131
|
-
print("Error cleaning session directory: {0}".format(dir_err))
|
|
132
|
-
|
|
133
|
-
print("{0}: Session files cleaned up forcefully due to corrupt state".format(SCRIPT_SUCCESS_MARKER))
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
|
-
# Check if session is already marked as inactive
|
|
137
|
-
if not session_state.get('active', False) or session_state.get('status') in ['stopped', 'error', 'timeout']:
|
|
138
|
-
print("{0}: Session is already marked as {1}".format(
|
|
139
|
-
SCRIPT_SUCCESS_MARKER, session_state.get('status', 'inactive')))
|
|
140
|
-
|
|
141
|
-
# Remove session files to ensure a clean state
|
|
142
|
-
try:
|
|
143
|
-
for filename in os.listdir(SESSION_DIR):
|
|
144
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
145
|
-
try:
|
|
146
|
-
if os.path.isfile(filepath):
|
|
147
|
-
os.unlink(filepath)
|
|
148
|
-
print("Removed session file for cleanup: {0}".format(filepath))
|
|
149
|
-
except Exception as e:
|
|
150
|
-
print("Error removing file {0}: {1}".format(filepath, e))
|
|
151
|
-
except Exception as e:
|
|
152
|
-
print("Error cleaning session directory: {0}".format(e))
|
|
153
|
-
|
|
154
|
-
return True
|
|
155
|
-
|
|
156
|
-
# Get lock file age to check if session is responsive
|
|
157
|
-
lock_is_stale = False
|
|
158
|
-
current_time = time.time()
|
|
159
|
-
|
|
160
|
-
try:
|
|
161
|
-
if os.path.exists(SESSION_LOCK_FILE):
|
|
162
|
-
with open(SESSION_LOCK_FILE, 'r') as f:
|
|
163
|
-
lock_time = float(f.read().strip())
|
|
164
|
-
lock_age = current_time - lock_time
|
|
165
|
-
|
|
166
|
-
if lock_age > 30:
|
|
167
|
-
print("WARNING: Session lock file is stale ({0:.1f} seconds old). Session may have crashed.".format(lock_age))
|
|
168
|
-
lock_is_stale = True
|
|
169
|
-
else:
|
|
170
|
-
print("WARNING: Session lock file not found. Session may have crashed.")
|
|
171
|
-
lock_is_stale = True
|
|
172
|
-
except Exception as e:
|
|
173
|
-
print("Warning: Error reading lock file: {0}".format(e))
|
|
174
|
-
lock_is_stale = True
|
|
175
|
-
|
|
176
|
-
# If the command file doesn't exist, we have a zombie session
|
|
177
|
-
if not os.path.exists(COMMAND_FILE):
|
|
178
|
-
print("WARNING: Command file not found. Session is in a zombie state.")
|
|
179
|
-
lock_is_stale = True
|
|
180
|
-
|
|
181
|
-
# If the session appears to be a zombie (stale lock), don't try to send a command
|
|
182
|
-
# Just force cleanup immediately
|
|
183
|
-
if lock_is_stale:
|
|
184
|
-
print("Session appears to be unresponsive. Skipping command and forcing cleanup.")
|
|
185
|
-
try:
|
|
186
|
-
# Update state file first
|
|
187
|
-
try:
|
|
188
|
-
session_state['active'] = False
|
|
189
|
-
session_state['status'] = "error"
|
|
190
|
-
session_state['end_time'] = current_time
|
|
191
|
-
session_state['error_reason'] = "Unresponsive session, forced cleanup"
|
|
192
|
-
|
|
193
|
-
with open(SESSION_STATE_FILE, 'w') as f:
|
|
194
|
-
json.dump(session_state, f, indent=2)
|
|
195
|
-
print("Updated session state to error before cleanup")
|
|
196
|
-
except Exception as state_err:
|
|
197
|
-
print("Warning: Failed to update session state file: {0}".format(state_err))
|
|
198
|
-
|
|
199
|
-
# Remove all session files
|
|
200
|
-
for filename in os.listdir(SESSION_DIR):
|
|
201
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
202
|
-
try:
|
|
203
|
-
if os.path.isfile(filepath):
|
|
204
|
-
os.unlink(filepath)
|
|
205
|
-
print("Removed zombie session file: {0}".format(filepath))
|
|
206
|
-
except Exception as e:
|
|
207
|
-
print("Error removing file {0}: {1}".format(filepath, e))
|
|
208
|
-
|
|
209
|
-
print("{0}: Zombie session files cleaned up forcefully".format(SCRIPT_SUCCESS_MARKER))
|
|
210
|
-
return True
|
|
211
|
-
except Exception as e:
|
|
212
|
-
print("{0}: Failed to force cleanup - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
|
|
213
|
-
return False
|
|
214
|
-
|
|
215
|
-
# For responsive sessions, try to send a stop command first
|
|
216
|
-
try:
|
|
217
|
-
# Create stop command
|
|
218
|
-
stop_command = {
|
|
219
|
-
"id": "stop_{0}".format(int(time.time())),
|
|
220
|
-
"type": "stop",
|
|
221
|
-
"timestamp": current_time
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
# Write command to file with pretty-printing
|
|
225
|
-
command_data = {
|
|
226
|
-
"status": "pending",
|
|
227
|
-
"command": stop_command,
|
|
228
|
-
"timestamp": current_time
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
print("Writing stop command to command file: {0}".format(COMMAND_FILE))
|
|
232
|
-
with open(COMMAND_FILE, 'w') as f:
|
|
233
|
-
json.dump(command_data, f, indent=2)
|
|
234
|
-
|
|
235
|
-
print("Stop command sent to session")
|
|
236
|
-
except Exception as cmd_err:
|
|
237
|
-
print("Error sending stop command: {0}".format(cmd_err))
|
|
238
|
-
# Continue to forced cleanup
|
|
239
|
-
|
|
240
|
-
# Wait for result (with timeout)
|
|
241
|
-
timeout = 15 # seconds (increased timeout for more reliability)
|
|
242
|
-
start_time = time.time()
|
|
243
|
-
result_received = False
|
|
244
|
-
|
|
245
|
-
while not result_received and (time.time() - start_time) < timeout:
|
|
246
|
-
# Check if result file exists
|
|
247
|
-
if not os.path.exists(RESULT_FILE):
|
|
248
|
-
time.sleep(0.5)
|
|
249
|
-
continue
|
|
250
|
-
|
|
251
|
-
try:
|
|
252
|
-
with open(RESULT_FILE, 'r') as f:
|
|
253
|
-
result_data = json.load(f)
|
|
254
|
-
|
|
255
|
-
if result_data.get("status") == "complete" and result_data.get("result"):
|
|
256
|
-
result = result_data.get("result")
|
|
257
|
-
if result.get('id') == stop_command['id']:
|
|
258
|
-
result_received = True
|
|
259
|
-
print("Session stop result: {0}".format(result.get('output', 'No output')))
|
|
260
|
-
break
|
|
261
|
-
except Exception as e:
|
|
262
|
-
print("Error reading result: {0}".format(e))
|
|
263
|
-
time.sleep(0.5)
|
|
264
|
-
|
|
265
|
-
# Update session state to indicate it's stopped regardless of result
|
|
266
|
-
try:
|
|
267
|
-
with open(SESSION_STATE_FILE, 'r') as f:
|
|
268
|
-
session_state = json.load(f)
|
|
269
|
-
|
|
270
|
-
session_state['active'] = False
|
|
271
|
-
session_state['status'] = "stopped"
|
|
272
|
-
session_state['end_time'] = time.time()
|
|
273
|
-
|
|
274
|
-
with open(SESSION_STATE_FILE, 'w') as f:
|
|
275
|
-
json.dump(session_state, f, indent=2)
|
|
276
|
-
print("Updated session state to stopped")
|
|
277
|
-
except Exception as e:
|
|
278
|
-
print("Warning: Failed to update session state file: {0}".format(e))
|
|
279
|
-
|
|
280
|
-
# Remove all session files for a clean state
|
|
281
|
-
print("Cleaning up all session files")
|
|
282
|
-
try:
|
|
283
|
-
for filename in os.listdir(SESSION_DIR):
|
|
284
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
285
|
-
try:
|
|
286
|
-
if os.path.isfile(filepath):
|
|
287
|
-
os.unlink(filepath)
|
|
288
|
-
print("Removed session file: {0}".format(filepath))
|
|
289
|
-
except Exception as e:
|
|
290
|
-
print("Error removing file {0}: {1}".format(filepath, e))
|
|
291
|
-
|
|
292
|
-
print("{0}: Session stopped and files cleaned up successfully".format(SCRIPT_SUCCESS_MARKER))
|
|
293
|
-
return True
|
|
294
|
-
except Exception as e:
|
|
295
|
-
print("{0}: Error during final cleanup - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
|
|
296
|
-
return False
|
|
297
|
-
|
|
298
|
-
except Exception as e:
|
|
299
|
-
print("{0}: Error stopping session - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
|
|
300
|
-
traceback.print_exc()
|
|
301
|
-
|
|
302
|
-
# Try one final desperate cleanup even if everything else failed
|
|
303
|
-
try:
|
|
304
|
-
if os.path.exists(SESSION_DIR):
|
|
305
|
-
for filename in os.listdir(SESSION_DIR):
|
|
306
|
-
filepath = os.path.join(SESSION_DIR, filename)
|
|
307
|
-
try:
|
|
308
|
-
if os.path.isfile(filepath):
|
|
309
|
-
os.unlink(filepath)
|
|
310
|
-
print("Emergency cleanup: Removed session file: {0}".format(filepath))
|
|
311
|
-
except Exception:
|
|
312
|
-
pass
|
|
313
|
-
print("Emergency session cleanup attempted after error")
|
|
314
|
-
except Exception:
|
|
315
|
-
pass
|
|
316
|
-
|
|
317
|
-
return False
|
|
318
|
-
|
|
319
|
-
# Main script
|
|
320
|
-
if __name__ == "__main__":
|
|
321
|
-
if stop_session():
|
|
322
|
-
sys.exit(0)
|
|
323
|
-
else:
|
|
324
|
-
sys.exit(1)
|