@codesys/mcp-toolkit 1.0.3 → 1.0.4

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,95 @@ 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 = [
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:false and pass command and arguments separately to properly handle paths with spaces
85
+ const command = codesysExePath;
86
+ const args = [
86
87
  `--profile=${codesysProfileName}`,
87
- `--noUI`,
88
+ '--noUI',
88
89
  `--runscript=${tempFilePath}`
89
90
  ];
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
91
+ process.stderr.write(`Executing: ${command} ${args.join(' ')}\n`); // Log the command
92
+ // Spawn the CODESYS process using shell:false for proper path handling with spaces
93
+ const codesysProcess = (0, child_process_1.spawn)(command, args, {
94
+ stdio: ['ignore', 'pipe', 'pipe'], // Ignore stdin, pipe stdout and stderr
95
+ shell: false // Don't use shell to avoid issues with paths containing spaces
96
96
  });
97
- // Capture stdout & stderr
97
+ // Capture stdout
98
98
  codesysProcess.stdout.on('data', (data) => {
99
99
  const chunk = data.toString();
100
+ // console.error('CODESYS stdout:', chunk); // Optional: Original debug log
100
101
  output += chunk;
101
102
  });
103
+ // Capture stderr
102
104
  codesysProcess.stderr.on('data', (data) => {
103
105
  const chunk = data.toString();
104
- process.stderr.write(`CODESYS stderr: ${chunk}\n`);
105
- output += chunk;
106
+ process.stderr.write(`CODESYS stderr: ${chunk}\n`); // Use process.stderr
107
+ output += chunk; // Include stderr in the output for debugging
106
108
  });
107
109
  // Wait for the process to exit with a timeout
108
110
  const exitCode = yield new Promise((resolve, reject) => {
109
- // Note: timeoutMs is now defined in the outer scope
111
+ // Add timeout handling
112
+ const timeoutMs = 30000; // 30 seconds
110
113
  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
114
+ process.stderr.write(`TIMEOUT: CODESYS process did not complete in ${timeoutMs}ms\n`);
115
+ // Force resolve with 0 to proceed with parsing output collected so far
116
+ codesysProcess.kill();
117
+ resolve(0);
118
+ }, timeoutMs);
121
119
  codesysProcess.on('close', (code) => {
122
120
  clearTimeout(timeout);
123
121
  resolve(code !== null && code !== void 0 ? code : 0);
124
122
  });
125
123
  codesysProcess.on('error', (err) => {
126
124
  clearTimeout(timeout);
127
- process.stderr.write(`Failed to start CODESYS process: ${err.message}\n`);
125
+ // Error starting the process itself (e.g., command not found)
126
+ process.stderr.write(`Failed to start CODESYS process: ${err.message}\n`); // Use process.stderr
128
127
  reject(err);
129
128
  });
130
129
  });
131
- process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`);
130
+ process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
132
131
  // Determine success based on exit code and output markers
132
+ // Note: CODESYS scripting might return exit code 0 even if the script had logical errors.
133
+ // Relying on specific output markers is more robust.
133
134
  if (output.includes(SCRIPT_SUCCESS_MARKER)) {
134
135
  success = true;
135
136
  }
136
137
  else if (output.includes(SCRIPT_ERROR_MARKER)) {
137
138
  success = false;
138
139
  }
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
140
  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");
141
+ // If no markers found, fall back to exit code (less reliable)
142
+ success = exitCode === 0;
143
+ if (!success) {
144
+ // Append the specific CODESYS error if available and no other marker found
145
+ if (!output.includes(SCRIPT_ERROR_MARKER)) {
146
+ output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}. Check CODESYS stderr output above for details.`;
147
+ }
148
+ else {
149
+ output += `\nSCRIPT_ERROR: Process exited with non-zero code ${exitCode}.`;
150
+ }
151
+ }
152
+ else {
153
+ // If exit code is 0 but no success marker, treat as potential issue or simple script
154
+ console.error("Warning: CODESYS script finished with exit code 0 but no SCRIPT_SUCCESS marker found."); // Log warning to stderr
155
+ // Optionally, you could treat this as success or failure depending on requirements
156
+ // For now, let's assume success if exit code is 0 and no error marker.
157
+ if (!output.includes(SCRIPT_ERROR_MARKER)) {
158
+ success = true;
159
+ }
150
160
  }
151
- success = true;
152
161
  }
153
162
  }
154
163
  catch (error) {
155
- process.stderr.write(`Error executing CODESYS script: ${error}\n`);
164
+ process.stderr.write(`Error executing CODESYS script: ${error}\n`); // Use process.stderr
156
165
  output += `\nSCRIPT_ERROR: Failed to execute script process. ${error.message}`;
157
166
  success = false;
158
167
  }
@@ -160,16 +169,17 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
160
169
  // Clean up the temporary file
161
170
  try {
162
171
  yield (0, promises_1.unlink)(tempFilePath);
163
- process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
172
+ process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`); // Use process.stderr
164
173
  }
165
174
  catch (cleanupError) {
166
- process.stderr.write(`Warning: Failed to delete temporary script file ${tempFilePath}: ${cleanupError.message}\n`);
167
- if (success) {
175
+ process.stderr.write(`Failed to delete temporary script file ${tempFilePath}: ${cleanupError}\n`); // Use process.stderr
176
+ // Don't overwrite the original error if cleanup fails
177
+ if (success) { // Only add cleanup error if the main process didn't already fail
168
178
  output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
169
179
  }
170
180
  }
171
181
  }
172
- // process.stderr.write(`Final script output received:\n---\n${output}\n---\n`);
173
- return { success, output };
182
+ process.stderr.write(`Final script output received:\n---\n${output}\n---\n`); // Use process.stderr
183
+ return { success, output }; // Revert return object
174
184
  });
175
185
  }