@codesys/mcp-toolkit 1.2.4 → 1.4.0

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.
@@ -63,11 +63,12 @@ const scriptLoader_1 = require("./scriptLoader");
63
63
  const DEFAULT_SCRIPT_TIMEOUT = 60000; // 60 seconds
64
64
  const DEFAULT_SESSION_IDLE_TIMEOUT = 300000; // 5 minutes
65
65
  const DEFAULT_SESSION_POLL_INTERVAL = 1000; // 1 second
66
- // Session directory and pipe names
66
+ // Session directory and file names
67
67
  const SESSION_DIR = path.join(os.tmpdir(), 'codesys_session');
68
- const COMMAND_PIPE = 'command_pipe';
69
- const RESULT_PIPE = 'result_pipe';
68
+ const COMMAND_FILE = 'command.json';
69
+ const RESULT_FILE = 'result.json';
70
70
  const STATE_FILE = 'session_state.json';
71
+ const SESSION_LOCK_FILE = 'session.lock';
71
72
  // Define expected success/error markers from the Python scripts
72
73
  const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
73
74
  const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
@@ -120,11 +121,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
120
121
  this.codesysProfileName = codesysProfileName;
121
122
  this.idleTimeoutMs = idleTimeoutMs;
122
123
  this.sessionId = (0, uuid_1.v4)();
123
- // Set up session directory and pipes
124
+ // Set up session directory and files
124
125
  this.sessionDir = path.join(SESSION_DIR, this.sessionId);
125
- this.commandPipePath = path.join(this.sessionDir, COMMAND_PIPE);
126
- this.resultPipePath = path.join(this.sessionDir, RESULT_PIPE);
126
+ this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
127
+ this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
127
128
  this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
129
+ this.lockFilePath = path.join(this.sessionDir, SESSION_LOCK_FILE);
128
130
  // Log status changes through event emitter
129
131
  this.on(exports.SESSION_EVENTS.STARTED, () => {
130
132
  console.error(`SESSION_MANAGER: Session started successfully`);
@@ -247,8 +249,8 @@ class CodesysSessionManager extends events_1.EventEmitter {
247
249
  // Interpolate parameters
248
250
  const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
249
251
  SESSION_DIR: this.sessionDir,
250
- COMMAND_PIPE: COMMAND_PIPE,
251
- RESULT_PIPE: RESULT_PIPE,
252
+ COMMAND_FILE: COMMAND_FILE,
253
+ RESULT_FILE: RESULT_FILE,
252
254
  STATE_FILE: STATE_FILE,
253
255
  SESSION_ID: this.sessionId,
254
256
  });
@@ -319,10 +321,10 @@ class CodesysSessionManager extends events_1.EventEmitter {
319
321
  const startTime = Date.now();
320
322
  while (Date.now() - startTime < timeoutMs) {
321
323
  try {
322
- // Check if pipes exist
323
- const commandPipeExists = fs.existsSync(this.commandPipePath);
324
- const resultPipeExists = fs.existsSync(this.resultPipePath);
325
- if (commandPipeExists && resultPipeExists) {
324
+ // Check if files exist
325
+ const commandFileExists = fs.existsSync(this.commandFilePath);
326
+ const resultFileExists = fs.existsSync(this.resultFilePath);
327
+ if (commandFileExists && resultFileExists) {
326
328
  // Read state file to check if session is ready
327
329
  const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
328
330
  const stateData = JSON.parse(stateContent);
@@ -366,14 +368,14 @@ class CodesysSessionManager extends events_1.EventEmitter {
366
368
  }
367
369
  try {
368
370
  // Check if we have a running session
369
- if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandPipePath)) {
371
+ if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandFilePath)) {
370
372
  // Load the stop_session script template
371
373
  const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('stop_session');
372
374
  // Interpolate parameters
373
375
  const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
374
376
  SESSION_DIR: this.sessionDir,
375
- COMMAND_PIPE: COMMAND_PIPE,
376
- RESULT_PIPE: RESULT_PIPE,
377
+ COMMAND_FILE: COMMAND_FILE,
378
+ RESULT_FILE: RESULT_FILE,
377
379
  STATE_FILE: STATE_FILE,
378
380
  SESSION_ID: this.sessionId,
379
381
  });
@@ -524,15 +526,15 @@ class CodesysSessionManager extends events_1.EventEmitter {
524
526
  this.resetIdleTimeout();
525
527
  // Mark the session as busy
526
528
  this.sessionState = exports.SessionState.BUSY;
527
- // Check if the command pipe exists
528
- if (!fs.existsSync(this.commandPipePath)) {
529
+ // Check if the command file exists
530
+ if (!fs.existsSync(this.commandFilePath)) {
529
531
  this.sessionState = exports.SessionState.ERROR;
530
- throw new Error(`Command pipe not found at ${this.commandPipePath}`);
532
+ throw new Error(`Command file not found at ${this.commandFilePath}`);
531
533
  }
532
- // Check if the result pipe exists
533
- if (!fs.existsSync(this.resultPipePath)) {
534
+ // Check if the result file exists
535
+ if (!fs.existsSync(this.resultFilePath)) {
534
536
  this.sessionState = exports.SessionState.ERROR;
535
- throw new Error(`Result pipe not found at ${this.resultPipePath}`);
537
+ throw new Error(`Result file not found at ${this.resultFilePath}`);
536
538
  }
537
539
  // Return a promise that will be resolved when script execution completes
538
540
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
@@ -571,15 +573,21 @@ class CodesysSessionManager extends events_1.EventEmitter {
571
573
  // Create the command to send
572
574
  const command = {
573
575
  id: scriptId,
576
+ type: "execute_script",
574
577
  script: scriptContent,
575
578
  timestamp: Date.now()
576
579
  };
577
- // Send the command
578
- const commandString = JSON.stringify(command) + '\n';
579
- fs.writeFileSync(this.commandPipePath, commandString);
580
- console.error(`SESSION_MANAGER: Command sent to pipe`);
581
- // Start reading the result pipe
582
- this.readResultPipe(scriptId);
580
+ // Prepare the command data
581
+ const commandData = {
582
+ status: "pending",
583
+ command: command,
584
+ timestamp: Date.now()
585
+ };
586
+ // Send the command by writing to the command file
587
+ fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData));
588
+ console.error(`SESSION_MANAGER: Command written to file`);
589
+ // Start reading the result file
590
+ this.readResultFile(scriptId);
583
591
  }
584
592
  catch (error) {
585
593
  console.error(`SESSION_MANAGER: Error executing script: ${error.message}`);
@@ -599,12 +607,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
599
607
  });
600
608
  }
601
609
  /**
602
- * Read the result pipe for a specific script execution
610
+ * Read the result file for a specific script execution
603
611
  * @param scriptId The script ID to read results for
604
612
  */
605
- readResultPipe(scriptId) {
606
- console.error(`SESSION_MANAGER: Starting to read result pipe for script ${scriptId}`);
607
- // We'll use a polling approach to read from the pipe
613
+ readResultFile(scriptId) {
614
+ console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
615
+ // We'll use a polling approach to read from the file
608
616
  const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
609
617
  try {
610
618
  if (!this.pendingScripts.has(scriptId)) {
@@ -612,21 +620,19 @@ class CodesysSessionManager extends events_1.EventEmitter {
612
620
  clearInterval(pollInterval);
613
621
  return;
614
622
  }
615
- // Check if the result pipe exists
616
- if (!fs.existsSync(this.resultPipePath)) {
617
- console.error(`SESSION_MANAGER: Result pipe not found at ${this.resultPipePath}`);
623
+ // Check if the result file exists
624
+ if (!fs.existsSync(this.resultFilePath)) {
625
+ console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath}`);
618
626
  return;
619
627
  }
620
- // Read the result pipe
621
- const resultContent = yield (0, promises_1.readFile)(this.resultPipePath, 'utf8');
622
- // Process all lines
623
- const lines = resultContent.split('\n');
624
- for (const line of lines) {
625
- if (!line.trim())
626
- continue;
627
- try {
628
- const result = JSON.parse(line);
629
- // Check if this result is for our script
628
+ // Read the result file
629
+ const resultContent = yield (0, promises_1.readFile)(this.resultFilePath, 'utf8');
630
+ try {
631
+ // Parse the result data
632
+ const resultData = JSON.parse(resultContent);
633
+ // Check if the result is complete and for our script
634
+ if (resultData.status === 'complete' && resultData.result) {
635
+ const result = resultData.result;
630
636
  if (result.id === scriptId) {
631
637
  console.error(`SESSION_MANAGER: Found result for script ${scriptId}`);
632
638
  // Remove from pending scripts
@@ -647,14 +653,13 @@ class CodesysSessionManager extends events_1.EventEmitter {
647
653
  return;
648
654
  }
649
655
  }
650
- catch (parseError) {
651
- console.error(`SESSION_MANAGER: Error parsing result line: ${parseError}`);
652
- // Continue with the next line
653
- }
656
+ }
657
+ catch (parseError) {
658
+ console.error(`SESSION_MANAGER: Error parsing result data: ${parseError}`);
654
659
  }
655
660
  }
656
661
  catch (error) {
657
- console.error(`SESSION_MANAGER: Error reading result pipe: ${error}`);
662
+ console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
658
663
  }
659
664
  }), 100); // Poll every 100ms
660
665
  // Set a timeout to stop polling after the script timeout
@@ -1,89 +1,110 @@
1
- # CODESYS MCP Session Management (Not Yet Implemented)
2
-
3
- > **IMPORTANT:** Session management is currently disabled in version 1.2.4.
4
- > The `start_session` and `stop_session` commands exist but are non-functional.
5
- > All operations will run in standard mode (starting and stopping CODESYS for each operation).
6
- > This feature will be fully implemented in a future release.
1
+ # Session Management in CODESYS MCP Toolkit
7
2
 
8
3
  ## Overview
9
4
 
10
- The CODESYS MCP Toolkit now supports persistent sessions, which significantly improves performance when executing multiple operations in sequence. This document explains how to use the session management functionality.
11
-
12
- ## Why Session Management?
5
+ Starting from version 1.4.0, the CODESYS MCP Toolkit supports persistent sessions. This feature significantly improves performance by allowing a single instance of CODESYS to stay open while multiple commands are executed, then closing it when explicitly requested or after a period of inactivity.
13
6
 
14
- Previously, each MCP command would start a new CODESYS process, execute the operation, and then shut down the process. This approach is inefficient when performing multiple operations, such as:
7
+ ## Benefits
15
8
 
16
- - Creating multiple POUs
17
- - Editing multiple code sections
18
- - Complex project setup with many steps
9
+ - **Faster execution**: Eliminates the overhead of starting and stopping CODESYS for each command
10
+ - **Shared context**: Commands executed in the same session share the same CODESYS runtime context
11
+ - **Resource efficiency**: Reduces CPU and memory usage by avoiding repeated process creation
12
+ - **Automatic timeout**: Sessions automatically shut down after a period of inactivity (10 minutes by default)
19
13
 
20
- With session management, you can:
21
- 1. Start a CODESYS session once
22
- 2. Execute multiple operations without restarting CODESYS
23
- 3. Close the session when you're finished
14
+ ## Session Management Commands
24
15
 
25
- This can reduce execution time from minutes to seconds for complex tasks.
16
+ The toolkit provides three specific commands for managing sessions:
26
17
 
27
- ## Using Session Management with Claude
18
+ ### 1. `start_session`
28
19
 
29
- You can now use the `start_session` and `stop_session` commands in your Claude interactions with CODESYS:
30
-
31
- ### Starting a Session
20
+ Starts a new persistent CODESYS session. If a session is already running, this command reports that fact without starting another.
32
21
 
22
+ **Example**:
33
23
  ```
34
- Using the CODESYS Local server, run the start_session command to open a persistent CODESYS session.
24
+ /mcp codesys_local start_session
35
25
  ```
36
26
 
37
- ### Using the Session
27
+ ### 2. `session_status`
38
28
 
39
- Once the session is started, all subsequent operations will use the persistent session automatically:
29
+ Checks if a session is currently active and returns information about it, including:
30
+ - Current state (running, busy, stopped, etc.)
31
+ - How long the session has been running
32
+ - Which project is open in the session (if any)
40
33
 
34
+ **Example**:
41
35
  ```
42
- Using the CODESYS Local server, create a new project at C:/MyProjects/Test.project
36
+ /mcp codesys_local session_status
43
37
  ```
44
38
 
45
- ```
46
- Using the CODESYS Local server, add a POU named MainProgram of type Program with ST language
47
- ```
39
+ ### 3. `stop_session`
48
40
 
49
- ### Checking Session Status
41
+ Stops an active session and cleans up session resources. If no session is running, this command reports that fact without taking any action.
50
42
 
43
+ **Example**:
51
44
  ```
52
- Using the CODESYS Local server, run the session_status command to check the current state of the CODESYS session.
45
+ /mcp codesys_local stop_session
53
46
  ```
54
47
 
55
- ### Stopping the Session
48
+ ## How to Use Session Management
56
49
 
57
- When you're done with all operations, you should close the session to free up resources:
50
+ ### Recommended Workflow
58
51
 
59
- ```
60
- Using the CODESYS Local server, run the stop_session command to close the CODESYS session.
61
- ```
52
+ 1. Start a session at the beginning of your work:
53
+ ```
54
+ /mcp codesys_local start_session
55
+ ```
56
+
57
+ 2. Execute all your commands normally - they will automatically use the active session:
58
+ ```
59
+ /mcp codesys_local open_project --filePath "C:/path/to/project.project"
60
+ /mcp codesys_local create_pou --projectFilePath "C:/path/to/project.project" --name "MyPOU" --type "FunctionBlock" --language "ST" --parentPath "Application"
61
+ ```
62
62
 
63
- ## Recommended Workflow
63
+ 3. When you're done working, stop the session:
64
+ ```
65
+ /mcp codesys_local stop_session
66
+ ```
64
67
 
65
- For the best performance, we recommend the following workflow when working with CODESYS projects:
68
+ If you forget to stop the session, it will automatically time out after 10 minutes of inactivity.
66
69
 
67
- 1. Start the session: `Using the CODESYS Local server, run the start_session command to open a persistent CODESYS session.`
68
- 2. Execute all required operations (create project, create POUs, edit code, etc.)
69
- 3. Stop the session: `Using the CODESYS Local server, run the stop_session command to close the CODESYS session.`
70
+ ### Session Behavior
70
71
 
71
- ## Technical Notes
72
+ - All commands automatically use an active session if one exists
73
+ - If no session exists when a command is run, it will execute without a session (traditional mode)
74
+ - You can explicitly start a session before running commands to improve performance
75
+ - Sessions maintain state between commands, including open projects
72
76
 
73
- - Sessions will automatically close after 5 minutes of inactivity to prevent resource leaks
74
- - The session status can be in one of the following states: `IDLE`, `STARTING`, `RUNNING`, `BUSY`, `STOPPING`, `STOPPED`, or `ERROR`
75
- - If a session operation fails, the system will automatically fall back to the standard one-time execution mode
77
+ ## Troubleshooting
76
78
 
77
- ## Limitations
79
+ ### Common Issues
78
80
 
79
- - The session management is still experimental and may not work with all CODESYS operations
80
- - Only one session can be active at a time per MCP server instance
81
- - Some operations that modify the CODESYS environment itself may require restarting the session
81
+ #### Session Won't Start
82
82
 
83
- ## Troubleshooting
83
+ If you can't start a session, check:
84
+ - Make sure CODESYS is properly installed and accessible at the path specified in your configuration
85
+ - Verify that the CODESYS profile exists and is valid
86
+ - Check if there are any error messages in the client's output
87
+
88
+ #### Session Appears Unresponsive
89
+
90
+ If a session seems stuck:
91
+ 1. Check its status: `/mcp codesys_local session_status`
92
+ 2. If it reports as active but not responding, try stopping it: `/mcp codesys_local stop_session`
93
+ 3. If it still doesn't respond, the toolkit includes a forced cleanup mechanism that will activate automatically when you try to stop a non-responsive session
94
+
95
+ #### Session Files Remain After Stopping
96
+
97
+ Session state is stored in a temporary directory (`[temp]/codesys_session/`). These files should be cleaned up automatically when a session stops, but in rare cases (like if the process is killed abruptly), they might remain. The toolkit will clean up old files when starting a new session.
98
+
99
+ ## Technical Details
100
+
101
+ The session management system works by:
84
102
 
85
- If you encounter issues with session management:
103
+ 1. Starting a CODESYS instance with a special Python script that enters a command loop
104
+ 2. Maintaining session state in files within a temporary directory
105
+ 3. Communicating with the running session via file-based IPC (Inter-Process Communication)
106
+ 4. Executing scripts within the context of the running CODESYS instance
107
+ 5. Automatically detecting and handling session health issues
86
108
 
87
- 1. Check the session status: `Using the CODESYS Local server, run the session_status command`
88
- 2. If the status is `ERROR`, try stopping the session and starting a new one
89
- 3. If problems persist, fall back to using standard operations without explicit session management
109
+ Session files are stored in the system's temporary directory in a subfolder called `codesys_session`.
110
+ EOF < /dev/null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesys/mcp-toolkit",
3
- "version": "1.2.4",
3
+ "version": "1.4.0",
4
4
  "description": "Model Context Protocol (MCP) server for CODESYS automation platform",
5
5
  "main": "dist/server.js",
6
6
  "bin": {