@codesys/mcp-toolkit 1.1.11 → 1.1.12

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,14 @@ 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.1.12] - 2025-05-06
9
+
10
+ ### Fixed
11
+ - Completely rewrote path handling to avoid excessive backslash escaping
12
+ - Added multiple fallback methods for opening CODESYS projects
13
+ - Removed use of non-existent get_version_info API
14
+ - Made project structure tool more resilient to various CODESYS API patterns
15
+
8
16
  ## [1.1.11] - 2025-05-06
9
17
 
10
18
  ### Fixed
@@ -8,31 +8,22 @@ import traceback
8
8
  MAX_RETRIES = 3
9
9
  RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
10
10
 
11
- # Special handling for Windows paths
12
- def sanitize_windows_path(path_str):
13
- """Properly sanitize Windows paths for CODESYS scripting"""
14
- # If it's already a Python raw string literal, return it as is
15
- if path_str.startswith('r"') or path_str.startswith("r'"):
16
- return path_str
17
-
18
- # Make Windows paths with backslashes work correctly when passed to CODESYS
19
- # We use actual backslashes (which will be doubled by Python raw string)
20
- sanitized = path_str.replace('\\', '\\\\')
21
-
22
- # Clean up any extraneous quotes
23
- sanitized = sanitized.strip('"\'')
24
-
25
- print("DEBUG: Sanitized path from '%s' to '%s'" % (path_str, sanitized))
26
- return sanitized
11
+ # Basic path cleanup without excessive escaping
12
+ def clean_path(path_str):
13
+ """Clean up a path for CODESYS scripting without excessive escaping"""
14
+ # Simply remove any extraneous quotes
15
+ cleaned = path_str.strip('"\'')
16
+ print("DEBUG: Cleaned path: '%s'" % cleaned)
17
+ return cleaned
27
18
 
28
19
  def ensure_project_open(target_project_path):
29
20
  print("DEBUG: Ensuring project is open: %s" % target_project_path)
30
21
 
31
- # First sanitize the path for proper Windows path handling
32
- sanitized_path = sanitize_windows_path(target_project_path)
22
+ # Just clean the path without adding escapes
23
+ path_to_use = clean_path(target_project_path)
33
24
 
34
25
  # Normalize target path once
35
- normalized_target_path = os.path.normcase(os.path.abspath(sanitized_path))
26
+ normalized_target_path = path_to_use
36
27
 
37
28
  for attempt in range(MAX_RETRIES):
38
29
  print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
@@ -6,6 +6,72 @@ def ensure_project_open(target_project_path):
6
6
  # Function implementation will be included from ensure_project_open.py
7
7
  pass
8
8
 
9
+ # Simpler version that directly uses script_engine.projects.open
10
+ def direct_project_open(project_path):
11
+ print("DEBUG: Attempting to open project directly: %s" % project_path)
12
+ try:
13
+ # Clean up any double backslashes
14
+ clean_path = project_path.replace('\\\\', '\\').strip('"\'')
15
+ print("DEBUG: Using path: %s" % clean_path)
16
+
17
+ # First check if the project is already open
18
+ try:
19
+ current = script_engine.get_current_project()
20
+ if current and hasattr(current, 'get_path'):
21
+ current_path = current.get_path()
22
+ if current_path == clean_path:
23
+ print("DEBUG: Project already open: %s" % clean_path)
24
+ return current
25
+ else:
26
+ print("DEBUG: Different project open. Closing: %s" % current_path)
27
+ script_engine.close_project()
28
+ except Exception as e:
29
+ print("DEBUG: Error checking current project: %s" % e)
30
+
31
+ # Try to open the project
32
+ try:
33
+ # Try different ways of opening the project
34
+ print("DEBUG: Trying script_engine.open_project")
35
+ project = script_engine.open_project(clean_path)
36
+ if project:
37
+ print("DEBUG: Project opened successfully with open_project")
38
+ return project
39
+ except Exception as e:
40
+ print("DEBUG: Error with open_project: %s" % e)
41
+
42
+ # Try alternative method
43
+ try:
44
+ print("DEBUG: Trying script_engine.projects.open")
45
+ project = script_engine.projects.open(clean_path)
46
+ if project:
47
+ print("DEBUG: Project opened successfully with projects.open")
48
+ return project
49
+ except Exception as e:
50
+ print("DEBUG: Error with projects.open: %s" % e)
51
+
52
+ # One more attempt if available
53
+ try:
54
+ print("DEBUG: Trying primary project access")
55
+ project = script_engine.projects.primary
56
+ if project:
57
+ print("DEBUG: Got primary project")
58
+ if hasattr(project, 'get_path'):
59
+ path = project.get_path()
60
+ print("DEBUG: Primary project path: %s" % path)
61
+ if path == clean_path:
62
+ return project
63
+ return project # Return anyway as fallback
64
+ except Exception as e:
65
+ print("DEBUG: Error getting primary project: %s" % e)
66
+
67
+ print("DEBUG: All attempts to open project failed")
68
+ return None
69
+
70
+ except Exception as e:
71
+ print("DEBUG: Unexpected error in direct_project_open: %s" % e)
72
+ traceback.print_exc()
73
+ return None
74
+
9
75
  def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
10
76
  lines = []; indent_str = " " * indent
11
77
  if obj is None:
@@ -55,37 +121,33 @@ def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
55
121
  traceback.print_exc() # Print detailed error for this node
56
122
  return lines
57
123
 
58
- # Normalize Windows paths for CODESYS scripting
59
- def normalize_path(path_str):
60
- # Replace backslashes with double backslashes for Python string in CODESYS
61
- normalized = path_str.replace('\\', '\\\\')
62
- # Remove any quotes that might be present
63
- normalized = normalized.strip('"\'')
64
- print("DEBUG: Normalized path from '%s' to '%s'" % (path_str, normalized))
65
- return normalized
124
+ # Simple path cleanup function - CODESYS doesn't need double-escaped paths
125
+ def clean_path(path_str):
126
+ # Just remove any quotes that might be present
127
+ cleaned = path_str.strip('"\'')
128
+ print("DEBUG: Cleaned path: '%s'" % cleaned)
129
+ return cleaned
66
130
 
67
131
  try:
68
- # Normalize the project path for CODESYS
69
- normalized_path = normalize_path(PROJECT_FILE_PATH)
70
- print("DEBUG: Getting structure for project path: '%s'" % normalized_path)
132
+ # Just use the path directly without escaping
133
+ project_path = PROJECT_FILE_PATH.strip('"\'')
134
+ print("DEBUG: Getting structure for project: '%s'" % project_path)
71
135
 
72
- # First confirm that CODESYS is running properly
73
- print("DEBUG: CODESYS version info:")
74
- try:
75
- version_info = script_engine.get_version_info()
76
- print("DEBUG: CODESYS Version: %s" % version_info)
77
- except Exception as v_err:
78
- print("DEBUG: Error getting CODESYS version: %s" % v_err)
136
+ print("DEBUG: Opening project directly...")
137
+ # Try our direct method first
138
+ primary_project = direct_project_open(project_path)
79
139
 
80
- print("DEBUG: Opening project...")
81
- primary_project = ensure_project_open(normalized_path)
140
+ # If that fails, try the standard method
141
+ if not primary_project:
142
+ print("DEBUG: Direct open failed, trying ensure_project_open...")
143
+ primary_project = ensure_project_open(project_path)
82
144
 
83
145
  if primary_project is None:
84
146
  print("DEBUG: ERROR - Project object is None after ensure_project_open")
85
- raise ValueError("Failed to open project: %s - project object is None" % normalized_path)
147
+ raise ValueError("Failed to open project: %s - project object is None" % project_path)
86
148
 
87
149
  print("DEBUG: Project object type: %s" % type(primary_project).__name__)
88
- project_name = os.path.basename(normalized_path)
150
+ project_name = os.path.basename(project_path)
89
151
 
90
152
  if hasattr(primary_project, 'get_name'):
91
153
  print("DEBUG: Project name from object: %s" % primary_project.get_name())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.1.11",
3
+ "version": "1.1.12",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
@@ -8,31 +8,22 @@ import traceback
8
8
  MAX_RETRIES = 3
9
9
  RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
10
10
 
11
- # Special handling for Windows paths
12
- def sanitize_windows_path(path_str):
13
- """Properly sanitize Windows paths for CODESYS scripting"""
14
- # If it's already a Python raw string literal, return it as is
15
- if path_str.startswith('r"') or path_str.startswith("r'"):
16
- return path_str
17
-
18
- # Make Windows paths with backslashes work correctly when passed to CODESYS
19
- # We use actual backslashes (which will be doubled by Python raw string)
20
- sanitized = path_str.replace('\\', '\\\\')
21
-
22
- # Clean up any extraneous quotes
23
- sanitized = sanitized.strip('"\'')
24
-
25
- print("DEBUG: Sanitized path from '%s' to '%s'" % (path_str, sanitized))
26
- return sanitized
11
+ # Basic path cleanup without excessive escaping
12
+ def clean_path(path_str):
13
+ """Clean up a path for CODESYS scripting without excessive escaping"""
14
+ # Simply remove any extraneous quotes
15
+ cleaned = path_str.strip('"\'')
16
+ print("DEBUG: Cleaned path: '%s'" % cleaned)
17
+ return cleaned
27
18
 
28
19
  def ensure_project_open(target_project_path):
29
20
  print("DEBUG: Ensuring project is open: %s" % target_project_path)
30
21
 
31
- # First sanitize the path for proper Windows path handling
32
- sanitized_path = sanitize_windows_path(target_project_path)
22
+ # Just clean the path without adding escapes
23
+ path_to_use = clean_path(target_project_path)
33
24
 
34
25
  # Normalize target path once
35
- normalized_target_path = os.path.normcase(os.path.abspath(sanitized_path))
26
+ normalized_target_path = path_to_use
36
27
 
37
28
  for attempt in range(MAX_RETRIES):
38
29
  print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
@@ -6,6 +6,72 @@ def ensure_project_open(target_project_path):
6
6
  # Function implementation will be included from ensure_project_open.py
7
7
  pass
8
8
 
9
+ # Simpler version that directly uses script_engine.projects.open
10
+ def direct_project_open(project_path):
11
+ print("DEBUG: Attempting to open project directly: %s" % project_path)
12
+ try:
13
+ # Clean up any double backslashes
14
+ clean_path = project_path.replace('\\\\', '\\').strip('"\'')
15
+ print("DEBUG: Using path: %s" % clean_path)
16
+
17
+ # First check if the project is already open
18
+ try:
19
+ current = script_engine.get_current_project()
20
+ if current and hasattr(current, 'get_path'):
21
+ current_path = current.get_path()
22
+ if current_path == clean_path:
23
+ print("DEBUG: Project already open: %s" % clean_path)
24
+ return current
25
+ else:
26
+ print("DEBUG: Different project open. Closing: %s" % current_path)
27
+ script_engine.close_project()
28
+ except Exception as e:
29
+ print("DEBUG: Error checking current project: %s" % e)
30
+
31
+ # Try to open the project
32
+ try:
33
+ # Try different ways of opening the project
34
+ print("DEBUG: Trying script_engine.open_project")
35
+ project = script_engine.open_project(clean_path)
36
+ if project:
37
+ print("DEBUG: Project opened successfully with open_project")
38
+ return project
39
+ except Exception as e:
40
+ print("DEBUG: Error with open_project: %s" % e)
41
+
42
+ # Try alternative method
43
+ try:
44
+ print("DEBUG: Trying script_engine.projects.open")
45
+ project = script_engine.projects.open(clean_path)
46
+ if project:
47
+ print("DEBUG: Project opened successfully with projects.open")
48
+ return project
49
+ except Exception as e:
50
+ print("DEBUG: Error with projects.open: %s" % e)
51
+
52
+ # One more attempt if available
53
+ try:
54
+ print("DEBUG: Trying primary project access")
55
+ project = script_engine.projects.primary
56
+ if project:
57
+ print("DEBUG: Got primary project")
58
+ if hasattr(project, 'get_path'):
59
+ path = project.get_path()
60
+ print("DEBUG: Primary project path: %s" % path)
61
+ if path == clean_path:
62
+ return project
63
+ return project # Return anyway as fallback
64
+ except Exception as e:
65
+ print("DEBUG: Error getting primary project: %s" % e)
66
+
67
+ print("DEBUG: All attempts to open project failed")
68
+ return None
69
+
70
+ except Exception as e:
71
+ print("DEBUG: Unexpected error in direct_project_open: %s" % e)
72
+ traceback.print_exc()
73
+ return None
74
+
9
75
  def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
10
76
  lines = []; indent_str = " " * indent
11
77
  if obj is None:
@@ -55,37 +121,33 @@ def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
55
121
  traceback.print_exc() # Print detailed error for this node
56
122
  return lines
57
123
 
58
- # Normalize Windows paths for CODESYS scripting
59
- def normalize_path(path_str):
60
- # Replace backslashes with double backslashes for Python string in CODESYS
61
- normalized = path_str.replace('\\', '\\\\')
62
- # Remove any quotes that might be present
63
- normalized = normalized.strip('"\'')
64
- print("DEBUG: Normalized path from '%s' to '%s'" % (path_str, normalized))
65
- return normalized
124
+ # Simple path cleanup function - CODESYS doesn't need double-escaped paths
125
+ def clean_path(path_str):
126
+ # Just remove any quotes that might be present
127
+ cleaned = path_str.strip('"\'')
128
+ print("DEBUG: Cleaned path: '%s'" % cleaned)
129
+ return cleaned
66
130
 
67
131
  try:
68
- # Normalize the project path for CODESYS
69
- normalized_path = normalize_path(PROJECT_FILE_PATH)
70
- print("DEBUG: Getting structure for project path: '%s'" % normalized_path)
132
+ # Just use the path directly without escaping
133
+ project_path = PROJECT_FILE_PATH.strip('"\'')
134
+ print("DEBUG: Getting structure for project: '%s'" % project_path)
71
135
 
72
- # First confirm that CODESYS is running properly
73
- print("DEBUG: CODESYS version info:")
74
- try:
75
- version_info = script_engine.get_version_info()
76
- print("DEBUG: CODESYS Version: %s" % version_info)
77
- except Exception as v_err:
78
- print("DEBUG: Error getting CODESYS version: %s" % v_err)
136
+ print("DEBUG: Opening project directly...")
137
+ # Try our direct method first
138
+ primary_project = direct_project_open(project_path)
79
139
 
80
- print("DEBUG: Opening project...")
81
- primary_project = ensure_project_open(normalized_path)
140
+ # If that fails, try the standard method
141
+ if not primary_project:
142
+ print("DEBUG: Direct open failed, trying ensure_project_open...")
143
+ primary_project = ensure_project_open(project_path)
82
144
 
83
145
  if primary_project is None:
84
146
  print("DEBUG: ERROR - Project object is None after ensure_project_open")
85
- raise ValueError("Failed to open project: %s - project object is None" % normalized_path)
147
+ raise ValueError("Failed to open project: %s - project object is None" % project_path)
86
148
 
87
149
  print("DEBUG: Project object type: %s" % type(primary_project).__name__)
88
- project_name = os.path.basename(normalized_path)
150
+ project_name = os.path.basename(project_path)
89
151
 
90
152
  if hasattr(primary_project, 'get_name'):
91
153
  print("DEBUG: Project name from object: %s" % primary_project.get_name())