@codesys/mcp-toolkit 1.4.9 → 1.5.1
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/CHANGELOG.md +13 -0
- package/dist/codesys_interop.js +2 -2
- package/dist/direct_session_command.js +261 -0
- package/dist/server.js +31 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.1] - 2025-05-02
|
|
9
|
+
### Added
|
|
10
|
+
- Implemented direct session command handling for project creation
|
|
11
|
+
- Added ability to communicate directly with active sessions via command.json
|
|
12
|
+
- Created a simplified project creation script optimized for sessions
|
|
13
|
+
- Added fallback to standard execution if direct approach fails
|
|
14
|
+
|
|
15
|
+
## [1.5.0] - 2025-05-02
|
|
16
|
+
### Changed
|
|
17
|
+
- Temporarily disabled session management for create_project operations to ensure reliability
|
|
18
|
+
- Reverted to direct script execution model for project creation operations
|
|
19
|
+
- Project creation now uses a simpler, more reliable approach that works consistently
|
|
20
|
+
|
|
8
21
|
## [1.4.9] - 2025-05-02
|
|
9
22
|
### Fixed
|
|
10
23
|
- Fixed critical issue with create_project not succeeding in persistent sessions
|
package/dist/codesys_interop.js
CHANGED
|
@@ -91,8 +91,8 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName,
|
|
|
91
91
|
if (!fs.existsSync(codesysExePath))
|
|
92
92
|
throw new Error(`CODESYS executable not found at provided path: ${codesysExePath}`);
|
|
93
93
|
// --- End Pre-checks ---
|
|
94
|
-
//
|
|
95
|
-
const SESSION_FEATURE_DISABLED =
|
|
94
|
+
// Session management feature control - TEMPORARILY DISABLE FOR DEBUGGING
|
|
95
|
+
const SESSION_FEATURE_DISABLED = true; // Session feature disabled for now
|
|
96
96
|
// --- Check if we should use the persistent session ---
|
|
97
97
|
if (!SESSION_FEATURE_DISABLED && sessionManager) {
|
|
98
98
|
const sessionState = sessionManager.getState();
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Direct Session Command Tool
|
|
4
|
+
*
|
|
5
|
+
* This file provides functions to directly interact with a CODESYS session
|
|
6
|
+
* by writing to the command.json file and reading from the result.json file.
|
|
7
|
+
*
|
|
8
|
+
* This is an alternative approach to the standard session management.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
44
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
45
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
46
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
47
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
48
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
49
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.isSessionActive = isSessionActive;
|
|
54
|
+
exports.executeScriptDirect = executeScriptDirect;
|
|
55
|
+
exports.createProjectDirect = createProjectDirect;
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const os = __importStar(require("os"));
|
|
59
|
+
const uuid_1 = require("uuid");
|
|
60
|
+
// Session directory and file names
|
|
61
|
+
const SESSION_DIR = path.join(os.tmpdir(), 'codesys_session');
|
|
62
|
+
const COMMAND_FILE = path.join(SESSION_DIR, 'command.json');
|
|
63
|
+
const RESULT_FILE = path.join(SESSION_DIR, 'result.json');
|
|
64
|
+
const SESSION_LOCK_FILE = path.join(SESSION_DIR, 'session.lock');
|
|
65
|
+
const STATE_FILE = path.join(SESSION_DIR, 'session_state.json');
|
|
66
|
+
/**
|
|
67
|
+
* Check if a session is active by looking for the lock file
|
|
68
|
+
* @returns True if a session appears to be active
|
|
69
|
+
*/
|
|
70
|
+
function isSessionActive() {
|
|
71
|
+
try {
|
|
72
|
+
// Check if session directory exists
|
|
73
|
+
if (!fs.existsSync(SESSION_DIR)) {
|
|
74
|
+
console.error(`SESSION_DIR doesn't exist: ${SESSION_DIR}`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
// Check if lock file exists
|
|
78
|
+
if (!fs.existsSync(SESSION_LOCK_FILE)) {
|
|
79
|
+
console.error(`SESSION_LOCK_FILE doesn't exist: ${SESSION_LOCK_FILE}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// Check if command file exists
|
|
83
|
+
if (!fs.existsSync(COMMAND_FILE)) {
|
|
84
|
+
console.error(`COMMAND_FILE doesn't exist: ${COMMAND_FILE}`);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
// Read the lock file to check age
|
|
88
|
+
const lockFileContent = fs.readFileSync(SESSION_LOCK_FILE, 'utf8');
|
|
89
|
+
const lockTime = parseFloat(lockFileContent);
|
|
90
|
+
const currentTime = Date.now() / 1000; // convert to seconds
|
|
91
|
+
const lockAgeSeconds = currentTime - lockTime;
|
|
92
|
+
// If lock is too old, session might be dead
|
|
93
|
+
if (lockAgeSeconds > 30) {
|
|
94
|
+
console.error(`Session lock file is too old (${lockAgeSeconds.toFixed(1)} seconds)`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error(`Error checking session: ${error}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Execute a script by directly writing to the command.json file
|
|
106
|
+
* @param scriptContent The Python script to execute
|
|
107
|
+
* @param timeoutMs Timeout in milliseconds
|
|
108
|
+
* @returns Promise that resolves to the script result
|
|
109
|
+
*/
|
|
110
|
+
function executeScriptDirect(scriptContent_1) {
|
|
111
|
+
return __awaiter(this, arguments, void 0, function* (scriptContent, timeoutMs = 60000) {
|
|
112
|
+
if (!isSessionActive()) {
|
|
113
|
+
throw new Error('No active session found');
|
|
114
|
+
}
|
|
115
|
+
// Generate a unique ID for this execution
|
|
116
|
+
const scriptId = (0, uuid_1.v4)();
|
|
117
|
+
// Create command data
|
|
118
|
+
const commandData = {
|
|
119
|
+
status: "pending",
|
|
120
|
+
command: {
|
|
121
|
+
id: scriptId,
|
|
122
|
+
type: "execute_script",
|
|
123
|
+
script: scriptContent,
|
|
124
|
+
timestamp: Date.now()
|
|
125
|
+
},
|
|
126
|
+
timestamp: Date.now()
|
|
127
|
+
};
|
|
128
|
+
console.error(`DIRECT_SESSION: Writing command to ${COMMAND_FILE}`);
|
|
129
|
+
// Write command to file
|
|
130
|
+
fs.writeFileSync(COMMAND_FILE, JSON.stringify(commandData, null, 2));
|
|
131
|
+
console.error(`DIRECT_SESSION: Command written to file`);
|
|
132
|
+
// Poll for result
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
let pollCount = 0;
|
|
135
|
+
const pollInterval = setInterval(() => {
|
|
136
|
+
pollCount++;
|
|
137
|
+
try {
|
|
138
|
+
// Check if result file exists
|
|
139
|
+
if (!fs.existsSync(RESULT_FILE)) {
|
|
140
|
+
if (pollCount % 10 === 0) {
|
|
141
|
+
console.error(`DIRECT_SESSION: Result file not found at ${RESULT_FILE} (poll ${pollCount})`);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Read result file
|
|
146
|
+
const resultContent = fs.readFileSync(RESULT_FILE, 'utf8');
|
|
147
|
+
try {
|
|
148
|
+
const resultData = JSON.parse(resultContent);
|
|
149
|
+
// Check if the result is complete and for our script
|
|
150
|
+
if (resultData.status === 'complete' && resultData.result) {
|
|
151
|
+
const result = resultData.result;
|
|
152
|
+
if (result.id === scriptId) {
|
|
153
|
+
console.error(`DIRECT_SESSION: Found result for script ${scriptId}`);
|
|
154
|
+
// Clear interval
|
|
155
|
+
clearInterval(pollInterval);
|
|
156
|
+
// Resolve with the result
|
|
157
|
+
resolve({
|
|
158
|
+
success: result.success,
|
|
159
|
+
output: result.output
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (parseError) {
|
|
166
|
+
console.error(`DIRECT_SESSION: Error parsing result data: ${parseError}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error(`DIRECT_SESSION: Error reading result file: ${error}`);
|
|
171
|
+
}
|
|
172
|
+
}, 100);
|
|
173
|
+
// Set timeout
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
clearInterval(pollInterval);
|
|
176
|
+
reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
|
|
177
|
+
}, timeoutMs);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create a project using a session
|
|
183
|
+
* @param targetProjectPath Full path to the project to create
|
|
184
|
+
* @param templateProjectPath Path to the template project
|
|
185
|
+
* @returns Promise that resolves when the project is created
|
|
186
|
+
*/
|
|
187
|
+
function createProjectDirect(targetProjectPath, templateProjectPath) {
|
|
188
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
189
|
+
try {
|
|
190
|
+
if (!isSessionActive()) {
|
|
191
|
+
throw new Error('No active session found');
|
|
192
|
+
}
|
|
193
|
+
// Normalize paths for cross-platform compatibility
|
|
194
|
+
const normalizedTargetPath = targetProjectPath.replace(/\\/g, '/');
|
|
195
|
+
const normalizedTemplatePath = templateProjectPath.replace(/\\/g, '/');
|
|
196
|
+
// Create a simplified project creation script that only copies the file and doesn't try to open it
|
|
197
|
+
const simpleProjectCreationScript = `
|
|
198
|
+
import sys, os, shutil, time, traceback
|
|
199
|
+
|
|
200
|
+
TEMPLATE_PROJECT_PATH = '${normalizedTemplatePath}'
|
|
201
|
+
PROJECT_FILE_PATH = '${normalizedTargetPath}'
|
|
202
|
+
|
|
203
|
+
print("Direct project creation script")
|
|
204
|
+
print(" Template: %s" % TEMPLATE_PROJECT_PATH)
|
|
205
|
+
print(" Target: %s" % PROJECT_FILE_PATH)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Ensure the target directory exists
|
|
209
|
+
target_dir = os.path.dirname(PROJECT_FILE_PATH)
|
|
210
|
+
if not os.path.exists(target_dir):
|
|
211
|
+
print("Creating target directory: %s" % target_dir)
|
|
212
|
+
os.makedirs(target_dir)
|
|
213
|
+
|
|
214
|
+
# Check if template exists
|
|
215
|
+
if not os.path.exists(TEMPLATE_PROJECT_PATH):
|
|
216
|
+
print("ERROR: Template project not found: %s" % TEMPLATE_PROJECT_PATH)
|
|
217
|
+
print("SCRIPT_ERROR: Template project not found")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
# Copy the template to the target
|
|
221
|
+
print("Copying template to target...")
|
|
222
|
+
shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH)
|
|
223
|
+
|
|
224
|
+
# Verify the file was created
|
|
225
|
+
if os.path.exists(PROJECT_FILE_PATH):
|
|
226
|
+
print("Project file created successfully: %s" % PROJECT_FILE_PATH)
|
|
227
|
+
print("File size: %d bytes" % os.path.getsize(PROJECT_FILE_PATH))
|
|
228
|
+
print("SCRIPT_SUCCESS: Project file created successfully")
|
|
229
|
+
else:
|
|
230
|
+
print("ERROR: Failed to create project file")
|
|
231
|
+
print("SCRIPT_ERROR: Failed to create project file")
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print("ERROR: %s" % e)
|
|
235
|
+
traceback.print_exc()
|
|
236
|
+
print("SCRIPT_ERROR: %s" % e)
|
|
237
|
+
sys.exit(1)
|
|
238
|
+
`;
|
|
239
|
+
// Execute the script directly
|
|
240
|
+
const result = yield executeScriptDirect(simpleProjectCreationScript, 120000);
|
|
241
|
+
if (result.success) {
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
message: `Project created successfully at ${targetProjectPath}`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
message: `Failed to create project: ${result.output}`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
message: `Error: ${error.message}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -69,6 +69,7 @@ const promises_1 = require("fs/promises");
|
|
|
69
69
|
const fs = __importStar(require("fs"));
|
|
70
70
|
const scriptLoader_1 = require("./scriptLoader");
|
|
71
71
|
const sessionManager_1 = require("./sessionManager");
|
|
72
|
+
const direct_session_command_1 = require("./direct_session_command");
|
|
72
73
|
// --- Wrap server logic in an exported function ---
|
|
73
74
|
function startMcpServer(config) {
|
|
74
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -387,30 +388,42 @@ function startMcpServer(config) {
|
|
|
387
388
|
'SKIP_OPEN': inSession ? 'true' : 'false'
|
|
388
389
|
});
|
|
389
390
|
console.error(">>> create_project (copy-then-open): PREPARED SCRIPT:", scriptContent.substring(0, 500) + "...");
|
|
390
|
-
//
|
|
391
|
-
// This helps prevent timing out on slower systems or when CODESYS is busy
|
|
391
|
+
// Try the direct session approach first if a session is active
|
|
392
392
|
let result;
|
|
393
|
-
|
|
394
|
-
if
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
console.error(">>> create_project: Using session with extended timeout (120s)");
|
|
393
|
+
let usingDirectSession = false;
|
|
394
|
+
// Check if there's an active session using our direct method
|
|
395
|
+
if ((0, direct_session_command_1.isSessionActive)()) {
|
|
396
|
+
console.error(">>> create_project: Detected active session, using direct command approach");
|
|
398
397
|
try {
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
// Use our direct session command approach
|
|
399
|
+
usingDirectSession = true;
|
|
400
|
+
const directResult = yield (0, direct_session_command_1.createProjectDirect)(normalizedTargetPath, normalizedTemplatePath);
|
|
401
|
+
if (directResult.success) {
|
|
402
|
+
// Format the result to match the expected format
|
|
403
|
+
result = {
|
|
404
|
+
success: true,
|
|
405
|
+
output: `SCRIPT_SUCCESS: Project created from template using direct session command.\n${directResult.message}`
|
|
406
|
+
};
|
|
407
|
+
console.error(">>> create_project: Direct session command succeeded");
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.error(`>>> create_project: Direct session command failed: ${directResult.message}`);
|
|
411
|
+
// Fall back to regular script execution
|
|
412
|
+
usingDirectSession = false;
|
|
413
|
+
result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
|
|
414
|
+
}
|
|
405
415
|
}
|
|
406
|
-
catch (
|
|
407
|
-
console.error(`>>> create_project:
|
|
408
|
-
// Fall back to
|
|
416
|
+
catch (directError) {
|
|
417
|
+
console.error(`>>> create_project: Error with direct session command: ${directError.message}`);
|
|
418
|
+
// Fall back to regular script execution
|
|
419
|
+
usingDirectSession = false;
|
|
409
420
|
result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName);
|
|
410
421
|
}
|
|
411
422
|
}
|
|
412
|
-
|
|
413
|
-
|
|
423
|
+
// If not using direct session, use the standard approach
|
|
424
|
+
if (!usingDirectSession) {
|
|
425
|
+
// Use a longer timeout for project creation operations (120 seconds)
|
|
426
|
+
console.error(">>> create_project: Using standard script execution");
|
|
414
427
|
result = yield (0, codesys_interop_1.executeCodesysScript)(scriptContent, codesysExePath, codesysProfileName, sessionManager);
|
|
415
428
|
}
|
|
416
429
|
console.error(">>> create_project (copy-then-open): EXECUTION RESULT:", JSON.stringify(result));
|