@codesys/mcp-toolkit 1.1.3 → 1.2.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/dist/server.js CHANGED
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * IMPORTANT: This server receives configuration as parameters from bin.ts,
8
8
  * which helps avoid issues with command-line argument passing in different execution environments.
9
- * (Incorporates script templates from v1.6.9 and improved tool descriptions)
10
9
  */
11
10
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
11
  if (k2 === undefined) k2 = k;
@@ -64,9 +63,11 @@ console.error(`>>> Initial CWD: ${process.cwd()}`);
64
63
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
65
64
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
66
65
  const zod_1 = require("zod");
67
- const codesys_interop_1 = require("./codesys_interop"); // Assumes this utility exists
66
+ const codesys_interop_1 = require("./codesys_interop");
68
67
  const path = __importStar(require("path"));
69
- const promises_1 = require("fs/promises"); // For file existence check (async)
68
+ const promises_1 = require("fs/promises");
69
+ const scriptLoader_1 = require("./scriptLoader");
70
+ const sessionManager_1 = require("./sessionManager");
70
71
  // --- Wrap server logic in an exported function ---
71
72
  function startMcpServer(config) {
72
73
  return __awaiter(this, void 0, void 0, function* () {
@@ -80,16 +81,13 @@ function startMcpServer(config) {
80
81
  console.error(`SERVER.TS: Using CODESYS Path: ${codesysExePath}`);
81
82
  console.error(`SERVER.TS: Using CODESYS Profile: ${codesysProfileName}`);
82
83
  // --- Sanity check - confirm the path exists if possible ---
83
- // This helps catch configuration issues early and prevents runtime failures
84
84
  console.error(`SERVER.TS: Checking existence of CODESYS executable: ${codesysExePath}`);
85
85
  try {
86
86
  // Using sync check here as it's part of initial setup before async operations start
87
87
  const fsChecker = require('fs');
88
88
  if (!fsChecker.existsSync(codesysExePath)) {
89
89
  console.error(`SERVER.TS ERROR: Determined CODESYS executable path does not exist: ${codesysExePath}`);
90
- // Consider throwing an error instead of exiting if bin.ts handles the catch
91
90
  throw new Error(`CODESYS executable not found at specified path: ${codesysExePath}`);
92
- // process.exit(1); // Avoid process.exit inside library functions if possible
93
91
  }
94
92
  else {
95
93
  console.error(`SERVER.TS: Confirmed CODESYS executable exists.`);
@@ -98,7 +96,6 @@ function startMcpServer(config) {
98
96
  catch (err) {
99
97
  console.error(`SERVER.TS ERROR: Error checking CODESYS path existence: ${err.message}`);
100
98
  throw err; // Rethrow the error to be caught by the caller (bin.ts)
101
- // process.exit(1);
102
99
  }
103
100
  // --- End Configuration Handling ---
104
101
  // --- Helper Function (fileExists - async version) ---
@@ -117,914 +114,31 @@ function startMcpServer(config) {
117
114
  });
118
115
  }
119
116
  // --- End Helper Function ---
117
+ // --- Initialize Session Manager ---
118
+ const sessionManager = new sessionManager_1.CodesysSessionManager(codesysExePath, codesysProfileName);
119
+ console.error('SERVER.TS: Session manager initialized');
120
+ // --- End Session Manager Initialization ---
121
+ // --- Preload script templates ---
122
+ try {
123
+ yield (0, scriptLoader_1.preloadAllScriptTemplates)();
124
+ }
125
+ catch (err) {
126
+ console.error(`SERVER.TS ERROR: Error preloading script templates: ${err.message}`);
127
+ // Continue execution, as script templates will be loaded on demand as needed
128
+ }
120
129
  // --- MCP Server Initialization ---
121
130
  console.error("SERVER.TS: Initializing McpServer...");
122
131
  const server = new mcp_js_1.McpServer({
123
132
  name: "CODESYS Control MCP Server",
124
- version: "1.7.1", // Update version as needed
133
+ version: "1.8.0", // Updated version for refactored code
125
134
  capabilities: {
126
- // Explicitly declare capabilities - enables listChanged notifications if supported by SDK
127
- resources: { listChanged: true }, // Assuming you might want to notify if resources change dynamically
128
- tools: { listChanged: true } // Assuming you might want to notify if tools change dynamically (less common)
135
+ resources: { listChanged: true },
136
+ tools: { listChanged: true }
129
137
  }
130
138
  });
131
139
  console.error("SERVER.TS: McpServer instance created.");
132
140
  // --- End MCP Server Initialization ---
133
- // --- Python Script Templates (Imported from v1.6.9) ---
134
- const ENSURE_PROJECT_OPEN_PYTHON_SNIPPET = `
135
- import sys
136
- import scriptengine as script_engine
137
- import os
138
- import time
139
- import traceback
140
-
141
- # --- Function to ensure the correct project is open ---
142
- MAX_RETRIES = 3
143
- RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
144
-
145
- def ensure_project_open(target_project_path):
146
- print("DEBUG: Ensuring project is open: %s" % target_project_path)
147
- # Normalize target path once
148
- normalized_target_path = os.path.normcase(os.path.abspath(target_project_path))
149
-
150
- for attempt in range(MAX_RETRIES):
151
- print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
152
- primary_project = None
153
- try:
154
- # Getting primary project might fail if CODESYS instance is unstable
155
- primary_project = script_engine.projects.primary
156
- except Exception as primary_err:
157
- print("WARN: Error getting primary project: %s. Assuming none." % primary_err)
158
- # traceback.print_exc() # Optional: Print stack trace for this error
159
- primary_project = None
160
-
161
- current_project_path = ""
162
- project_ok = False # Flag to check if target is confirmed primary and accessible
163
-
164
- if primary_project:
165
- try:
166
- # Getting path should be relatively safe if primary_project object exists
167
- current_project_path = os.path.normcase(os.path.abspath(primary_project.path))
168
- print("DEBUG: Current primary project path: %s" % current_project_path)
169
- if current_project_path == normalized_target_path:
170
- # Found the right project as primary, now check if it's usable
171
- print("DEBUG: Target project path matches primary. Checking access...")
172
- try:
173
- # Try a relatively safe operation to confirm object usability
174
- # Getting children count is a reasonable check
175
- _ = len(primary_project.get_children(False))
176
- print("DEBUG: Target project '%s' is primary and accessible." % target_project_path)
177
- project_ok = True
178
- return primary_project # SUCCESS CASE 1: Already open and accessible
179
- except Exception as access_err:
180
- # Project found, but accessing it failed. Might be unstable.
181
- print("WARN: Primary project access check failed for '%s': %s. Will attempt reopen." % (current_project_path, access_err))
182
- # traceback.print_exc() # Optional: Print stack trace
183
- primary_project = None # Force reopen by falling through
184
- else:
185
- # A *different* project is primary
186
- print("DEBUG: Primary project is '%s', not the target '%s'." % (current_project_path, normalized_target_path))
187
- # Consider closing the wrong project if causing issues, but for now, just open target
188
- # try:
189
- # print("DEBUG: Closing incorrect primary project '%s'..." % current_project_path)
190
- # primary_project.close() # Be careful with unsaved changes
191
- # except Exception as close_err:
192
- # print("WARN: Failed to close incorrect primary project: %s" % close_err)
193
- primary_project = None # Force open target project
194
-
195
- except Exception as path_err:
196
- # Failed even to get the path of the supposed primary project
197
- print("WARN: Could not get path of current primary project: %s. Assuming not the target." % path_err)
198
- # traceback.print_exc() # Optional: Print stack trace
199
- primary_project = None # Force open target project
200
-
201
- # If target project not confirmed as primary and accessible, attempt to open/reopen
202
- if not project_ok:
203
- # Log clearly whether we are opening initially or reopening
204
- if primary_project is None and current_project_path == "":
205
- print("DEBUG: No primary project detected. Attempting to open target: %s" % target_project_path)
206
- elif primary_project is None and current_project_path != "":
207
- print("DEBUG: Primary project was '%s' but failed access check or needed close. Attempting to open target: %s" % (current_project_path, target_project_path))
208
- else: # Includes cases where wrong project was open
209
- print("DEBUG: Target project not primary or initial check failed. Attempting to open/reopen: %s" % target_project_path)
210
-
211
- try:
212
- # Set flags for silent opening, handle potential attribute errors
213
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
214
- # try:
215
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
216
- # except AttributeError:
217
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
218
- # update_mode = 3 # 1=NoUpdates, 2=SilentMode
219
-
220
- opened_project = None
221
- try:
222
- # The actual open call
223
- print("DEBUG: Calling script_engine.projects.open('%s', update_flags=%s)..." % (target_project_path, update_mode))
224
- opened_project = script_engine.projects.open(target_project_path, update_flags=update_mode)
225
-
226
- if not opened_project:
227
- # This is a critical failure if open returns None without exception
228
- print("ERROR: projects.open returned None for %s on attempt %d" % (target_project_path, attempt + 1))
229
- # Allow retry loop to continue
230
- else:
231
- # Open call returned *something*, let's verify
232
- print("DEBUG: projects.open call returned an object for: %s" % target_project_path)
233
- print("DEBUG: Pausing for stabilization after open...")
234
- time.sleep(RETRY_DELAY) # Give CODESYS time
235
- # Re-verify: Is the project now primary and accessible?
236
- recheck_primary = None
237
- try: recheck_primary = script_engine.projects.primary
238
- except Exception as recheck_primary_err: print("WARN: Error getting primary project after reopen: %s" % recheck_primary_err)
239
-
240
- if recheck_primary:
241
- recheck_path = ""
242
- try: # Getting path might fail
243
- recheck_path = os.path.normcase(os.path.abspath(recheck_primary.path))
244
- except Exception as recheck_path_err:
245
- print("WARN: Failed to get path after reopen: %s" % recheck_path_err)
246
-
247
- if recheck_path == normalized_target_path:
248
- print("DEBUG: Target project confirmed as primary after reopening.")
249
- try: # Final sanity check
250
- _ = len(recheck_primary.get_children(False))
251
- print("DEBUG: Reopened project basic access confirmed.")
252
- return recheck_primary # SUCCESS CASE 2: Successfully opened/reopened
253
- except Exception as access_err_reopen:
254
- print("WARN: Reopened project (%s) basic access check failed: %s." % (normalized_target_path, access_err_reopen))
255
- # traceback.print_exc() # Optional
256
- # Allow retry loop to continue
257
- else:
258
- print("WARN: Different project is primary after reopening! Expected '%s', got '%s'." % (normalized_target_path, recheck_path))
259
- # Allow retry loop to continue, maybe it fixes itself
260
- else:
261
- print("WARN: No primary project found after reopening attempt %d!" % (attempt+1))
262
- # Allow retry loop to continue
263
-
264
- except Exception as open_err:
265
- # Catch errors during the open call itself
266
- print("ERROR: Exception during projects.open call on attempt %d: %s" % (attempt + 1, open_err))
267
- traceback.print_exc() # Crucial for diagnosing open failures
268
- # Allow retry loop to continue
269
-
270
- except Exception as outer_open_err:
271
- # Catch errors in the flag setup etc.
272
- print("ERROR: Unexpected error during open setup/logic attempt %d: %s" % (attempt + 1, outer_open_err))
273
- traceback.print_exc()
274
-
275
- # If we didn't return successfully in this attempt, wait before retrying
276
- if attempt < MAX_RETRIES - 1:
277
- print("DEBUG: Ensure project attempt %d did not succeed. Waiting %f seconds..." % (attempt + 1, RETRY_DELAY))
278
- time.sleep(RETRY_DELAY)
279
- else: # Last attempt failed
280
- print("ERROR: Failed all ensure_project_open attempts for %s." % normalized_target_path)
281
-
282
-
283
- # If all retries fail after the loop
284
- raise RuntimeError("Failed to ensure project '%s' is open and accessible after %d attempts." % (target_project_path, MAX_RETRIES))
285
- # --- End of function ---
286
-
287
- # Placeholder for the project file path (must be set in scripts using this snippet)
288
- PROJECT_FILE_PATH = r"{PROJECT_FILE_PATH}"
289
- `;
290
- const CHECK_STATUS_SCRIPT = `
291
- import sys, scriptengine as script_engine, os, traceback
292
- project_open = False; project_name = "No project open"; project_path = "N/A"; scripting_ok = False
293
- try:
294
- scripting_ok = True; primary_project = script_engine.projects.primary
295
- if primary_project:
296
- project_open = True
297
- try:
298
- project_path = os.path.normcase(os.path.abspath(primary_project.path))
299
- try:
300
- project_name = primary_project.get_name() # Might fail
301
- if not project_name: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
302
- except: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
303
- except Exception as e_path: project_path = "N/A (Error: %s)" % e_path; project_name = "Unnamed (Path Error)"
304
- print("Project Open: %s" % project_open); print("Project Name: %s" % project_name)
305
- print("Project Path: %s" % project_path); print("Scripting OK: %s" % scripting_ok)
306
- print("SCRIPT_SUCCESS: Status check complete."); sys.exit(0)
307
- except Exception as e:
308
- error_message = "Error during status check: %s" % e
309
- print(error_message); print("Scripting OK: False")
310
- # traceback.print_exc() # Optional traceback
311
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
312
- `;
313
- const OPEN_PROJECT_SCRIPT_TEMPLATE = `
314
- import sys, scriptengine as script_engine, os, traceback
315
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
316
- try:
317
- project = ensure_project_open(PROJECT_FILE_PATH)
318
- # Get name from object if possible, otherwise use path basename
319
- proj_name = "Unknown"
320
- try:
321
- if project: proj_name = project.get_name() or os.path.basename(PROJECT_FILE_PATH)
322
- else: proj_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
323
- except Exception:
324
- proj_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
325
- print("Project Opened: %s" % proj_name)
326
- print("SCRIPT_SUCCESS: Project opened successfully.")
327
- sys.exit(0)
328
- except Exception as e:
329
- error_message = "Error opening project %s: %s" % (PROJECT_FILE_PATH, e)
330
- print(error_message)
331
- traceback.print_exc()
332
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
333
- `;
334
- const CREATE_PROJECT_SCRIPT_TEMPLATE = `
335
- import sys, scriptengine as script_engine, os, shutil, time, traceback
336
- # Placeholders
337
- TEMPLATE_PROJECT_PATH = r'{TEMPLATE_PROJECT_PATH}' # Path to Standard.project
338
- PROJECT_FILE_PATH = r'{PROJECT_FILE_PATH}' # Path for the new project (Target Path)
339
- try:
340
- print("DEBUG: Python script create_project (copy from template):")
341
- print("DEBUG: Template Source = %s" % TEMPLATE_PROJECT_PATH)
342
- print("DEBUG: Target Path = %s" % PROJECT_FILE_PATH)
343
- if not PROJECT_FILE_PATH: raise ValueError("Target project file path empty.")
344
- if not TEMPLATE_PROJECT_PATH: raise ValueError("Template project file path empty.")
345
- if not os.path.exists(TEMPLATE_PROJECT_PATH): raise IOError("Template project file not found: %s" % TEMPLATE_PROJECT_PATH)
346
-
347
- # 1. Copy the template project file to the new location
348
- target_dir = os.path.dirname(PROJECT_FILE_PATH)
349
- if not os.path.exists(target_dir): print("DEBUG: Creating target directory: %s" % target_dir); os.makedirs(target_dir)
350
- # Check if target file already exists
351
- if os.path.exists(PROJECT_FILE_PATH): print("WARN: Target project file already exists, overwriting: %s" % PROJECT_FILE_PATH)
352
-
353
- print("DEBUG: Copying '%s' to '%s'..." % (TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH))
354
- shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH) # copy2 preserves metadata
355
- print("DEBUG: File copy complete.")
356
-
357
- # 2. Open the newly copied project file
358
- print("DEBUG: Opening the copied project: %s" % PROJECT_FILE_PATH)
359
- # Set flags for silent opening
360
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
361
- # try:
362
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
363
- # except AttributeError:
364
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
365
- # update_mode = 3
366
-
367
- project = script_engine.projects.open(PROJECT_FILE_PATH, update_flags=update_mode)
368
- print("DEBUG: script_engine.projects.open returned: %s" % project)
369
- if project:
370
- print("DEBUG: Pausing briefly after open...")
371
- time.sleep(1.0) # Allow CODESYS to potentially initialize things
372
- try:
373
- print("DEBUG: Explicitly saving project after opening copy...")
374
- project.save();
375
- print("DEBUG: Project save after opening copy succeeded.")
376
- except Exception as save_err:
377
- print("WARN: Explicit save after opening copy failed: %s" % save_err)
378
- # Decide if this is critical - maybe not, but good to know.
379
- print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
380
- print("SCRIPT_SUCCESS: Project copied from template and opened successfully.")
381
- sys.exit(0)
382
- else:
383
- error_message = "Failed to open project copy %s after copying template %s. projects.open returned None." % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH)
384
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
385
- except Exception as e:
386
- detailed_error = traceback.format_exc()
387
- error_message = "Error creating project '%s' from template '%s': %s\\n%s" % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH, e, detailed_error)
388
- print(error_message); print("SCRIPT_ERROR: Error copying/opening template: %s" % e); sys.exit(1)
389
- `;
390
- const SAVE_PROJECT_SCRIPT_TEMPLATE = `
391
- import sys, scriptengine as script_engine, os, traceback
392
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
393
- try:
394
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
395
- # Get name from object if possible, otherwise use path basename
396
- project_name = "Unknown"
397
- try:
398
- if primary_project: project_name = primary_project.get_name() or os.path.basename(PROJECT_FILE_PATH)
399
- else: project_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
400
- except Exception:
401
- project_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
402
-
403
- print("DEBUG: Saving project: %s (%s)" % (project_name, PROJECT_FILE_PATH))
404
- primary_project.save()
405
- print("DEBUG: project.save() executed.")
406
- print("Project Saved: %s" % project_name)
407
- print("SCRIPT_SUCCESS: Project saved successfully.")
408
- sys.exit(0)
409
- except Exception as e:
410
- error_message = "Error saving project %s: %s" % (PROJECT_FILE_PATH, e)
411
- print(error_message)
412
- traceback.print_exc()
413
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
414
- `;
415
- const FIND_OBJECT_BY_PATH_PYTHON_SNIPPET = `
416
- import traceback
417
- # --- Find object by path function ---
418
- def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
419
- print("DEBUG: Finding %s by path: '%s'" % (target_type_name, full_path))
420
- normalized_path = full_path.replace('\\\\', '/').strip('/')
421
- path_parts = normalized_path.split('/')
422
- if not path_parts:
423
- print("ERROR: Path is empty.")
424
- return None
425
-
426
- # Determine the actual starting node (project or application)
427
- project = start_node # Assume start_node is project initially
428
- if not hasattr(start_node, 'active_application') and hasattr(start_node, 'project'):
429
- # If start_node is not project but has project ref (e.g., an application), get the project
430
- try: project = start_node.project
431
- except Exception as proj_ref_err:
432
- print("WARN: Could not get project reference from start_node: %s" % proj_ref_err)
433
- # Proceed assuming start_node might be the project anyway or search fails
434
-
435
- # Try to get the application object robustly if we think we have the project
436
- app = None
437
- if hasattr(project, 'active_application'):
438
- try: app = project.active_application
439
- except Exception: pass # Ignore errors getting active app
440
- if not app:
441
- try:
442
- apps = project.find("Application", True) # Search recursively
443
- if apps: app = apps[0]
444
- except Exception: pass
445
-
446
- # Check if the first path part matches the application name
447
- app_name_lower = ""
448
- if app:
449
- try: app_name_lower = (app.get_name() or "application").lower()
450
- except Exception: app_name_lower = "application" # Fallback
451
-
452
- # Decide where to start the traversal
453
- current_obj = start_node # Default to the node passed in
454
- if hasattr(project, 'active_application'): # Only adjust if start_node was likely the project
455
- if app and path_parts[0].lower() == app_name_lower:
456
- print("DEBUG: Path starts with Application name '%s'. Beginning search there." % path_parts[0])
457
- current_obj = app
458
- path_parts = path_parts[1:] # Consume the app name part
459
- # If path was *only* the application name
460
- if not path_parts:
461
- print("DEBUG: Target path is the Application object itself.")
462
- return current_obj
463
- else:
464
- print("DEBUG: Path does not start with Application name. Starting search from project root.")
465
- current_obj = project # Start search from the project root
466
- else:
467
- print("DEBUG: Starting search from originally provided node.")
468
-
469
-
470
- # Traverse the remaining path parts
471
- parent_path_str = getattr(current_obj, 'get_name', lambda: str(current_obj))() # Safer name getting
472
-
473
- for i, part_name in enumerate(path_parts):
474
- is_last_part = (i == len(path_parts) - 1)
475
- print("DEBUG: Searching for part [%d/%d]: '%s' under '%s'" % (i+1, len(path_parts), part_name, parent_path_str))
476
- found_in_parent = None
477
- try:
478
- # Prioritize non-recursive find for direct children
479
- children_of_current = current_obj.get_children(False)
480
- print("DEBUG: Found %d direct children under '%s'." % (len(children_of_current), parent_path_str))
481
- for child in children_of_current:
482
- child_name = getattr(child, 'get_name', lambda: None)() # Safer name getting
483
- # print("DEBUG: Checking child: '%s'" % child_name) # Verbose
484
- if child_name == part_name:
485
- found_in_parent = child
486
- print("DEBUG: Found direct child matching '%s'." % part_name)
487
- break # Found direct child, stop searching children
488
-
489
- # If not found directly, AND it's the last part, try recursive find from current parent
490
- if not found_in_parent and is_last_part:
491
- print("DEBUG: Direct find failed for last part '%s'. Trying recursive find under '%s'." % (part_name, parent_path_str))
492
- found_recursive_list = current_obj.find(part_name, True) # Recursive find
493
- if found_recursive_list:
494
- # Maybe add a check here if multiple are found?
495
- found_in_parent = found_recursive_list[0] # Take the first match
496
- print("DEBUG: Found last part '%s' recursively." % part_name)
497
- else:
498
- print("DEBUG: Recursive find also failed for last part '%s'." % part_name)
499
-
500
- # Update current object if found
501
- if found_in_parent:
502
- current_obj = found_in_parent
503
- parent_path_str = getattr(current_obj, 'get_name', lambda: part_name)() # Safer name getting
504
- print("DEBUG: Stepped into '%s'." % parent_path_str)
505
- else:
506
- # If not found at any point, the path is invalid from this parent
507
- print("ERROR: Path part '%s' not found under '%s'." % (part_name, parent_path_str))
508
- return None # Path broken
509
-
510
- except Exception as find_err:
511
- print("ERROR: Exception while searching for '%s' under '%s': %s" % (part_name, parent_path_str, find_err))
512
- traceback.print_exc()
513
- return None # Error during search
514
-
515
- # Final verification (optional but recommended): Check if the found object's name matches the last part
516
- final_expected_name = full_path.split('/')[-1]
517
- found_final_name = getattr(current_obj, 'get_name', lambda: None)() # Safer name getting
518
-
519
- if found_final_name == final_expected_name:
520
- print("DEBUG: Final %s found and name verified for path '%s': %s" % (target_type_name, full_path, found_final_name))
521
- return current_obj
522
- else:
523
- print("ERROR: Traversal ended on object '%s' but expected final name was '%s'." % (found_final_name, final_expected_name))
524
- return None # Name mismatch implies target not found as expected
525
-
526
- # --- End of find object function ---
527
- `;
528
- const CREATE_POU_SCRIPT_TEMPLATE = `
529
- import sys, scriptengine as script_engine, os, traceback
530
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
531
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
532
- POU_NAME = "{POU_NAME}"; POU_TYPE_STR = "{POU_TYPE_STR}"; IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"; PARENT_PATH_REL = "{PARENT_PATH}"
533
- pou_type_map = { "Program": script_engine.PouType.Program, "FunctionBlock": script_engine.PouType.FunctionBlock, "Function": script_engine.PouType.Function }
534
- # Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
535
- # lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
536
-
537
- try:
538
- print("DEBUG: create_pou script: Name='%s', Type='%s', Lang='%s', ParentPath='%s', Project='%s'" % (POU_NAME, POU_TYPE_STR, IMPL_LANGUAGE_STR, PARENT_PATH_REL, PROJECT_FILE_PATH))
539
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
540
- if not POU_NAME: raise ValueError("POU name empty.")
541
- if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
542
-
543
- # Resolve POU Type Enum
544
- pou_type_enum = pou_type_map.get(POU_TYPE_STR)
545
- if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
546
-
547
- # Find parent object using the robust function
548
- parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
549
- if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
550
-
551
- parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
552
- print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
553
-
554
- # Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
555
- if not hasattr(parent_object, 'create_pou'):
556
- raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
557
-
558
- # Set language GUID to None (let CODESYS default based on parent/settings)
559
- lang_guid = None
560
- print("DEBUG: Setting language to None (will use default).")
561
- # Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
562
-
563
- print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
564
-
565
- # Call create_pou using keyword arguments
566
- new_pou = parent_object.create_pou(
567
- name=POU_NAME,
568
- type=pou_type_enum,
569
- language=lang_guid # Pass None
570
- )
571
-
572
- print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
573
- if new_pou:
574
- new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
575
- print("DEBUG: POU object created: %s" % new_pou_name)
576
-
577
- # --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
578
- try:
579
- print("DEBUG: Saving Project...")
580
- primary_project.save() # Save the overall project file
581
- print("DEBUG: Project saved successfully after POU creation.")
582
- except Exception as save_err:
583
- print("ERROR: Failed to save Project after POU creation: %s" % save_err)
584
- detailed_error = traceback.format_exc()
585
- error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
586
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
587
- # --- END SAVING ---
588
-
589
- print("POU Created: %s" % new_pou_name); print("Type: %s" % POU_TYPE_STR); print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR); print("Parent Path: %s" % PARENT_PATH_REL)
590
- print("SCRIPT_SUCCESS: POU created successfully."); sys.exit(0)
591
- else:
592
- error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME; print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
593
- except Exception as e:
594
- detailed_error = traceback.format_exc()
595
- error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
596
- print(error_message); print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e)); sys.exit(1)
597
- `;
598
- const SET_POU_CODE_SCRIPT_TEMPLATE = `
599
- import sys, scriptengine as script_engine, os, traceback
600
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
601
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
602
- POU_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
603
- DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
604
- IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
605
-
606
- try:
607
- print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
608
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
609
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
610
-
611
- # Find the target POU/Method/Property object
612
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
613
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
614
-
615
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
616
- print("DEBUG: Found target object: %s" % target_name)
617
-
618
- # --- Set Declaration Part ---
619
- declaration_updated = False
620
- # Check if the content is actually provided (might be None/empty if only impl is set)
621
- has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
622
- if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
623
- if hasattr(target_object, 'textual_declaration'):
624
- decl_obj = target_object.textual_declaration
625
- if decl_obj and hasattr(decl_obj, 'replace'):
626
- try:
627
- print("DEBUG: Accessing textual_declaration...")
628
- decl_obj.replace(DECLARATION_CONTENT)
629
- print("DEBUG: Set declaration text using replace().")
630
- declaration_updated = True
631
- except Exception as decl_err:
632
- print("ERROR: Failed to set declaration text: %s" % decl_err)
633
- traceback.print_exc() # Print stack trace for detailed error
634
- else:
635
- print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
636
- else:
637
- print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
638
- else:
639
- print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
640
-
641
-
642
- # --- Set Implementation Part ---
643
- implementation_updated = False
644
- has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
645
- if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
646
- if hasattr(target_object, 'textual_implementation'):
647
- impl_obj = target_object.textual_implementation
648
- if impl_obj and hasattr(impl_obj, 'replace'):
649
- try:
650
- print("DEBUG: Accessing textual_implementation...")
651
- impl_obj.replace(IMPLEMENTATION_CONTENT)
652
- print("DEBUG: Set implementation text using replace().")
653
- implementation_updated = True
654
- except Exception as impl_err:
655
- print("ERROR: Failed to set implementation text: %s" % impl_err)
656
- traceback.print_exc() # Print stack trace for detailed error
657
- else:
658
- print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
659
- else:
660
- print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
661
- else:
662
- print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
663
-
664
-
665
- # --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
666
- # Only save if something was actually updated to avoid unnecessary saves
667
- if declaration_updated or implementation_updated:
668
- try:
669
- print("DEBUG: Saving Project (after code change)...")
670
- primary_project.save() # Save the overall project file
671
- print("DEBUG: Project saved successfully after code change.")
672
- except Exception as save_err:
673
- print("ERROR: Failed to save Project after setting code: %s" % save_err)
674
- detailed_error = traceback.format_exc()
675
- error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
676
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
677
- else:
678
- print("DEBUG: No code parts were updated, skipping project save.")
679
- # --- END SAVING ---
680
-
681
- print("Code Set For: %s" % target_name)
682
- print("Path: %s" % POU_FULL_PATH)
683
- print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully."); sys.exit(0)
684
-
685
- except Exception as e:
686
- detailed_error = traceback.format_exc()
687
- error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
688
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
689
- `;
690
- const CREATE_PROPERTY_SCRIPT_TEMPLATE = `
691
- import sys, scriptengine as script_engine, os, traceback
692
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
693
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
694
- PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
695
- PROPERTY_NAME = "{PROPERTY_NAME}"
696
- PROPERTY_TYPE = "{PROPERTY_TYPE}"
697
- # Optional: Language for Getter/Setter (usually defaults to ST)
698
- # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
699
-
700
- try:
701
- print("DEBUG: create_property script: ParentPOU='%s', Name='%s', Type='%s', Project='%s'" % (PARENT_POU_FULL_PATH, PROPERTY_NAME, PROPERTY_TYPE, PROJECT_FILE_PATH))
702
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
703
- if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
704
- if not PROPERTY_NAME: raise ValueError("Property name empty.")
705
- if not PROPERTY_TYPE: raise ValueError("Property type empty.")
706
-
707
- # Find the parent POU object
708
- parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
709
- if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
710
-
711
- parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
712
- print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
713
-
714
- # Check if parent object supports creating properties (should implement ScriptIecLanguageMemberContainer)
715
- if not hasattr(parent_pou_object, 'create_property'):
716
- raise TypeError("Parent object '%s' of type %s does not support create_property." % (parent_pou_name, type(parent_pou_object).__name__))
717
-
718
- # Default language to None (usually ST)
719
- lang_guid = None
720
- print("DEBUG: Calling create_property: Name='%s', Type='%s', Lang=%s" % (PROPERTY_NAME, PROPERTY_TYPE, lang_guid))
721
-
722
- # Call the create_property method ON THE PARENT POU
723
- new_property_object = parent_pou_object.create_property(
724
- name=PROPERTY_NAME,
725
- return_type=PROPERTY_TYPE,
726
- language=lang_guid # Pass None to use default
727
- )
728
-
729
- if new_property_object:
730
- new_prop_name = getattr(new_property_object, 'get_name', lambda: PROPERTY_NAME)()
731
- print("DEBUG: Property object created: %s" % new_prop_name)
732
-
733
- # --- SAVE THE PROJECT TO PERSIST THE NEW PROPERTY OBJECT ---
734
- try:
735
- print("DEBUG: Saving Project (after property creation)...")
736
- primary_project.save()
737
- print("DEBUG: Project saved successfully after property creation.")
738
- except Exception as save_err:
739
- print("ERROR: Failed to save Project after creating property: %s" % save_err)
740
- detailed_error = traceback.format_exc()
741
- error_message = "Error saving Project after creating property '%s': %s\\n%s" % (PROPERTY_NAME, save_err, detailed_error)
742
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
743
- # --- END SAVING ---
744
-
745
- print("Property Created: %s" % new_prop_name)
746
- print("Parent POU: %s" % PARENT_POU_FULL_PATH)
747
- print("Type: %s" % PROPERTY_TYPE)
748
- print("SCRIPT_SUCCESS: Property created successfully."); sys.exit(0)
749
- else:
750
- error_message = "Failed to create property '%s' under '%s'. create_property returned None." % (PROPERTY_NAME, parent_pou_name)
751
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
752
-
753
- except Exception as e:
754
- detailed_error = traceback.format_exc()
755
- error_message = "Error creating property '%s' under POU '%s' in project '%s': %s\\n%s" % (PROPERTY_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
756
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
757
- `;
758
- const CREATE_METHOD_SCRIPT_TEMPLATE = `
759
- import sys, scriptengine as script_engine, os, traceback
760
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
761
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
762
- PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
763
- METHOD_NAME = "{METHOD_NAME}"
764
- RETURN_TYPE = "{RETURN_TYPE}" # Can be empty string for no return type
765
- # Optional: Language
766
- # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
767
-
768
- try:
769
- print("DEBUG: create_method script: ParentPOU='%s', Name='%s', ReturnType='%s', Project='%s'" % (PARENT_POU_FULL_PATH, METHOD_NAME, RETURN_TYPE, PROJECT_FILE_PATH))
770
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
771
- if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
772
- if not METHOD_NAME: raise ValueError("Method name empty.")
773
- # RETURN_TYPE can be empty
774
-
775
- # Find the parent POU object
776
- parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
777
- if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
778
-
779
- parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
780
- print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
781
-
782
- # Check if parent object supports creating methods (should implement ScriptIecLanguageMemberContainer)
783
- if not hasattr(parent_pou_object, 'create_method'):
784
- raise TypeError("Parent object '%s' of type %s does not support create_method." % (parent_pou_name, type(parent_pou_object).__name__))
785
-
786
- # Default language to None (usually ST)
787
- lang_guid = None
788
- # Use None if RETURN_TYPE is empty string, otherwise use the string
789
- actual_return_type = RETURN_TYPE if RETURN_TYPE else None
790
- print("DEBUG: Calling create_method: Name='%s', ReturnType=%s, Lang=%s" % (METHOD_NAME, actual_return_type, lang_guid))
791
-
792
- # Call the create_method method ON THE PARENT POU
793
- new_method_object = parent_pou_object.create_method(
794
- name=METHOD_NAME,
795
- return_type=actual_return_type,
796
- language=lang_guid # Pass None to use default
797
- )
798
-
799
- if new_method_object:
800
- new_meth_name = getattr(new_method_object, 'get_name', lambda: METHOD_NAME)()
801
- print("DEBUG: Method object created: %s" % new_meth_name)
802
-
803
- # --- SAVE THE PROJECT TO PERSIST THE NEW METHOD OBJECT ---
804
- try:
805
- print("DEBUG: Saving Project (after method creation)...")
806
- primary_project.save()
807
- print("DEBUG: Project saved successfully after method creation.")
808
- except Exception as save_err:
809
- print("ERROR: Failed to save Project after creating method: %s" % save_err)
810
- detailed_error = traceback.format_exc()
811
- error_message = "Error saving Project after creating method '%s': %s\\n%s" % (METHOD_NAME, save_err, detailed_error)
812
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
813
- # --- END SAVING ---
814
-
815
- print("Method Created: %s" % new_meth_name)
816
- print("Parent POU: %s" % PARENT_POU_FULL_PATH)
817
- print("Return Type: %s" % (RETURN_TYPE if RETURN_TYPE else "(None)"))
818
- print("SCRIPT_SUCCESS: Method created successfully."); sys.exit(0)
819
- else:
820
- error_message = "Failed to create method '%s' under '%s'. create_method returned None." % (METHOD_NAME, parent_pou_name)
821
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
822
-
823
- except Exception as e:
824
- detailed_error = traceback.format_exc()
825
- error_message = "Error creating method '%s' under POU '%s' in project '%s': %s\\n%s" % (METHOD_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
826
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
827
- `;
828
- const COMPILE_PROJECT_SCRIPT_TEMPLATE = `
829
- import sys, scriptengine as script_engine, os, traceback
830
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
831
- try:
832
- print("DEBUG: compile_project script: Project='%s'" % PROJECT_FILE_PATH)
833
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
834
- project_name = os.path.basename(PROJECT_FILE_PATH)
835
- target_app = None
836
- app_name = "N/A"
837
-
838
- # Try getting active application first
839
- try:
840
- target_app = primary_project.active_application
841
- if target_app:
842
- app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (Active)")()
843
- print("DEBUG: Found active application: %s" % app_name)
844
- except Exception as active_err:
845
- print("WARN: Could not get active application: %s. Searching..." % active_err)
846
-
847
- # If no active app, search for the first one
848
- if not target_app:
849
- print("DEBUG: Searching for first compilable application...")
850
- apps = []
851
- try:
852
- # Search recursively through all project objects
853
- all_children = primary_project.get_children(True)
854
- for child in all_children:
855
- # Check using the marker property and if build method exists
856
- if hasattr(child, 'is_application') and child.is_application and hasattr(child, 'build'):
857
- app_name_found = getattr(child, 'get_name', lambda: "Unnamed App")()
858
- print("DEBUG: Found potential application object: %s" % app_name_found)
859
- apps.append(child)
860
- break # Take the first one found
861
- except Exception as find_err: print("WARN: Error finding application object: %s" % find_err)
862
-
863
- if not apps: raise RuntimeError("No compilable application found in project '%s'" % project_name)
864
- target_app = apps[0]
865
- app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (First Found)")()
866
- print("WARN: Compiling first found application: %s" % app_name)
867
-
868
- print("DEBUG: Calling build() on app '%s'..." % app_name)
869
- if not hasattr(target_app, 'build'):
870
- raise TypeError("Selected object '%s' is not an application or doesn't support build()." % app_name)
871
-
872
- # Execute the build
873
- target_app.build();
874
- print("DEBUG: Build command executed for application '%s'." % app_name)
875
-
876
- # Check messages is harder without direct access to message store from script.
877
- # Rely on CODESYS UI or log output for now.
878
- print("Compile Initiated For Application: %s" % app_name); print("In Project: %s" % project_name)
879
- print("SCRIPT_SUCCESS: Application compilation initiated."); sys.exit(0)
880
- except Exception as e:
881
- detailed_error = traceback.format_exc()
882
- error_message = "Error initiating compilation for project %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
883
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
884
- `;
885
- const GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE = `
886
- import sys, scriptengine as script_engine, os, traceback
887
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
888
- # FIND_OBJECT_BY_PATH_PYTHON_SNIPPET is NOT needed here, we start from project root.
889
-
890
- def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
891
- lines = []; indent_str = " " * indent
892
- if indent > max_depth:
893
- lines.append("%s- Max recursion depth reached." % indent_str)
894
- return lines
895
- try:
896
- name = "Unnamed"; obj_type = type(obj).__name__
897
- guid_str = ""
898
- folder_str = ""
899
- try:
900
- name = getattr(obj, 'get_name', lambda: "Unnamed")() or "Unnamed" # Safer get_name
901
- if hasattr(obj, 'guid'): guid_str = " {%s}" % obj.guid
902
- if hasattr(obj, 'is_folder') and obj.is_folder: folder_str = " [Folder]"
903
- except Exception as name_err:
904
- print("WARN: Error getting name/guid/folder status for an object: %s" % name_err)
905
- name = "!!! Error Getting Name !!!"
906
-
907
- lines.append("%s- %s (%s)%s%s" % (indent_str, name, obj_type, folder_str, guid_str))
908
-
909
- # Get children only if the object potentially has them
910
- children = []
911
- can_have_children = hasattr(obj, 'get_children') and (
912
- not hasattr(obj, 'is_folder') or # If it's not clear if it's a folder (e.g., project root)
913
- (hasattr(obj, 'is_folder') and obj.is_folder) or # If it is a folder
914
- # Add known container types explicitly, check marker interfaces too
915
- hasattr(obj, 'is_project') or hasattr(obj, 'is_application') or hasattr(obj, 'is_device') or hasattr(obj,'is_pou')
916
- )
917
-
918
- if can_have_children:
919
- try:
920
- children = obj.get_children(False)
921
- # print("DEBUG: %s has %d children" % (name, len(children))) # Verbose
922
- except Exception as get_child_err:
923
- lines.append("%s ERROR getting children: %s" % (indent_str, get_child_err))
924
- # traceback.print_exc() # Optional
925
-
926
- for child in children:
927
- lines.extend(get_object_structure(child, indent + 1, max_depth)) # Recurse
928
-
929
- except Exception as e:
930
- lines.append("%s- Error processing node: %s" % (indent_str, e))
931
- traceback.print_exc() # Print detailed error for this node
932
- return lines
933
- try:
934
- print("DEBUG: Getting structure for: %s" % PROJECT_FILE_PATH)
935
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
936
- project_name = os.path.basename(PROJECT_FILE_PATH)
937
- print("DEBUG: Getting structure for project: %s" % project_name)
938
- # Use the project object obtained from ensure_project_open
939
- structure_list = get_object_structure(primary_project, max_depth=15) # Set a reasonable depth
940
- structure_output = "\\n".join(structure_list)
941
- # Ensure markers are printed distinctly
942
- print("\\n--- PROJECT STRUCTURE START ---")
943
- print(structure_output)
944
- print("--- PROJECT STRUCTURE END ---\\n")
945
- print("SCRIPT_SUCCESS: Project structure retrieved."); sys.exit(0)
946
- except Exception as e:
947
- detailed_error = traceback.format_exc()
948
- error_message = "Error getting structure for %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
949
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
950
- `;
951
- const GET_POU_CODE_SCRIPT_TEMPLATE = `
952
- import sys, scriptengine as script_engine, os, traceback
953
- ${ENSURE_PROJECT_OPEN_PYTHON_SNIPPET}
954
- ${FIND_OBJECT_BY_PATH_PYTHON_SNIPPET}
955
- POU_FULL_PATH = "{POU_FULL_PATH}"; CODE_START_MARKER = "### POU CODE START ###"; CODE_END_MARKER = "### POU CODE END ###"
956
- DECL_START_MARKER = "### POU DECLARATION START ###"; DECL_END_MARKER = "### POU DECLARATION END ###"
957
- IMPL_START_MARKER = "### POU IMPLEMENTATION START ###"; IMPL_END_MARKER = "### POU IMPLEMENTATION END ###"
958
-
959
- try:
960
- print("DEBUG: Getting code: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
961
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
962
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
963
-
964
- # Find the target POU/Method/Property object
965
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
966
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
967
-
968
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
969
- print("DEBUG: Found target object: %s" % target_name)
970
-
971
- declaration_code = ""; implementation_code = ""
972
-
973
- # --- Get Declaration Part ---
974
- if hasattr(target_object, 'textual_declaration'):
975
- decl_obj = target_object.textual_declaration
976
- if decl_obj and hasattr(decl_obj, 'text'):
977
- try:
978
- declaration_code = decl_obj.text
979
- print("DEBUG: Got declaration text.")
980
- except Exception as decl_read_err:
981
- print("ERROR: Failed to read declaration text: %s" % decl_read_err)
982
- declaration_code = "/* ERROR reading declaration: %s */" % decl_read_err
983
- else:
984
- print("WARN: textual_declaration exists but is None or has no 'text' attribute.")
985
- else:
986
- print("WARN: No textual_declaration attribute.")
987
-
988
- # --- Get Implementation Part ---
989
- if hasattr(target_object, 'textual_implementation'):
990
- impl_obj = target_object.textual_implementation
991
- if impl_obj and hasattr(impl_obj, 'text'):
992
- try:
993
- implementation_code = impl_obj.text
994
- print("DEBUG: Got implementation text.")
995
- except Exception as impl_read_err:
996
- print("ERROR: Failed to read implementation text: %s" % impl_read_err)
997
- implementation_code = "/* ERROR reading implementation: %s */" % impl_read_err
998
- else:
999
- print("WARN: textual_implementation exists but is None or has no 'text' attribute.")
1000
- else:
1001
- print("WARN: No textual_implementation attribute.")
1002
-
1003
-
1004
- print("Code retrieved for: %s" % target_name)
1005
- # Print declaration between markers, ensuring markers are on separate lines
1006
- print("\\n" + DECL_START_MARKER)
1007
- print(declaration_code)
1008
- print(DECL_END_MARKER + "\\n")
1009
- # Print implementation between markers
1010
- print(IMPL_START_MARKER)
1011
- print(implementation_code)
1012
- print(IMPL_END_MARKER + "\\n")
1013
-
1014
- # --- LEGACY MARKERS for backward compatibility if needed ---
1015
- # Combine both for old marker format, adding a separator line
1016
- # legacy_combined_code = declaration_code + "\\n\\n// Implementation\\n" + implementation_code
1017
- # print(CODE_START_MARKER); print(legacy_combined_code); print(CODE_END_MARKER)
1018
- # --- END LEGACY ---
1019
-
1020
- print("SCRIPT_SUCCESS: Code retrieved."); sys.exit(0)
1021
- except Exception as e:
1022
- detailed_error = traceback.format_exc()
1023
- error_message = "Error getting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
1024
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
1025
- `;
1026
- // --- End Python Script Templates ---
1027
- // --- Zod Schemas (moved for clarity before usage) ---
141
+ // --- Zod Schemas ---
1028
142
  const PouTypeEnum = zod_1.z.enum(["Program", "FunctionBlock", "Function"]);
1029
143
  const ImplementationLanguageEnum = zod_1.z.enum(["ST", "LD", "FBD", "SFC", "IL", "CFC", "StructuredText", "LadderDiagram", "FunctionBlockDiagram", "SequentialFunctionChart", "InstructionList", "ContinuousFunctionChart"]);
1030
144
  // --- End Zod Schemas ---
@@ -1035,46 +149,54 @@ except Exception as e:
1035
149
  var _a, _b, _c, _d, _e;
1036
150
  console.error(`SERVER.TS Resource request: ${uri.href}`);
1037
151
  try {
1038
- const result = yield (0, codesys_interop_1.executeCodesysScript)(CHECK_STATUS_SCRIPT, codesysExePath, codesysProfileName);
152
+ const scriptContent = yield (0, scriptLoader_1.loadScriptTemplate)('check_status');
153
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1039
154
  const outputLines = result.output.split(/[\r\n]+/).filter(line => line.trim());
1040
155
  const statusData = {};
1041
- outputLines.forEach(line => { const match = line.match(/^([^:]+):\s*(.*)$/); if (match) {
1042
- statusData[match[1].trim()] = match[2].trim();
1043
- } });
156
+ outputLines.forEach(line => {
157
+ const match = line.match(/^([^:]+):\s*(.*)$/);
158
+ if (match) {
159
+ statusData[match[1].trim()] = match[2].trim();
160
+ }
161
+ });
1044
162
  const statusText = `CODESYS Status:\n - Scripting OK: ${(_a = statusData['Scripting OK']) !== null && _a !== void 0 ? _a : 'Unknown'}\n - Project Open: ${(_b = statusData['Project Open']) !== null && _b !== void 0 ? _b : 'Unknown'}\n - Project Name: ${(_c = statusData['Project Name']) !== null && _c !== void 0 ? _c : 'Unknown'}\n - Project Path: ${(_d = statusData['Project Path']) !== null && _d !== void 0 ? _d : 'N/A'}`;
1045
163
  const isError = !result.success || ((_e = statusData['Scripting OK']) === null || _e === void 0 ? void 0 : _e.toLowerCase()) !== 'true';
1046
- // **** RETURN MCP STRUCTURE ****
1047
164
  return { contents: [{ uri: uri.href, text: statusText, contentType: "text/plain" }], isError: isError };
1048
165
  }
1049
166
  catch (error) {
1050
167
  console.error(`Error resource ${uri.href}:`, error);
1051
- // **** RETURN MCP STRUCTURE ****
1052
168
  return { contents: [{ uri: uri.href, text: `Failed status script: ${error.message}`, contentType: "text/plain" }], isError: true };
1053
169
  }
1054
170
  }));
1055
- // *** DEFINE TEMPLATES ***
171
+ // Templates
1056
172
  const projectStructureTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/structure", { list: undefined });
1057
173
  const pouCodeTemplate = new mcp_js_1.ResourceTemplate("codesys://project/{+project_path}/pou/{+pou_path}/code", { list: undefined });
1058
- // *** END DEFINE TEMPLATES ***
1059
174
  server.resource("project-structure", projectStructureTemplate, (uri, params) => __awaiter(this, void 0, void 0, function* () {
1060
- // *** DEFINE VARIABLES (like projectPath) ***
1061
175
  const projectPathParam = params.project_path;
1062
176
  if (typeof projectPathParam !== 'string') {
1063
- return { contents: [{ uri: uri.href, text: `Error: Invalid project path type (${typeof projectPathParam}).`, contentType: "text/plain" }], isError: true };
177
+ return {
178
+ contents: [{ uri: uri.href, text: `Error: Invalid project path type (${typeof projectPathParam}).`, contentType: "text/plain" }],
179
+ isError: true
180
+ };
1064
181
  }
1065
- const projectPath = projectPathParam; // Define projectPath
182
+ const projectPath = projectPathParam;
1066
183
  if (!projectPath) {
1067
- return { contents: [{ uri: uri.href, text: "Error: Project path missing.", contentType: "text/plain" }], isError: true };
184
+ return {
185
+ contents: [{ uri: uri.href, text: "Error: Project path missing.", contentType: "text/plain" }],
186
+ isError: true
187
+ };
1068
188
  }
1069
- // *** END DEFINE VARIABLES ***
1070
189
  console.error(`Resource request: project structure for ${projectPath}`);
1071
190
  try {
1072
191
  const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1073
- const escapedPathForPython = absoluteProjPath.replace(/\\/g, '\\\\');
1074
- // *** DEFINE scriptContent ***
1075
- const scriptContent = GET_PROJECT_STRUCTURE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPathForPython);
1076
- // *** END DEFINE scriptContent ***
1077
- const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
192
+ // Load script template and prepare parameters
193
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('get_project_structure');
194
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
195
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + scriptTemplate;
196
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
197
+ 'PROJECT_FILE_PATH': absoluteProjPath.replace(/\\/g, '\\\\')
198
+ });
199
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1078
200
  let structureText = `Error retrieving structure for ${absoluteProjPath}.\n\n${result.output}`;
1079
201
  let isError = !result.success;
1080
202
  if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
@@ -1094,42 +216,47 @@ except Exception as e:
1094
216
  else {
1095
217
  isError = true;
1096
218
  }
1097
- // **** RETURN MCP STRUCTURE ****
1098
219
  return { contents: [{ uri: uri.href, text: structureText, contentType: "text/plain" }], isError: isError };
1099
220
  }
1100
221
  catch (error) {
1101
222
  console.error(`Error getting structure ${uri.href}:`, error);
1102
- // **** RETURN MCP STRUCTURE ****
1103
223
  return { contents: [{ uri: uri.href, text: `Failed structure script for '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1104
224
  }
1105
225
  }));
1106
226
  server.resource("pou-code", pouCodeTemplate, (uri, params) => __awaiter(this, void 0, void 0, function* () {
1107
- // *** DEFINE VARIABLES (like projectPath, pouPath) ***
1108
227
  const projectPathParam = params.project_path;
1109
228
  const pouPathParam = params.pou_path;
1110
229
  if (typeof projectPathParam !== 'string' || typeof pouPathParam !== 'string') {
1111
- return { contents: [{ uri: uri.href, text: "Error: Invalid project or POU path type.", contentType: "text/plain" }], isError: true };
230
+ return {
231
+ contents: [{ uri: uri.href, text: "Error: Invalid project or POU path type.", contentType: "text/plain" }],
232
+ isError: true
233
+ };
1112
234
  }
1113
- const projectPath = projectPathParam; // Define projectPath
1114
- const pouPath = pouPathParam; // Define pouPath
235
+ const projectPath = projectPathParam;
236
+ const pouPath = pouPathParam;
1115
237
  if (!projectPath || !pouPath) {
1116
- return { contents: [{ uri: uri.href, text: "Error: Project or POU path missing.", contentType: "text/plain" }], isError: true };
238
+ return {
239
+ contents: [{ uri: uri.href, text: "Error: Project or POU path missing.", contentType: "text/plain" }],
240
+ isError: true
241
+ };
1117
242
  }
1118
- // *** END DEFINE VARIABLES ***
1119
243
  console.error(`Resource request: POU code: Project='${projectPath}', POU='${pouPath}'`);
1120
244
  try {
1121
245
  const absoluteProjPath = path.normalize(path.isAbsolute(projectPath) ? projectPath : path.join(WORKSPACE_DIR, projectPath));
1122
246
  const sanitizedPouPath = String(pouPath).replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1123
- const escapedProjPath = absoluteProjPath.replace(/\\/g, '\\\\');
1124
- // *** DEFINE scriptContent ***
1125
- let scriptContent = GET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedProjPath);
1126
- scriptContent = scriptContent.replace("{POU_FULL_PATH}", sanitizedPouPath);
1127
- // *** END DEFINE scriptContent ***
1128
- const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
247
+ // Load script template and prepare parameters
248
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('get_pou_code');
249
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
250
+ const findObjectByPathTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('find_object_by_path');
251
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + findObjectByPathTemplate + '\n\n' + scriptTemplate;
252
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
253
+ 'PROJECT_FILE_PATH': absoluteProjPath.replace(/\\/g, '\\\\'),
254
+ 'POU_FULL_PATH': sanitizedPouPath
255
+ });
256
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1129
257
  let codeText = `Error retrieving code for object '${sanitizedPouPath}' in project '${absoluteProjPath}'.\n\n${result.output}`;
1130
258
  let isError = !result.success;
1131
259
  if (result.success && result.output.includes("SCRIPT_SUCCESS")) {
1132
- // ... (marker parsing logic using new markers) ...
1133
260
  const declStartMarker = "### POU DECLARATION START ###";
1134
261
  const declEndMarker = "### POU DECLARATION END ###";
1135
262
  const implStartMarker = "### POU IMPLEMENTATION START ###";
@@ -1153,45 +280,53 @@ except Exception as e:
1153
280
  console.error(`WARN: Implementation markers not found correctly for ${sanitizedPouPath}`);
1154
281
  }
1155
282
  codeText = `// ----- Declaration -----\n${declaration}\n\n// ----- Implementation -----\n${implementation}`;
1156
- // *** END MARKER PARSING ***
1157
283
  }
1158
284
  else {
1159
285
  isError = true;
1160
286
  }
1161
- // **** RETURN MCP STRUCTURE ****
1162
287
  return { contents: [{ uri: uri.href, text: codeText, contentType: "text/plain" }], isError: isError };
1163
288
  }
1164
289
  catch (error) {
1165
290
  console.error(`Error getting POU code ${uri.href}:`, error);
1166
- // **** RETURN MCP STRUCTURE ****
1167
291
  return { contents: [{ uri: uri.href, text: `Failed POU code script for '${pouPath}' in '${projectPath}': ${error.message}`, contentType: "text/plain" }], isError: true };
1168
292
  }
1169
293
  }));
1170
294
  // --- End Resources ---
1171
295
  // --- Tools ---
1172
- server.tool("open_project", // Tool Name
1173
- "Opens an existing CODESYS project file.", // Tool Description
1174
- {
296
+ server.tool("open_project", "Opens an existing CODESYS project file.", {
1175
297
  filePath: zod_1.z.string().describe("Path to the project file (e.g., 'C:/Projects/MyPLC.project' or '/Users/user/projects/my_project.project').")
1176
298
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1177
299
  const { filePath } = args;
1178
300
  let absPath = path.normalize(path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_DIR, filePath));
1179
301
  console.error(`Tool call: open_project: ${absPath}`);
1180
302
  try {
1181
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1182
- const script = OPEN_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1183
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
303
+ // Load the script template and prepare with parameters
304
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('open_project');
305
+ // Combine with required ensure_project_open script (will handle any imports)
306
+ const ensureProjectOpenScript = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
307
+ const combinedScript = ensureProjectOpenScript + '\n\n' + scriptTemplate;
308
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
309
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\')
310
+ });
311
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1184
312
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1185
- return { content: [{ type: "text", text: success ? `Project opened: ${absPath}` : `Failed open project ${absPath}. Output:\n${result.output}` }], isError: !success };
313
+ return {
314
+ content: [{
315
+ type: "text",
316
+ text: success ? `Project opened: ${absPath}` : `Failed open project ${absPath}. Output:\n${result.output}`
317
+ }],
318
+ isError: !success
319
+ };
1186
320
  }
1187
321
  catch (e) {
1188
322
  console.error(`Error open_project ${absPath}: ${e}`);
1189
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
323
+ return {
324
+ content: [{ type: "text", text: `Error: ${e.message}` }],
325
+ isError: true
326
+ };
1190
327
  }
1191
328
  }));
1192
- server.tool("create_project", // Tool Name
1193
- "Creates a new CODESYS project from the standard template.", // Tool Description
1194
- {
329
+ server.tool("create_project", "Creates a new CODESYS project from the standard template.", {
1195
330
  filePath: zod_1.z.string().describe("Path where the new project file should be created (e.g., 'C:/Projects/NewPLC.project' or '/Users/user/projects/new_project.project').")
1196
331
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1197
332
  const { filePath } = args;
@@ -1199,7 +334,7 @@ except Exception as e:
1199
334
  console.error(`Tool call: create_project (copy template): ${absPath}`);
1200
335
  let templatePath = "";
1201
336
  try {
1202
- // Template finding logic (same as before)
337
+ // Template finding logic
1203
338
  const baseDir = path.dirname(path.dirname(codesysExePath));
1204
339
  templatePath = path.normalize(path.join(baseDir, 'Templates', 'Standard.project'));
1205
340
  if (!(yield fileExists(templatePath))) {
@@ -1226,52 +361,74 @@ except Exception as e:
1226
361
  else {
1227
362
  console.error(`DEBUG: Found template relative to exe: ${templatePath}`);
1228
363
  }
1229
- // *** END TEMPLATE FINDING ***
1230
364
  }
1231
365
  catch (e) {
1232
366
  console.error(`Template Error: ${e.message}`);
1233
- return { content: [{ type: "text", text: `Template Error: ${e.message}` }], isError: true };
367
+ return {
368
+ content: [{ type: "text", text: `Template Error: ${e.message}` }],
369
+ isError: true
370
+ };
1234
371
  }
1235
372
  try {
1236
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1237
- const escTmplPath = templatePath.replace(/\\/g, '\\\\');
1238
- const script = CREATE_PROJECT_SCRIPT_TEMPLATE
1239
- .replace("{PROJECT_FILE_PATH}", escProjPath)
1240
- .replace("{TEMPLATE_PROJECT_PATH}", escTmplPath);
1241
- console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", script.substring(0, 500) + "...");
1242
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
373
+ // Load script template and interpolate parameters
374
+ const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('create_project');
375
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
376
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
377
+ 'TEMPLATE_PROJECT_PATH': templatePath.replace(/\\/g, '\\\\')
378
+ });
379
+ console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
380
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1243
381
  console.error(">>> create_project (copy-then-open): EXECUTION RESULT:", JSON.stringify(result));
1244
382
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1245
- return { content: [{ type: "text", text: success ? `Project created from template: ${absPath}` : `Failed create project ${absPath} from template. Output:\n${result.output}` }], isError: !success };
383
+ return {
384
+ content: [{
385
+ type: "text",
386
+ text: success ? `Project created from template: ${absPath}` : `Failed create project ${absPath} from template. Output:\n${result.output}`
387
+ }],
388
+ isError: !success
389
+ };
1246
390
  }
1247
391
  catch (e) {
1248
392
  console.error(`Error create_project ${absPath}: ${e}`);
1249
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
393
+ return {
394
+ content: [{ type: "text", text: `Error: ${e.message}` }],
395
+ isError: true
396
+ };
1250
397
  }
1251
398
  }));
1252
- server.tool("save_project", // Tool Name
1253
- "Saves the currently open CODESYS project.", // Tool Description
1254
- {
399
+ server.tool("save_project", "Saves the currently open CODESYS project.", {
1255
400
  projectFilePath: zod_1.z.string().describe("Path to the project file to ensure is open before saving (e.g., 'C:/Projects/MyPLC.project').")
1256
401
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1257
402
  const { projectFilePath } = args;
1258
403
  let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1259
404
  console.error(`Tool call: save_project: ${absPath}`);
1260
405
  try {
1261
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1262
- const script = SAVE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1263
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
406
+ // Load and combine script templates
407
+ const saveProjectTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('save_project');
408
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
409
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + saveProjectTemplate;
410
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
411
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\')
412
+ });
413
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1264
414
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1265
- return { content: [{ type: "text", text: success ? `Project saved: ${absPath}` : `Failed save project ${absPath}. Output:\n${result.output}` }], isError: !success };
415
+ return {
416
+ content: [{
417
+ type: "text",
418
+ text: success ? `Project saved: ${absPath}` : `Failed save project ${absPath}. Output:\n${result.output}`
419
+ }],
420
+ isError: !success
421
+ };
1266
422
  }
1267
423
  catch (e) {
1268
424
  console.error(`Error save_project ${absPath}: ${e}`);
1269
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
425
+ return {
426
+ content: [{ type: "text", text: `Error: ${e.message}` }],
427
+ isError: true
428
+ };
1270
429
  }
1271
430
  }));
1272
- server.tool("create_pou", // Tool Name
1273
- "Creates a new Program, Function Block, or Function POU within the specified CODESYS project.", // Tool Description
1274
- {
431
+ server.tool("create_pou", "Creates a new Program, Function Block, or Function POU within the specified CODESYS project.", {
1275
432
  projectFilePath: zod_1.z.string().describe("Path to the project file (e.g., 'C:/Projects/MyPLC.project')."),
1276
433
  name: zod_1.z.string().describe("Name for the new POU (must be a valid IEC identifier)."),
1277
434
  type: PouTypeEnum.describe("Type of POU (Program, FunctionBlock, Function)."),
@@ -1284,26 +441,39 @@ except Exception as e:
1284
441
  const sanName = name.trim();
1285
442
  console.error(`Tool call: create_pou: Name='${sanName}', Type='${type}', Lang='${language}', Parent='${sanParentPath}', Project='${absPath}'`);
1286
443
  try {
1287
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1288
- let script = CREATE_POU_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1289
- script = script.replace("{POU_NAME}", sanName);
1290
- script = script.replace("{POU_TYPE_STR}", type);
1291
- script = script.replace("{IMPL_LANGUAGE_STR}", language);
1292
- script = script.replace("{PARENT_PATH}", sanParentPath);
1293
- console.error(">>> create_pou: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1294
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
444
+ // Load and combine script templates
445
+ const createPouTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('create_pou');
446
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
447
+ const findObjectByPathTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('find_object_by_path');
448
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + findObjectByPathTemplate + '\n\n' + createPouTemplate;
449
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
450
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
451
+ 'POU_NAME': sanName,
452
+ 'POU_TYPE_STR': type,
453
+ 'IMPL_LANGUAGE_STR': language,
454
+ 'PARENT_PATH': sanParentPath
455
+ });
456
+ console.error(">>> create_pou: PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
457
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1295
458
  console.error(">>> create_pou: EXECUTION RESULT:", JSON.stringify(result));
1296
459
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1297
- return { content: [{ type: "text", text: success ? `POU '${sanName}' created in '${sanParentPath}' of ${absPath}. Project saved.` : `Failed create POU '${sanName}'. Output:\n${result.output}` }], isError: !success };
460
+ return {
461
+ content: [{
462
+ type: "text",
463
+ text: success ? `POU '${sanName}' created in '${sanParentPath}' of ${absPath}. Project saved.` : `Failed create POU '${sanName}'. Output:\n${result.output}`
464
+ }],
465
+ isError: !success
466
+ };
1298
467
  }
1299
468
  catch (e) {
1300
469
  console.error(`Error create_pou ${sanName} in ${absPath}: ${e}`);
1301
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
470
+ return {
471
+ content: [{ type: "text", text: `Error: ${e.message}` }],
472
+ isError: true
473
+ };
1302
474
  }
1303
475
  }));
1304
- server.tool("set_pou_code", // Tool Name
1305
- "Sets the declaration and/or implementation code for a specific POU, Method, or Property.", // Tool Description
1306
- {
476
+ server.tool("set_pou_code", "Sets the declaration and/or implementation code for a specific POU, Method, or Property.", {
1307
477
  projectFilePath: zod_1.z.string().describe("Path to the project file (e.g., 'C:/Projects/MyPLC.project')."),
1308
478
  pouPath: zod_1.z.string().describe("Full relative path to the target object (e.g., 'Application/MyPOU', 'MyFolder/MyFB/MyMethod', 'MyFolder/MyFB/MyProperty')."),
1309
479
  declarationCode: zod_1.z.string().describe("Code for the declaration part (VAR...END_VAR). If omitted, the declaration is not changed.").optional(),
@@ -1311,36 +481,51 @@ except Exception as e:
1311
481
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1312
482
  const { projectFilePath, pouPath, declarationCode, implementationCode } = args;
1313
483
  if (declarationCode === undefined && implementationCode === undefined) {
1314
- return { content: [{ type: "text", text: "Error: At least one of declarationCode or implementationCode must be provided." }], isError: true };
484
+ return {
485
+ content: [{ type: "text", text: "Error: At least one of declarationCode or implementationCode must be provided." }],
486
+ isError: true
487
+ };
1315
488
  }
1316
489
  let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1317
490
  const sanPouPath = pouPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
1318
491
  console.error(`Tool call: set_pou_code: Target='${sanPouPath}', Project='${absPath}'`);
1319
492
  try {
1320
- const escProjPath = absPath.replace(/\\/g, '\\\\');
493
+ // Load and combine script templates
494
+ const setPouCodeTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('set_pou_code');
495
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
496
+ const findObjectByPathTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('find_object_by_path');
497
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + findObjectByPathTemplate + '\n\n' + setPouCodeTemplate;
1321
498
  // Escape content for Python triple-quoted strings
1322
499
  const sanDeclCode = (declarationCode !== null && declarationCode !== void 0 ? declarationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1323
500
  const sanImplCode = (implementationCode !== null && implementationCode !== void 0 ? implementationCode : "").replace(/\\/g, '\\\\').replace(/"""/g, '\\"\\"\\"');
1324
- let script = SET_POU_CODE_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1325
- script = script.replace("{POU_FULL_PATH}", sanPouPath);
1326
- script = script.replace("{DECLARATION_CONTENT}", sanDeclCode);
1327
- script = script.replace("{IMPLEMENTATION_CONTENT}", sanImplCode);
1328
- console.error(">>> set_pou_code: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1329
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
501
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
502
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
503
+ 'POU_FULL_PATH': sanPouPath,
504
+ 'DECLARATION_CONTENT': sanDeclCode,
505
+ 'IMPLEMENTATION_CONTENT': sanImplCode
506
+ });
507
+ console.error(">>> set_pou_code: PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
508
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1330
509
  console.error(">>> set_pou_code: EXECUTION RESULT:", JSON.stringify(result));
1331
510
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1332
- return { content: [{ type: "text", text: success ? `Code set for '${sanPouPath}' in ${absPath}. Project saved.` : `Failed set code for '${sanPouPath}'. Output:\n${result.output}` }], isError: !success };
511
+ return {
512
+ content: [{
513
+ type: "text",
514
+ text: success ? `Code set for '${sanPouPath}' in ${absPath}. Project saved.` : `Failed set code for '${sanPouPath}'. Output:\n${result.output}`
515
+ }],
516
+ isError: !success
517
+ };
1333
518
  }
1334
519
  catch (e) {
1335
520
  console.error(`Error set_pou_code ${sanPouPath} in ${absPath}: ${e}`);
1336
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
521
+ return {
522
+ content: [{ type: "text", text: `Error: ${e.message}` }],
523
+ isError: true
524
+ };
1337
525
  }
1338
526
  }));
1339
- server.tool("create_property", // Tool Name
1340
- "Creates a new Property within a specific Function Block POU.", // Tool Description
1341
- {
527
+ server.tool("create_property", "Creates a new Property within a specific Function Block POU.", {
1342
528
  projectFilePath: zod_1.z.string().describe("Path to the project file (e.g., 'C:/Projects/MyPLC.project')."),
1343
- // Assuming properties can only be added to FBs in standard CODESYS scripting
1344
529
  parentPouPath: zod_1.z.string().describe("Relative path to the parent Function Block POU (e.g., 'Application/MyFB')."),
1345
530
  propertyName: zod_1.z.string().describe("Name for the new property (must be a valid IEC identifier)."),
1346
531
  propertyType: zod_1.z.string().describe("Data type of the property (e.g., 'BOOL', 'INT', 'MyDUT').")
@@ -1352,36 +537,48 @@ except Exception as e:
1352
537
  const sanPropType = propertyType.trim();
1353
538
  console.error(`Tool call: create_property: Name='${sanPropName}', Type='${sanPropType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1354
539
  if (!sanPropName || !sanPropType) {
1355
- return { content: [{ type: "text", text: `Error: Property name and type cannot be empty.` }], isError: true };
540
+ return {
541
+ content: [{ type: "text", text: `Error: Property name and type cannot be empty.` }],
542
+ isError: true
543
+ };
1356
544
  }
1357
545
  try {
1358
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1359
- let script = CREATE_PROPERTY_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1360
- script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1361
- script = script.replace("{PROPERTY_NAME}", sanPropName);
1362
- script = script.replace("{PROPERTY_TYPE}", sanPropType);
1363
- console.error(">>> create_property: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1364
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
546
+ // Load and combine script templates
547
+ const createPropertyTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('create_property');
548
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
549
+ const findObjectByPathTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('find_object_by_path');
550
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + findObjectByPathTemplate + '\n\n' + createPropertyTemplate;
551
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
552
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
553
+ 'PARENT_POU_FULL_PATH': sanParentPath,
554
+ 'PROPERTY_NAME': sanPropName,
555
+ 'PROPERTY_TYPE': sanPropType
556
+ });
557
+ console.error(">>> create_property: PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
558
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1365
559
  console.error(">>> create_property: EXECUTION RESULT:", JSON.stringify(result));
1366
560
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1367
561
  return {
1368
- content: [{ type: "text", text: success ? `Property '${sanPropName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create property '${sanPropName}'. Output:\n${result.output}` }],
562
+ content: [{
563
+ type: "text",
564
+ text: success ? `Property '${sanPropName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create property '${sanPropName}'. Output:\n${result.output}`
565
+ }],
1369
566
  isError: !success
1370
567
  };
1371
568
  }
1372
569
  catch (e) {
1373
570
  console.error(`Error create_property ${sanPropName} in ${absPath}: ${e}`);
1374
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
571
+ return {
572
+ content: [{ type: "text", text: `Error: ${e.message}` }],
573
+ isError: true
574
+ };
1375
575
  }
1376
576
  }));
1377
- server.tool("create_method", // Tool Name
1378
- "Creates a new Method within a specific Function Block POU.", // Tool Description
1379
- {
577
+ server.tool("create_method", "Creates a new Method within a specific Function Block POU.", {
1380
578
  projectFilePath: zod_1.z.string().describe("Path to the project file (e.g., 'C:/Projects/MyPLC.project')."),
1381
- // Assuming methods can typically only be added to FBs in standard CODESYS scripting
1382
579
  parentPouPath: zod_1.z.string().describe("Relative path to the parent Function Block POU (e.g., 'Application/MyFB')."),
1383
580
  methodName: zod_1.z.string().describe("Name of the new method (must be a valid IEC identifier)."),
1384
- returnType: zod_1.z.string().optional().describe("Return type (e.g., 'BOOL', 'INT'). Leave empty or omit for no return value (PROCEDURE)."),
581
+ returnType: zod_1.z.string().optional().describe("Return type (e.g., 'BOOL', 'INT'). Leave empty or omit for no return value (PROCEDURE).")
1385
582
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1386
583
  const { projectFilePath, parentPouPath, methodName, returnType } = args;
1387
584
  let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
@@ -1390,40 +587,58 @@ except Exception as e:
1390
587
  const sanReturnType = (returnType !== null && returnType !== void 0 ? returnType : "").trim();
1391
588
  console.error(`Tool call: create_method: Name='${sanMethName}', Return='${sanReturnType}', ParentPOU='${sanParentPath}', Project='${absPath}'`);
1392
589
  if (!sanMethName) {
1393
- return { content: [{ type: "text", text: `Error: Method name cannot be empty.` }], isError: true };
590
+ return {
591
+ content: [{ type: "text", text: `Error: Method name cannot be empty.` }],
592
+ isError: true
593
+ };
1394
594
  }
1395
595
  try {
1396
- const escProjPath = absPath.replace(/\\/g, '\\\\');
1397
- let script = CREATE_METHOD_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escProjPath);
1398
- script = script.replace("{PARENT_POU_FULL_PATH}", sanParentPath);
1399
- script = script.replace("{METHOD_NAME}", sanMethName);
1400
- script = script.replace("{RETURN_TYPE}", sanReturnType);
1401
- console.error(">>> create_method: PREPARED SCRIPT:", script.substring(0, 500) + "...");
1402
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
596
+ // Load and combine script templates
597
+ const createMethodTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('create_method');
598
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
599
+ const findObjectByPathTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('find_object_by_path');
600
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + findObjectByPathTemplate + '\n\n' + createMethodTemplate;
601
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
602
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
603
+ 'PARENT_POU_FULL_PATH': sanParentPath,
604
+ 'METHOD_NAME': sanMethName,
605
+ 'RETURN_TYPE': sanReturnType
606
+ });
607
+ console.error(">>> create_method: PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
608
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1403
609
  console.error(">>> create_method: EXECUTION RESULT:", JSON.stringify(result));
1404
610
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1405
611
  return {
1406
- content: [{ type: "text", text: success ? `Method '${sanMethName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create method '${sanMethName}'. Output:\n${result.output}` }],
612
+ content: [{
613
+ type: "text",
614
+ text: success ? `Method '${sanMethName}' created under '${sanParentPath}' in ${absPath}. Project saved.` : `Failed to create method '${sanMethName}'. Output:\n${result.output}`
615
+ }],
1407
616
  isError: !success
1408
617
  };
1409
618
  }
1410
619
  catch (e) {
1411
620
  console.error(`Error create_method ${sanMethName} in ${absPath}: ${e}`);
1412
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
621
+ return {
622
+ content: [{ type: "text", text: `Error: ${e.message}` }],
623
+ isError: true
624
+ };
1413
625
  }
1414
626
  }));
1415
- server.tool("compile_project", // Tool Name
1416
- "Compiles (Builds) the primary application within a CODESYS project.", // Tool Description
1417
- {
627
+ server.tool("compile_project", "Compiles (Builds) the primary application within a CODESYS project.", {
1418
628
  projectFilePath: zod_1.z.string().describe("Path to the project file containing the application to compile (e.g., 'C:/Projects/MyPLC.project').")
1419
629
  }, (args) => __awaiter(this, void 0, void 0, function* () {
1420
630
  const { projectFilePath } = args;
1421
631
  let absPath = path.normalize(path.isAbsolute(projectFilePath) ? projectFilePath : path.join(WORKSPACE_DIR, projectFilePath));
1422
632
  console.error(`Tool call: compile_project: ${absPath}`);
1423
633
  try {
1424
- const escapedPath = absPath.replace(/\\/g, '\\\\');
1425
- const script = COMPILE_PROJECT_SCRIPT_TEMPLATE.replace("{PROJECT_FILE_PATH}", escapedPath);
1426
- const result = yield (0, codesys_interop_1.executeCodesysScript)(script, codesysExePath, codesysProfileName);
634
+ // Load and combine script templates
635
+ const compileProjectTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('compile_project');
636
+ const ensureProjectOpenTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('ensure_project_open');
637
+ const combinedScript = ensureProjectOpenTemplate + '\n\n' + compileProjectTemplate;
638
+ const scriptContent = (0, scriptLoader_1.interpolateScript)(combinedScript, {
639
+ 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\')
640
+ });
641
+ const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
1427
642
  const success = result.success && result.output.includes("SCRIPT_SUCCESS");
1428
643
  // Check for actual compile errors in the output log
1429
644
  const hasCompileErrors = result.output.includes("Compile complete --") && !/ 0 error\(s\),/.test(result.output);
@@ -1435,11 +650,125 @@ except Exception as e:
1435
650
  // Report as error if compile fails, even if script technically succeeded
1436
651
  isError = true;
1437
652
  }
1438
- return { content: [{ type: "text", text: message }], isError: isError };
653
+ return {
654
+ content: [{ type: "text", text: message }],
655
+ isError: isError
656
+ };
1439
657
  }
1440
658
  catch (e) {
1441
659
  console.error(`Error compile_project ${absPath}: ${e}`);
1442
- return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
660
+ return {
661
+ content: [{ type: "text", text: `Error: ${e.message}` }],
662
+ isError: true
663
+ };
664
+ }
665
+ }));
666
+ // --- Session Management Tools ---
667
+ server.tool("start_session", "Starts a persistent CODESYS session for improved performance with multiple operations", {}, () => __awaiter(this, void 0, void 0, function* () {
668
+ console.error("Tool call: start_session");
669
+ try {
670
+ const currentState = sessionManager.getState();
671
+ if (currentState === sessionManager_1.SessionState.RUNNING || currentState === sessionManager_1.SessionState.BUSY) {
672
+ return {
673
+ content: [{
674
+ type: "text",
675
+ text: "CODESYS session is already running"
676
+ }],
677
+ isError: false
678
+ };
679
+ }
680
+ if (currentState === sessionManager_1.SessionState.STARTING) {
681
+ return {
682
+ content: [{
683
+ type: "text",
684
+ text: "CODESYS session is already starting"
685
+ }],
686
+ isError: false
687
+ };
688
+ }
689
+ yield sessionManager.startSession();
690
+ return {
691
+ content: [{
692
+ type: "text",
693
+ text: "CODESYS session started successfully"
694
+ }],
695
+ isError: false
696
+ };
697
+ }
698
+ catch (error) {
699
+ console.error(`Error starting session: ${error.message}`);
700
+ return {
701
+ content: [{
702
+ type: "text",
703
+ text: `Failed to start CODESYS session: ${error.message}`
704
+ }],
705
+ isError: true
706
+ };
707
+ }
708
+ }));
709
+ server.tool("stop_session", "Stops the persistent CODESYS session", {}, () => __awaiter(this, void 0, void 0, function* () {
710
+ console.error("Tool call: stop_session");
711
+ try {
712
+ const currentState = sessionManager.getState();
713
+ if (currentState === sessionManager_1.SessionState.STOPPED) {
714
+ return {
715
+ content: [{
716
+ type: "text",
717
+ text: "CODESYS session is already stopped"
718
+ }],
719
+ isError: false
720
+ };
721
+ }
722
+ if (currentState === sessionManager_1.SessionState.STOPPING) {
723
+ return {
724
+ content: [{
725
+ type: "text",
726
+ text: "CODESYS session is already stopping"
727
+ }],
728
+ isError: false
729
+ };
730
+ }
731
+ yield sessionManager.stopSession();
732
+ return {
733
+ content: [{
734
+ type: "text",
735
+ text: "CODESYS session stopped successfully"
736
+ }],
737
+ isError: false
738
+ };
739
+ }
740
+ catch (error) {
741
+ console.error(`Error stopping session: ${error.message}`);
742
+ return {
743
+ content: [{
744
+ type: "text",
745
+ text: `Failed to stop CODESYS session: ${error.message}`
746
+ }],
747
+ isError: true
748
+ };
749
+ }
750
+ }));
751
+ server.tool("session_status", "Returns the status of the CODESYS session", {}, () => __awaiter(this, void 0, void 0, function* () {
752
+ console.error("Tool call: session_status");
753
+ try {
754
+ const state = sessionManager.getState();
755
+ return {
756
+ content: [{
757
+ type: "text",
758
+ text: `CODESYS session status: ${state}`
759
+ }],
760
+ isError: false
761
+ };
762
+ }
763
+ catch (error) {
764
+ console.error(`Error getting session status: ${error.message}`);
765
+ return {
766
+ content: [{
767
+ type: "text",
768
+ text: `Failed to get CODESYS session status: ${error.message}`
769
+ }],
770
+ isError: true
771
+ };
1443
772
  }
1444
773
  }));
1445
774
  // --- End Tools ---
@@ -1451,16 +780,13 @@ except Exception as e:
1451
780
  const transport = new stdio_js_1.StdioServerTransport();
1452
781
  console.error("SERVER.TS: Connecting MCP server via stdio...");
1453
782
  // No need to await connect here if startMcpServer is called by bin.ts which awaits it
1454
- // await server.connect(transport);
1455
783
  server.connect(transport); // Connect but don't await here, let bin.ts handle waiting
1456
784
  console.error("SERVER.TS: MCP Server connection initiated via stdio.");
1457
- // console.error("SERVER.TS: server.connect() promise resolved successfully."); // This log might be premature now
1458
785
  }
1459
786
  catch (error) {
1460
787
  console.error("FATAL: Failed to initiate MCP server connection:", error);
1461
788
  // Re-throw error so bin.ts can catch it
1462
789
  throw error;
1463
- // process.exit(1);
1464
790
  }
1465
791
  // --- End Server Connection ---
1466
792
  });