@codesys/mcp-toolkit 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.6] - 2025-05-02
9
+ ### Fixed
10
+ - Fixed session management "hanging" issue with start_session
11
+ - Added stdout flushing in Python scripts to ensure output is visible
12
+ - Improved session script logging for better debugging
13
+ - Added more detailed state information in session scripts
14
+
15
+ ## [1.4.5] - 2025-05-02
16
+ ### Fixed
17
+ - Completely redesigned path handling for Windows paths
18
+ - Removed raw string (r'...') approach for more direct path handling
19
+ - Implemented path normalization using forward slashes for cross-platform compatibility
20
+ - Fixed create_project script to properly handle Windows paths in IronPython environment
21
+
8
22
  ## [1.4.4] - 2025-05-02
9
23
  ### Fixed
10
24
  - Fixed "unexpected token 'C'" error in create_project script
@@ -121,19 +121,14 @@ function interpolateScript(template, params) {
121
121
  let result = template;
122
122
  // Replace each parameter in the template
123
123
  for (const [key, value] of Object.entries(params)) {
124
- // Handle Windows paths specially
125
- let escapedValue;
126
- // Check if this looks like a Windows path
127
- if (typeof value === 'string' && (value.includes(':\\') || value.includes('\\'))) {
128
- // For Windows paths, use a different approach with raw strings
129
- console.error(`SCRIPT_LOADER: Detected Windows path: ${value}`);
130
- // Use Python raw string format to avoid backslash interpretation issues
131
- escapedValue = `r'${value.replace(/'/g, "\\'")}'`;
132
- console.error(`SCRIPT_LOADER: Converted to Python raw string: ${escapedValue}`);
133
- }
134
- else {
135
- // For non-path values, just escape backslashes normally
136
- escapedValue = String(value).replace(/\\/g, '\\\\');
124
+ // Get the escaped value, handling special characters
125
+ // Note: We've changed our approach to use forward slashes everywhere
126
+ // and handle the path normalization in each script instead of using r-strings
127
+ let escapedValue = String(value);
128
+ // If value has single quotes, escape them
129
+ if (escapedValue.includes("'")) {
130
+ escapedValue = escapedValue.replace(/'/g, "\\'");
131
+ console.error(`SCRIPT_LOADER: Escaped single quotes in value: ${escapedValue}`);
137
132
  }
138
133
  // Replace {PARAM_NAME} with the escaped value
139
134
  const paramPattern = new RegExp(`\\{${key}\\}`, 'g');
@@ -1,34 +1,13 @@
1
1
  import sys, scriptengine as script_engine, os, shutil, time, traceback
2
- # Placeholders for paths - note that these might be prefixed with r' for raw strings
3
- # from the TypeScript/Node.js side due to Windows path handling
4
- TEMPLATE_PROJECT_PATH_RAW = '{TEMPLATE_PROJECT_PATH}' # Path to Standard.project
5
- PROJECT_FILE_PATH_RAW = '{PROJECT_FILE_PATH}' # Path for the new project (Target Path)
6
2
 
7
- # Process paths that might be Python raw strings (r'path')
8
- def extract_path(path_str):
9
- # Check if path starts with r' prefix and ends with ' (raw string notation)
10
- if isinstance(path_str, str):
11
- if path_str.startswith("r'") and path_str.endswith("'"):
12
- try:
13
- # Extract the content between r' and '
14
- return path_str[2:-1]
15
- except Exception as e:
16
- print("DEBUG: Error extracting raw string content: %s" % e)
17
- return path_str
18
- return path_str
3
+ # Direct path assignment - no more raw string handling
4
+ # Paths will be correctly escaped in the template substitution
5
+ TEMPLATE_PROJECT_PATH = '{TEMPLATE_PROJECT_PATH}'
6
+ PROJECT_FILE_PATH = '{PROJECT_FILE_PATH}'
19
7
 
20
- # Process template path and project file path
21
- try:
22
- TEMPLATE_PROJECT_PATH = extract_path(TEMPLATE_PROJECT_PATH_RAW)
23
- PROJECT_FILE_PATH = extract_path(PROJECT_FILE_PATH_RAW)
24
- print("DEBUG: Processed paths:")
25
- print("DEBUG: TEMPLATE_PROJECT_PATH_RAW: %s" % TEMPLATE_PROJECT_PATH_RAW)
26
- print("DEBUG: TEMPLATE_PROJECT_PATH (processed): %s" % TEMPLATE_PROJECT_PATH)
27
- print("DEBUG: PROJECT_FILE_PATH_RAW: %s" % PROJECT_FILE_PATH_RAW)
28
- print("DEBUG: PROJECT_FILE_PATH (processed): %s" % PROJECT_FILE_PATH)
29
- except Exception as path_proc_err:
30
- print("CRITICAL: Error processing path variables: %s" % path_proc_err)
31
- traceback.print_exc()
8
+ # Print raw paths for debugging
9
+ print("DEBUG: Template path (raw): %s" % repr(TEMPLATE_PROJECT_PATH))
10
+ print("DEBUG: Project path (raw): %s" % repr(PROJECT_FILE_PATH))
32
11
 
33
12
  print("DEBUG: Processed TEMPLATE_PROJECT_PATH: %s" % TEMPLATE_PROJECT_PATH)
34
13
  print("DEBUG: Processed PROJECT_FILE_PATH: %s" % PROJECT_FILE_PATH)
@@ -387,6 +387,7 @@ def check_command():
387
387
 
388
388
  # Execute the command
389
389
  print("Executing command: {0}".format(command.get("type", "unknown")))
390
+ sys.stdout.flush() # Ensure output is flushed before potentially long-running operation
390
391
  result, stop_requested = execute_command(command)
391
392
  print("Command execution complete, stop_requested: {0}".format(stop_requested))
392
393
 
@@ -423,6 +424,7 @@ def check_command():
423
424
  except Exception as cmd_err:
424
425
  print("Error processing command: {0}".format(cmd_err))
425
426
  traceback.print_exc()
427
+ sys.stdout.flush() # Ensure error output is flushed
426
428
 
427
429
  # Try to reset the command file
428
430
  try:
@@ -448,6 +450,7 @@ def check_command():
448
450
  except Exception as e:
449
451
  print("Error in check_command: {0}".format(e))
450
452
  traceback.print_exc()
453
+ sys.stdout.flush() # Ensure error output is flushed
451
454
  return False
452
455
 
453
456
  def update_last_activity():
@@ -497,6 +500,7 @@ def command_loop():
497
500
  print("Session state file: {0}".format(SESSION_STATE_FILE))
498
501
  print("Poll interval: {0} seconds".format(POLL_INTERVAL))
499
502
  print("Max inactivity time: {0} seconds".format(MAX_INACTIVITY_TIME))
503
+ sys.stdout.flush() # Ensure initialization output is visible immediately
500
504
 
501
505
  # Set up initial session state files
502
506
  try:
@@ -519,9 +523,14 @@ def command_loop():
519
523
  "timestamp": time.time()
520
524
  }, f, indent=2)
521
525
  print("Created initial result file")
526
+ sys.stdout.flush()
522
527
  except Exception as setup_err:
523
528
  print("Error setting up initial files: {0}".format(setup_err))
524
529
  traceback.print_exc()
530
+ sys.stdout.flush()
531
+
532
+ print("Entering main command loop...")
533
+ sys.stdout.flush()
525
534
 
526
535
  while not stop_requested:
527
536
  try:
@@ -536,24 +545,27 @@ def command_loop():
536
545
  if current_time - last_heartbeat > 60:
537
546
  print("Command loop heartbeat - still alive (iteration {0})".format(loop_count))
538
547
  last_heartbeat = current_time
548
+ sys.stdout.flush()
539
549
 
540
550
  # Check for new commands
541
- print("Checking for commands...")
542
551
  stop_requested = check_command()
543
552
  if stop_requested:
544
553
  print("Stop was requested by a command")
554
+ sys.stdout.flush()
545
555
 
546
556
  # Check for inactivity timeout
547
557
  if check_inactivity_timeout():
548
558
  print("Session timeout due to inactivity")
549
559
  update_session_state("timeout")
550
560
  stop_requested = True
561
+ sys.stdout.flush()
551
562
 
552
563
  # Periodically update session state (every 30 seconds)
553
564
  if (current_time - last_state_update) > 30:
554
565
  print("Periodic session state update")
555
566
  update_session_state()
556
567
  last_state_update = current_time
568
+ sys.stdout.flush()
557
569
 
558
570
  # Sleep for a bit
559
571
  time.sleep(POLL_INTERVAL)
@@ -561,34 +573,48 @@ def command_loop():
561
573
  except Exception as e:
562
574
  print("Error in command loop: {0}".format(e))
563
575
  traceback.print_exc()
576
+ sys.stdout.flush()
564
577
  # Sleep for a bit before retrying
565
578
  time.sleep(POLL_INTERVAL)
566
579
 
567
580
  print("Command loop stopped after {0} iterations".format(loop_count))
581
+ sys.stdout.flush()
568
582
 
569
583
  # Main script
570
584
  def main():
571
585
  try:
586
+ print("Starting CODESYS automation session...")
587
+ sys.stdout.flush()
588
+
572
589
  # Setup session directory and files
590
+ print("Setting up session directory...")
591
+ sys.stdout.flush()
573
592
  if not setup_session_dir():
593
+ print("Failed to set up session directory")
594
+ sys.stdout.flush()
574
595
  sys.exit(1)
575
596
 
576
597
  # Update initial session state
598
+ print("Updating initial session state...")
599
+ sys.stdout.flush()
577
600
  update_session_state()
578
601
 
579
602
  # Start command loop
580
603
  print("{0}: CODESYS session started".format(SCRIPT_SUCCESS_MARKER))
604
+ sys.stdout.flush()
581
605
  command_loop()
582
606
 
583
607
  # Update final state
584
608
  update_session_state("stopped")
585
609
 
586
610
  print("{0}: CODESYS session stopped cleanly".format(SCRIPT_SUCCESS_MARKER))
611
+ sys.stdout.flush()
587
612
  sys.exit(0)
588
613
  except Exception as e:
589
614
  print("Error in session: {0}".format(e))
590
615
  print("{0}: Session failed - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
591
616
  traceback.print_exc()
617
+ sys.stdout.flush()
592
618
  sys.exit(1)
593
619
 
594
620
  if __name__ == "__main__":
package/dist/server.js CHANGED
@@ -130,7 +130,7 @@ function startMcpServer(config) {
130
130
  console.error("SERVER.TS: Initializing McpServer...");
131
131
  const server = new mcp_js_1.McpServer({
132
132
  name: "CODESYS Control MCP Server",
133
- version: "1.8.0", // Updated version for refactored code
133
+ version: "1.8.1", // Updated version for session fixes
134
134
  capabilities: {
135
135
  resources: { listChanged: true },
136
136
  tools: { listChanged: true }
@@ -372,9 +372,13 @@ function startMcpServer(config) {
372
372
  try {
373
373
  // Load script template and interpolate parameters
374
374
  const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('create_project');
375
+ // Normalize paths for more consistent handling between platforms
376
+ const normalizedTargetPath = absPath.replace(/\\/g, '/');
377
+ const normalizedTemplatePath = templatePath.replace(/\\/g, '/');
378
+ console.error(`Using normalized paths for script: Target=${normalizedTargetPath}, Template=${normalizedTemplatePath}`);
375
379
  const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
376
- 'PROJECT_FILE_PATH': absPath.replace(/\\/g, '\\\\'),
377
- 'TEMPLATE_PROJECT_PATH': templatePath.replace(/\\/g, '\\\\')
380
+ 'PROJECT_FILE_PATH': normalizedTargetPath,
381
+ 'TEMPLATE_PROJECT_PATH': normalizedTemplatePath
378
382
  });
379
383
  console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
380
384
  const result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {