@codesys/mcp-toolkit 1.0.3 → 1.0.5

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.
@@ -1,155 +1,155 @@
1
- # @codesys/mcp-toolkit
2
-
3
- ![npm](https://img.shields.io/npm/v/@codesys/mcp-toolkit)
4
- ![License](https://img.shields.io/github/license/yourusername/codesys-mcp-toolkit)
5
-
6
- A Model Context Protocol (MCP) server for CODESYS V3 programming environments. This toolkit enables seamless interaction between MCP clients (like Claude Desktop) and CODESYS, allowing automation of project management, POU creation, code editing, and compilation tasks.
7
-
8
- <p align="center">
9
- <img src="https://github.com/yourusername/codesys-mcp-toolkit/raw/main/docs/assets/diagram.png" alt="CODESYS MCP Toolkit Architecture" width="600">
10
- </p>
11
-
12
- ## 🌟 Features
13
-
14
- - **Project Management**
15
- - Open existing CODESYS projects
16
- - Create new projects from templates
17
- - Save project changes
18
-
19
- - **POU Management**
20
- - Create Programs, Function Blocks, and Functions
21
- - Set declaration and implementation code
22
- - Create properties and methods for Function Blocks
23
- - Compile projects
24
-
25
- - **MCP Resources**
26
- - `codesys://project/status`: Check scripting status and project state
27
- - `codesys://project/{+project_path}/structure`: Retrieve project structure
28
- - `codesys://project/{+project_path}/pou/{+pou_path}/code`: Read POU code
29
-
30
- - **MCP Tools**
31
- - `open_project`: Open specified CODESYS project
32
- - `create_project`: Create new CODESYS project
33
- - `save_project`: Save currently open project
34
- - `create_pou`: Create new Program, Function Block, or Function
35
- - `set_pou_code`: Update code for a specified POU
36
- - `create_property`: Create property in a Function Block
37
- - `create_method`: Create method in a Function Block
38
- - `compile_project`: Compile the open project
39
-
40
- ## 📋 Prerequisites
41
-
42
- - **CODESYS V3**: A working CODESYS V3 installation (tested with 3.5 SP21) with **Scripting Engine** component enabled
43
- - **Node.js**: Version 18 or later required
44
- - **MCP Client**: An MCP-enabled application (e.g., Claude Desktop)
45
-
46
- ## 🚀 Installation
47
-
48
- ### Option 1: Install from npm (Recommended)
49
-
50
- ```bash
51
- npm install -g @codesys/mcp-toolkit
52
- ```
53
-
54
- This installs the package globally, making the `codesys-mcp-toolkit` command available in your terminal.
55
-
56
- ### Option 2: Install from Source
57
-
58
- ```bash
59
- # Clone the repository
60
- git clone https://github.com/yourusername/codesys-mcp-toolkit.git
61
- cd codesys-mcp-toolkit
62
-
63
- # Install dependencies
64
- npm install
65
-
66
- # Build the project
67
- npm run build
68
-
69
- # Install globally
70
- npm install -g .
71
- ```
72
-
73
- ## 🔧 Configuration
74
-
75
- ### Claude Desktop Configuration
76
-
77
- 1. Locate your Claude Desktop configuration file:
78
- - Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`
79
-
80
- 2. Add the CODESYS MCP server entry:
81
-
82
- ```json
83
- {
84
- "mcpServers": {
85
- "codesys_local": {
86
- "command": "npx",
87
- "args": [
88
- "-y",
89
- "@codesys/mcp-toolkit",
90
- "--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
91
- "--codesys-profile", "CODESYS V3.5 SP21"
92
- ]
93
- }
94
- }
95
- }
96
- ```
97
-
98
- 3. Customize the paths to match your CODESYS installation:
99
- - `--codesys-path`: Path to your CODESYS.exe
100
- - `--codesys-profile`: Your CODESYS profile name
101
-
102
- 4. Restart Claude Desktop to apply the changes
103
-
104
- ## 🛠️ Usage Examples
105
-
106
- ### Creating a Function Block with Properties
107
-
108
- ```
109
- Ask Claude: "Using the CODESYS Local server, please create a Motor Controller function block with Start and Stop methods, plus CurrentSpeed property."
110
- ```
111
-
112
- Claude will execute these MCP tool calls:
113
- 1. `create_pou` to create the MotorController Function Block
114
- 2. `create_property` to add the CurrentSpeed property
115
- 3. `create_method` to add Start and Stop methods
116
- 4. `set_pou_code` to implement each method and property
117
-
118
- ### Opening and Compiling a Project
119
-
120
- ```
121
- Ask Claude: "Can you open the project at C:/MyProjects/TestProject.project using CODESYS Local and compile it?"
122
- ```
123
-
124
- Claude will:
125
- 1. Use `open_project` to open the specified file
126
- 2. Use `compile_project` to build the application
127
-
128
- ## 🔍 Troubleshooting
129
-
130
- ### Common Issues
131
-
132
- - **Connection Failures**: Check Claude Desktop logs for error messages
133
- - **Path Issues**: Ensure CODESYS executable path is correct
134
- - **Profile Mismatch**: Verify profile name matches exactly
135
- - **Permission Problems**: Ensure Claude has appropriate permissions
136
- - **CODESYS Already Running**: Close other CODESYS instances
137
-
138
- ### Debug Logs
139
-
140
- Check logs at:
141
- - Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\`
142
-
143
- ## 🤝 Contributing
144
-
145
- Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
146
-
147
- ## 📝 License
148
-
149
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
150
-
151
- ## 🙏 Acknowledgements
152
-
153
- - The [CODESYS](https://www.codesys.com/) team for their scripting engine
154
- - [Model Context Protocol](https://github.com/m-ld/model-context-protocol) project
155
- - All contributors who have helped improve this toolkit
1
+ # @codesys/mcp-toolkit
2
+
3
+ ![npm](https://img.shields.io/npm/v/@codesys/mcp-toolkit)
4
+ ![License](https://img.shields.io/github/license/yourusername/codesys-mcp-toolkit)
5
+
6
+ A Model Context Protocol (MCP) server for CODESYS V3 programming environments. This toolkit enables seamless interaction between MCP clients (like Claude Desktop) and CODESYS, allowing automation of project management, POU creation, code editing, and compilation tasks.
7
+
8
+ <p align="center">
9
+ <img src="https://github.com/yourusername/codesys-mcp-toolkit/raw/main/docs/assets/diagram.png" alt="CODESYS MCP Toolkit Architecture" width="600">
10
+ </p>
11
+
12
+ ## 🌟 Features
13
+
14
+ - **Project Management**
15
+ - Open existing CODESYS projects
16
+ - Create new projects from templates
17
+ - Save project changes
18
+
19
+ - **POU Management**
20
+ - Create Programs, Function Blocks, and Functions
21
+ - Set declaration and implementation code
22
+ - Create properties and methods for Function Blocks
23
+ - Compile projects
24
+
25
+ - **MCP Resources**
26
+ - `codesys://project/status`: Check scripting status and project state
27
+ - `codesys://project/{+project_path}/structure`: Retrieve project structure
28
+ - `codesys://project/{+project_path}/pou/{+pou_path}/code`: Read POU code
29
+
30
+ - **MCP Tools**
31
+ - `open_project`: Open specified CODESYS project
32
+ - `create_project`: Create new CODESYS project
33
+ - `save_project`: Save currently open project
34
+ - `create_pou`: Create new Program, Function Block, or Function
35
+ - `set_pou_code`: Update code for a specified POU
36
+ - `create_property`: Create property in a Function Block
37
+ - `create_method`: Create method in a Function Block
38
+ - `compile_project`: Compile the open project
39
+
40
+ ## 📋 Prerequisites
41
+
42
+ - **CODESYS V3**: A working CODESYS V3 installation (tested with 3.5 SP21) with **Scripting Engine** component enabled
43
+ - **Node.js**: Version 18 or later required
44
+ - **MCP Client**: An MCP-enabled application (e.g., Claude Desktop)
45
+
46
+ ## 🚀 Installation
47
+
48
+ ### Option 1: Install from npm (Recommended)
49
+
50
+ ```bash
51
+ npm install -g @codesys/mcp-toolkit
52
+ ```
53
+
54
+ This installs the package globally, making the `codesys-mcp-toolkit` command available in your terminal.
55
+
56
+ ### Option 2: Install from Source
57
+
58
+ ```bash
59
+ # Clone the repository
60
+ git clone https://github.com/yourusername/codesys-mcp-toolkit.git
61
+ cd codesys-mcp-toolkit
62
+
63
+ # Install dependencies
64
+ npm install
65
+
66
+ # Build the project
67
+ npm run build
68
+
69
+ # Install globally
70
+ npm install -g .
71
+ ```
72
+
73
+ ## 🔧 Configuration
74
+
75
+ ### Claude Desktop Configuration
76
+
77
+ 1. Locate your Claude Desktop configuration file:
78
+ - Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`
79
+
80
+ 2. Add the CODESYS MCP server entry:
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "codesys_local": {
86
+ "command": "npx",
87
+ "args": [
88
+ "-y",
89
+ "@codesys/mcp-toolkit",
90
+ "--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
91
+ "--codesys-profile", "CODESYS V3.5 SP21"
92
+ ]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ 3. Customize the paths to match your CODESYS installation:
99
+ - `--codesys-path`: Path to your CODESYS.exe
100
+ - `--codesys-profile`: Your CODESYS profile name
101
+
102
+ 4. Restart Claude Desktop to apply the changes
103
+
104
+ ## 🛠️ Usage Examples
105
+
106
+ ### Creating a Function Block with Properties
107
+
108
+ ```
109
+ Ask Claude: "Using the CODESYS Local server, please create a Motor Controller function block with Start and Stop methods, plus CurrentSpeed property."
110
+ ```
111
+
112
+ Claude will execute these MCP tool calls:
113
+ 1. `create_pou` to create the MotorController Function Block
114
+ 2. `create_property` to add the CurrentSpeed property
115
+ 3. `create_method` to add Start and Stop methods
116
+ 4. `set_pou_code` to implement each method and property
117
+
118
+ ### Opening and Compiling a Project
119
+
120
+ ```
121
+ Ask Claude: "Can you open the project at C:/MyProjects/TestProject.project using CODESYS Local and compile it?"
122
+ ```
123
+
124
+ Claude will:
125
+ 1. Use `open_project` to open the specified file
126
+ 2. Use `compile_project` to build the application
127
+
128
+ ## 🔍 Troubleshooting
129
+
130
+ ### Common Issues
131
+
132
+ - **Connection Failures**: Check Claude Desktop logs for error messages
133
+ - **Path Issues**: Ensure CODESYS executable path is correct
134
+ - **Profile Mismatch**: Verify profile name matches exactly
135
+ - **Permission Problems**: Ensure Claude has appropriate permissions
136
+ - **CODESYS Already Running**: Close other CODESYS instances
137
+
138
+ ### Debug Logs
139
+
140
+ Check logs at:
141
+ - Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\`
142
+
143
+ ## 🤝 Contributing
144
+
145
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
146
+
147
+ ## 📝 License
148
+
149
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
150
+
151
+ ## 🙏 Acknowledgements
152
+
153
+ - The [CODESYS](https://www.codesys.com/) team for their scripting engine
154
+ - [Model Context Protocol](https://github.com/m-ld/model-context-protocol) project
155
+ - All contributors who have helped improve this toolkit
@@ -73,86 +73,90 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
73
73
  const tempFilePath = path.join(tempDir, tempFileName);
74
74
  let output = '';
75
75
  let success = false;
76
- // Define timeout duration *before* the promise
77
- const timeoutMs = 60000; // Increased timeout to 60 seconds (accessible below)
78
76
  try {
79
77
  // Normalize line endings to LF (\n)
80
78
  const normalizedScriptContent = scriptContent.replace(/\r\n/g, '\n');
81
79
  // Write the script content to the temporary file using latin1 encoding
82
80
  yield (0, promises_1.writeFile)(tempFilePath, normalizedScriptContent, 'latin1'); // Explicitly use latin1
83
- process.stderr.write(`Temporary script written to: ${tempFilePath} with latin1 encoding and LF line endings\n`);
84
- // Arguments for CODESYS.exe
85
- const argsArray = [
86
- `--profile=${codesysProfileName}`,
87
- `--noUI`,
88
- `--runscript=${tempFilePath}`
89
- ];
90
- process.stderr.write(`Executing: "${codesysExePath}" with args: ${JSON.stringify(argsArray)}\n`);
91
- process.stderr.write('>>>> DEBUG: Attempting spawn with shell:true to handle paths with spaces. <<<<\n');
92
- // Spawn the CODESYS process WITH shell:true to handle paths with spaces properly
93
- const codesysProcess = (0, child_process_1.spawn)(`"${codesysExePath}"`, argsArray, {
94
- stdio: ['ignore', 'pipe', 'pipe'],
95
- shell: true
81
+ process.stderr.write(`Temporary script written to: ${tempFilePath} with latin1 encoding and LF line endings\n`); // Use process.stderr
82
+ // Add --additionalfolder argument
83
+ const additionalFolderPath = "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\AdditionalFolders\\Default"; // Assuming Default instance
84
+ // Fix: Use shell:true but ensure proper quoting for paths with spaces
85
+ const commandWithArgs = `"${codesysExePath}" --profile="${codesysProfileName}" --noUI --runscript="${tempFilePath}"`;
86
+ process.stderr.write(`Executing: ${commandWithArgs}\n`); // Log the command
87
+ // Spawn the CODESYS process with shell:true for compatibility with CODESYS argument parsing
88
+ const codesysProcess = (0, child_process_1.spawn)(commandWithArgs, {
89
+ stdio: ['ignore', 'pipe', 'pipe'], // Ignore stdin, pipe stdout and stderr
90
+ shell: true // Keep shell:true but with properly quoted paths
96
91
  });
97
- // Capture stdout & stderr
92
+ // Capture stdout
98
93
  codesysProcess.stdout.on('data', (data) => {
99
94
  const chunk = data.toString();
95
+ // console.error('CODESYS stdout:', chunk); // Optional: Original debug log
100
96
  output += chunk;
101
97
  });
98
+ // Capture stderr
102
99
  codesysProcess.stderr.on('data', (data) => {
103
100
  const chunk = data.toString();
104
- process.stderr.write(`CODESYS stderr: ${chunk}\n`);
105
- output += chunk;
101
+ process.stderr.write(`CODESYS stderr: ${chunk}\n`); // Use process.stderr
102
+ output += chunk; // Include stderr in the output for debugging
106
103
  });
107
104
  // Wait for the process to exit with a timeout
108
105
  const exitCode = yield new Promise((resolve, reject) => {
109
- // Note: timeoutMs is now defined in the outer scope
106
+ // Add timeout handling
107
+ const timeoutMs = 30000; // 30 seconds
110
108
  const timeout = setTimeout(() => {
111
- process.stderr.write(`TIMEOUT: CODESYS process did not complete in ${timeoutMs}ms. Killing process.\n`);
112
- codesysProcess.kill('SIGTERM');
113
- setTimeout(() => {
114
- if (!codesysProcess.killed) {
115
- process.stderr.write('Force killing CODESYS process after timeout.\n');
116
- codesysProcess.kill('SIGKILL');
117
- }
118
- }, 2000);
119
- resolve(999); // Use a distinct code for timeout
120
- }, timeoutMs); // Use timeoutMs here
109
+ process.stderr.write(`TIMEOUT: CODESYS process did not complete in ${timeoutMs}ms\n`);
110
+ // Force resolve with 0 to proceed with parsing output collected so far
111
+ codesysProcess.kill();
112
+ resolve(0);
113
+ }, timeoutMs);
121
114
  codesysProcess.on('close', (code) => {
122
115
  clearTimeout(timeout);
123
116
  resolve(code !== null && code !== void 0 ? code : 0);
124
117
  });
125
118
  codesysProcess.on('error', (err) => {
126
119
  clearTimeout(timeout);
127
- process.stderr.write(`Failed to start CODESYS process: ${err.message}\n`);
120
+ // Error starting the process itself (e.g., command not found)
121
+ process.stderr.write(`Failed to start CODESYS process: ${err.message}\n`); // Use process.stderr
128
122
  reject(err);
129
123
  });
130
124
  });
131
- process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`);
125
+ process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
132
126
  // Determine success based on exit code and output markers
127
+ // Note: CODESYS scripting might return exit code 0 even if the script had logical errors.
128
+ // Relying on specific output markers is more robust.
133
129
  if (output.includes(SCRIPT_SUCCESS_MARKER)) {
134
130
  success = true;
135
131
  }
136
132
  else if (output.includes(SCRIPT_ERROR_MARKER)) {
137
133
  success = false;
138
134
  }
139
- else if (exitCode !== 0) {
140
- success = false;
141
- if (!output.includes(SCRIPT_ERROR_MARKER)) {
142
- // Use timeoutMs from outer scope here - NOW IT WORKS!
143
- const exitReason = exitCode === 999 ? `due to timeout (${timeoutMs}ms)` : `with non-zero code ${exitCode}`;
144
- output += `\nSCRIPT_ERROR: Process exited ${exitReason}. Check CODESYS stderr output above for details.`;
145
- }
146
- }
147
135
  else {
148
- if (!output.includes(SCRIPT_SUCCESS_MARKER)) {
149
- process.stderr.write("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found.\n");
136
+ // If no markers found, fall back to exit code (less reliable)
137
+ success = exitCode === 0;
138
+ if (!success) {
139
+ // Append the specific CODESYS error if available and no other marker found
140
+ if (!output.includes(SCRIPT_ERROR_MARKER)) {
141
+ output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}. Check CODESYS stderr output above for details.`;
142
+ }
143
+ else {
144
+ output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}.`;
145
+ }
146
+ }
147
+ else {
148
+ // If exit code is 0 but no success marker, treat as potential issue or simple script
149
+ console.error("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found."); // Log warning to stderr
150
+ // Optionally, you could treat this as success or failure depending on requirements
151
+ // For now, let's assume success if exit code is 0 and no error marker.
152
+ if (!output.includes(SCRIPT_ERROR_MARKER)) {
153
+ success = true;
154
+ }
150
155
  }
151
- success = true;
152
156
  }
153
157
  }
154
158
  catch (error) {
155
- process.stderr.write(`Error executing CODESYS script: ${error}\n`);
159
+ process.stderr.write(`Error executing CODESYS script: ${error}\n`); // Use process.stderr
156
160
  output += `\nSCRIPT_ERROR: Failed to execute script process. ${error.message}`;
157
161
  success = false;
158
162
  }
@@ -160,16 +164,17 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
160
164
  // Clean up the temporary file
161
165
  try {
162
166
  yield (0, promises_1.unlink)(tempFilePath);
163
- process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
167
+ process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`); // Use process.stderr
164
168
  }
165
169
  catch (cleanupError) {
166
- process.stderr.write(`Warning: Failed to delete temporary script file ${tempFilePath}: ${cleanupError.message}\n`);
167
- if (success) {
170
+ process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`); // Use process.stderr
171
+ // Don't overwrite the original error if cleanup fails
172
+ if (success) { // Only add cleanup error if the main process didn't already fail
168
173
  output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
169
174
  }
170
175
  }
171
176
  }
172
- // process.stderr.write(`Final script output received:\n---\n${output}\n---\n`);
173
- return { success, output };
177
+ process.stderr.write(`Final script output received:\n---\n${output}\n---\n`); // Use process.stderr
178
+ return { success, output }; // Revert return object
174
179
  });
175
180
  }