@codesys/mcp-toolkit 1.0.5 → 1.0.7

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.
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ const { exec } = require('child_process');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
7
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
8
+ const { z } = require('zod');
9
+
10
+ console.error("Starting standalone CODESYS MCP server...");
11
+
12
+ // Create a simple MCP server
13
+ const server = new McpServer({
14
+ name: "Standalone CODESYS MCP Server",
15
+ version: "1.0.0",
16
+ capabilities: { resources: {}, tools: {} }
17
+ });
18
+
19
+ // Direct implementation of executeCodesysScript
20
+ async function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName) {
21
+ // Validate inputs
22
+ if (!codesysExePath) throw new Error('CODESYS executable path was not provided');
23
+ if (!codesysProfileName) throw new Error('CODESYS profile name was not provided');
24
+
25
+ console.error(`DIRECT: Using CODESYS Path: ${codesysExePath}`);
26
+ console.error(`DIRECT: Using CODESYS Profile: ${codesysProfileName}`);
27
+
28
+ // Create temp script file
29
+ const tempDir = os.tmpdir();
30
+ const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).slice(2, 9)}.py`;
31
+ const tempFilePath = path.join(tempDir, tempFileName);
32
+
33
+ let output = '';
34
+ let success = false;
35
+
36
+ try {
37
+ // Write script to temp file
38
+ fs.writeFileSync(tempFilePath, scriptContent, 'latin1');
39
+ console.error(`DIRECT: Wrote script to: ${tempFilePath}`);
40
+
41
+ // Build command with proper quoting
42
+ const commandWithArgs = `"${codesysExePath}" --profile="${codesysProfileName}" --noUI --runscript="${tempFilePath}"`;
43
+ console.error(`DIRECT: Command: ${commandWithArgs}`);
44
+
45
+ // Execute using exec
46
+ success = await new Promise((resolve) => {
47
+ exec(commandWithArgs, (error, stdout, stderr) => {
48
+ if (stdout) output += stdout;
49
+ if (stderr) output += stderr;
50
+
51
+ console.error(`DIRECT: stdout: ${stdout.slice(0, 200)}${stdout.length > 200 ? '...' : ''}`);
52
+ if (stderr) console.error(`DIRECT: stderr: ${stderr}`);
53
+
54
+ if (error) {
55
+ console.error(`DIRECT: Error: ${error.message}`);
56
+ resolve(false);
57
+ } else {
58
+ resolve(true);
59
+ }
60
+ });
61
+ });
62
+
63
+ // Check for success/error markers in output
64
+ if (output.includes('SCRIPT_SUCCESS')) {
65
+ success = true;
66
+ } else if (output.includes('SCRIPT_ERROR')) {
67
+ success = false;
68
+ }
69
+
70
+ } catch (err) {
71
+ console.error(`DIRECT: Exception: ${err.message}`);
72
+ output += `\nError: ${err.message}`;
73
+ success = false;
74
+ } finally {
75
+ // Clean up temp file
76
+ try {
77
+ fs.unlinkSync(tempFilePath);
78
+ console.error(`DIRECT: Deleted temp file: ${tempFilePath}`);
79
+ } catch (err) {
80
+ console.error(`DIRECT: Failed to delete temp file: ${err.message}`);
81
+ }
82
+ }
83
+
84
+ return { success, output };
85
+ }
86
+
87
+ // Implement a basic check_status tool
88
+ server.tool("check_status", {}, async () => {
89
+ try {
90
+ const codesysPath = process.argv[2] || "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe";
91
+ const profileName = process.argv[3] || "CODESYS V3.5 SP21";
92
+
93
+ console.error(`DIRECT: Check Status with ${codesysPath} and ${profileName}`);
94
+
95
+ const scriptContent = `
96
+ import sys, scriptengine as script_engine, os
97
+ try:
98
+ print("Python script running")
99
+ print("Checking CODESYS status...")
100
+ scripting_ok = True
101
+ primary_project = script_engine.projects.primary
102
+ print("Primary project: " + str(primary_project))
103
+ print("SCRIPT_SUCCESS: Status check completed")
104
+ sys.exit(0)
105
+ except Exception as e:
106
+ print("Error: " + str(e))
107
+ print("SCRIPT_ERROR: " + str(e))
108
+ sys.exit(1)
109
+ `;
110
+
111
+ const result = await executeCodesysScript(scriptContent, codesysPath, profileName);
112
+ return {
113
+ content: [{ type: "text", text: `Status check ${result.success ? 'succeeded' : 'failed'}.\nOutput:\n${result.output}` }],
114
+ isError: !result.success
115
+ };
116
+ } catch (err) {
117
+ console.error(`DIRECT: Tool error: ${err.message}`);
118
+ return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
119
+ }
120
+ });
121
+
122
+ // Start the server
123
+ async function startServer() {
124
+ try {
125
+ const transport = new StdioServerTransport();
126
+ console.error("DIRECT: Connecting server via stdio...");
127
+ await server.connect(transport);
128
+ console.error("DIRECT: Server connected and listening");
129
+ } catch (error) {
130
+ console.error("DIRECT: Failed to start server:", error);
131
+ process.exit(1);
132
+ }
133
+ }
134
+
135
+ // Show args
136
+ console.error(`DIRECT: Args: ${JSON.stringify(process.argv)}`);
137
+
138
+ // Start the server
139
+ startServer();
140
+
141
+ // Handle shutdown
142
+ process.on('SIGINT', () => {
143
+ console.error('\nDIRECT: SIGINT received, shutting down...');
144
+ process.exit(0);
145
+ });
146
+
147
+ process.on('SIGTERM', () => {
148
+ console.error('\nDIRECT: SIGTERM received, shutting down...');
149
+ process.exit(0);
150
+ });
package/dist/bin.js CHANGED
@@ -18,13 +18,22 @@ commander_1.program
18
18
  .option('-p, --codesys-profile <profile>', 'CODESYS profile name', 'CODESYS V3.5 SP21')
19
19
  .parse(process.argv);
20
20
  const options = commander_1.program.opts();
21
- // Log startup message for debugging
21
+ // Enhanced logging for debugging purposes
22
+ console.error(`========================================`);
22
23
  console.error(`Starting CODESYS MCP Server v${version}`);
23
- console.error(`Using CODESYS path: ${options.codesysPath}`);
24
- console.error(`Using CODESYS profile: ${options.codesysProfile}`);
25
- // Pass command line arguments to the CODESYS executable path and profile
24
+ console.error(`CODESYS path: ${options.codesysPath}`);
25
+ console.error(`CODESYS profile: ${options.codesysProfile}`);
26
+ console.error(`Using Node.js ${process.version}`);
27
+ console.error(`Current directory: ${process.cwd()}`);
28
+ console.error(`========================================`);
29
+ // Pass properly sanitized command line arguments
30
+ const codesysPath = options.codesysPath.trim();
31
+ const codesysProfile = options.codesysProfile.trim();
26
32
  // The server.js file expects these as positional parameters
27
- process.argv[2] = options.codesysPath;
28
- process.argv[3] = options.codesysProfile;
33
+ process.argv[2] = codesysPath;
34
+ process.argv[3] = codesysProfile;
35
+ // Also set them as environment variables as a backup mechanism
36
+ process.env.CODESYS_PATH = codesysPath;
37
+ process.env.CODESYS_PROFILE = codesysProfile;
29
38
  // Import and start the server
30
39
  import('./server.js');
@@ -84,44 +84,41 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
84
84
  // Fix: Use shell:true but ensure proper quoting for paths with spaces
85
85
  const commandWithArgs = `"${codesysExePath}" --profile="${codesysProfileName}" --noUI --runscript="${tempFilePath}"`;
86
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
91
- });
92
- // Capture stdout
93
- codesysProcess.stdout.on('data', (data) => {
94
- const chunk = data.toString();
95
- // console.error('CODESYS stdout:', chunk); // Optional: Original debug log
96
- output += chunk;
97
- });
98
- // Capture stderr
99
- codesysProcess.stderr.on('data', (data) => {
100
- const chunk = data.toString();
101
- process.stderr.write(`CODESYS stderr: ${chunk}\n`); // Use process.stderr
102
- output += chunk; // Include stderr in the output for debugging
103
- });
104
- // Wait for the process to exit with a timeout
105
- const exitCode = yield new Promise((resolve, reject) => {
106
- // Add timeout handling
107
- const timeoutMs = 30000; // 30 seconds
108
- const timeout = setTimeout(() => {
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);
114
- codesysProcess.on('close', (code) => {
115
- clearTimeout(timeout);
116
- resolve(code !== null && code !== void 0 ? code : 0);
87
+ // CONFIRMED WORKING APPROACH: Use simple exec() with complete quoting
88
+ // This approach is tested and works with the CODESYS executable
89
+ const execResult = yield new Promise((resolve) => {
90
+ process.stderr.write(`EXECUTING COMMAND: ${commandWithArgs}\n`);
91
+ // Execute command using exec which handles Windows command paths better
92
+ const childProcess = (0, child_process_1.exec)(commandWithArgs, { timeout: 30000 }, (error, stdout, stderr) => {
93
+ var _a;
94
+ let combinedOutput = "";
95
+ if (stdout)
96
+ combinedOutput += stdout;
97
+ if (stderr)
98
+ combinedOutput += stderr;
99
+ // Log results for debugging
100
+ if (stdout)
101
+ process.stderr.write(`CODESYS stdout: ${stdout.length > 100 ? stdout.substring(0, 100) + '...' : stdout}\n`);
102
+ if (stderr)
103
+ process.stderr.write(`CODESYS stderr: ${stderr}\n`);
104
+ if (error) {
105
+ process.stderr.write(`CODESYS exec error: ${error.message}\n`);
106
+ resolve({ code: (_a = error.code) !== null && _a !== void 0 ? _a : 1, output: combinedOutput });
107
+ }
108
+ else {
109
+ resolve({ code: 0, output: combinedOutput });
110
+ }
117
111
  });
118
- codesysProcess.on('error', (err) => {
119
- clearTimeout(timeout);
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
122
- reject(err);
112
+ // Safety cleanup for timeout case
113
+ childProcess.on('timeout', () => {
114
+ process.stderr.write(`TIMEOUT: CODESYS process timed out after 30 seconds\n`);
115
+ childProcess.kill();
116
+ resolve({ code: 0, output: output + "\nTIMEOUT: CODESYS process timed out" });
123
117
  });
124
118
  });
119
+ // Add exec results to our output and get the exit code
120
+ output += execResult.output;
121
+ const exitCode = execResult.code;
125
122
  process.stderr.write(`CODESYS process exited with code: ${exitCode}\n`); // Use process.stderr
126
123
  // Determine success based on exit code and output markers
127
124
  // Note: CODESYS scripting might return exit code 0 even if the script had logical errors.