@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 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
@@ -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
- // Enable or disable session management
95
- const SESSION_FEATURE_DISABLED = false; // Session feature enabled
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
- // Use a longer timeout for project creation operations in sessions (120 seconds)
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
- // Check if we're running in a session
394
- if (sessionManager &&
395
- (sessionManager.getState() === sessionManager_1.SessionState.RUNNING ||
396
- sessionManager.getState() === sessionManager_1.SessionState.BUSY)) {
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
- // Execute script directly through session manager to specify longer timeout
400
- const scriptResult = yield sessionManager.executeScript(scriptContent, 120000);
401
- result = {
402
- success: scriptResult.success,
403
- output: scriptResult.output
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 (sessionError) {
407
- console.error(`>>> create_project: Session execution failed, falling back to one-time execution: ${sessionError.message}`);
408
- // Fall back to one-time execution
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
- else {
413
- // Standard execution without a session
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.4.9",
3
+ "version": "1.5.1",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {