@codesys/mcp-toolkit 1.0.1 → 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,85 +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
- // Spawn the CODESYS process WITHOUT shell:true for the main command
92
- const codesysProcess = (0, child_process_1.spawn)(codesysExePath, argsArray, {
93
- stdio: ['ignore', 'pipe', 'pipe'],
94
- shell: false
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
95
96
  });
96
- // Capture stdout & stderr
97
+ // Capture stdout
97
98
  codesysProcess.stdout.on('data', (data) => {
98
99
  const chunk = data.toString();
100
+ // console.error('CODESYS stdout:', chunk); // Optional: Original debug log
99
101
  output += chunk;
100
102
  });
103
+ // Capture stderr
101
104
  codesysProcess.stderr.on('data', (data) => {
102
105
  const chunk = data.toString();
103
- process.stderr.write(`CODESYS stderr: ${chunk}\n`);
104
- output += chunk;
106
+ process.stderr.write(`CODESYS stderr: ${chunk}\n`); // Use process.stderr
107
+ output += chunk; // Include stderr in the output for debugging
105
108
  });
106
109
  // Wait for the process to exit with a timeout
107
110
  const exitCode = yield new Promise((resolve, reject) => {
108
- // Note: timeoutMs is now defined in the outer scope
111
+ // Add timeout handling
112
+ const timeoutMs = 30000; // 30 seconds
109
113
  const timeout = setTimeout(() => {
110
- process.stderr.write(`TIMEOUT: CODESYS process did not complete in ${timeoutMs}ms. Killing process.\n`);
111
- codesysProcess.kill('SIGTERM');
112
- setTimeout(() => {
113
- if (!codesysProcess.killed) {
114
- process.stderr.write('Force killing CODESYS process after timeout.\n');
115
- codesysProcess.kill('SIGKILL');
116
- }
117
- }, 2000);
118
- resolve(999); // Use a distinct code for timeout
119
- }, 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);
120
119
  codesysProcess.on('close', (code) => {
121
120
  clearTimeout(timeout);
122
121
  resolve(code !== null && code !== void 0 ? code : 0);
123
122
  });
124
123
  codesysProcess.on('error', (err) => {
125
124
  clearTimeout(timeout);
126
- 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
127
127
  reject(err);
128
128
  });
129
129
  });
130
- process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`);
130
+ process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
131
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.
132
134
  if (output.includes(SCRIPT_SUCCESS_MARKER)) {
133
135
  success = true;
134
136
  }
135
137
  else if (output.includes(SCRIPT_ERROR_MARKER)) {
136
138
  success = false;
137
139
  }
138
- else if (exitCode !== 0) {
139
- success = false;
140
- if (!output.includes(SCRIPT_ERROR_MARKER)) {
141
- // Use timeoutMs from outer scope here - NOW IT WORKS!
142
- const exitReason = exitCode === 999 ? `due to timeout (${timeoutMs}ms)` : `with non-zero code ${exitCode}`;
143
- output += `\nSCRIPT_ERROR: Process exited ${exitReason}. Check CODESYS stderr output above for details.`;
144
- }
145
- }
146
140
  else {
147
- if (!output.includes(SCRIPT_SUCCESS_MARKER)) {
148
- 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
+ }
149
160
  }
150
- success = true;
151
161
  }
152
162
  }
153
163
  catch (error) {
154
- process.stderr.write(`Error executing CODESYS script: ${error}\n`);
164
+ process.stderr.write(`Error executing CODESYS script: ${error}\n`); // Use process.stderr
155
165
  output += `\nSCRIPT_ERROR: Failed to execute script process. ${error.message}`;
156
166
  success = false;
157
167
  }
@@ -159,16 +169,17 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
159
169
  // Clean up the temporary file
160
170
  try {
161
171
  yield (0, promises_1.unlink)(tempFilePath);
162
- process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`);
172
+ process.stderr.write(`Temporary script deleted: ${tempFilePath}\n`); // Use process.stderr
163
173
  }
164
174
  catch (cleanupError) {
165
- process.stderr.write(`Warning: Failed to delete temporary script file ${tempFilePath}: ${cleanupError.message}\n`);
166
- 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
167
178
  output += `\nWARNING: Failed to delete temporary script file ${tempFilePath}. ${cleanupError.message}`;
168
179
  }
169
180
  }
170
181
  }
171
- // process.stderr.write(`Final script output received:\n---\n${output}\n---\n`);
172
- 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
173
184
  });
174
185
  }