@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.
- package/direct-codesys-mcp.js +150 -0
- package/dist/bin.js +15 -6
- package/dist/codesys_interop.js +32 -35
- package/dist/server.js +899 -884
- package/package.json +45 -47
|
@@ -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
|
-
//
|
|
21
|
+
// Enhanced logging for debugging purposes
|
|
22
|
+
console.error(`========================================`);
|
|
22
23
|
console.error(`Starting CODESYS MCP Server v${version}`);
|
|
23
|
-
console.error(`
|
|
24
|
-
console.error(`
|
|
25
|
-
|
|
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] =
|
|
28
|
-
process.argv[3] =
|
|
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');
|
package/dist/codesys_interop.js
CHANGED
|
@@ -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
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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.
|