@codesys/mcp-toolkit 1.5.8 → 2.0.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +153 -189
  2. package/LICENSE +21 -21
  3. package/README.md +269 -152
  4. package/dist/actions.d.ts +28 -0
  5. package/dist/actions.js +98 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/bridge/bridge.d.ts +105 -0
  8. package/dist/bridge/bridge.js +400 -0
  9. package/dist/bridge/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +196 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/codesys/cache.d.ts +48 -0
  14. package/dist/codesys/cache.js +82 -0
  15. package/dist/codesys/cache.js.map +1 -0
  16. package/dist/codesys/changes.d.ts +57 -0
  17. package/dist/codesys/changes.js +133 -0
  18. package/dist/codesys/changes.js.map +1 -0
  19. package/dist/codesys/decl.d.ts +45 -0
  20. package/dist/codesys/decl.js +251 -0
  21. package/dist/codesys/decl.js.map +1 -0
  22. package/dist/codesys/devices.d.ts +34 -0
  23. package/dist/codesys/devices.js +82 -0
  24. package/dist/codesys/devices.js.map +1 -0
  25. package/dist/codesys/diff.d.ts +13 -0
  26. package/dist/codesys/diff.js +61 -0
  27. package/dist/codesys/diff.js.map +1 -0
  28. package/dist/codesys/install.d.ts +40 -0
  29. package/dist/codesys/install.js +144 -0
  30. package/dist/codesys/install.js.map +1 -0
  31. package/dist/codesys/library-browse.d.ts +19 -0
  32. package/dist/codesys/library-browse.js +57 -0
  33. package/dist/codesys/library-browse.js.map +1 -0
  34. package/dist/codesys/outline.d.ts +32 -0
  35. package/dist/codesys/outline.js +219 -0
  36. package/dist/codesys/outline.js.map +1 -0
  37. package/dist/codesys/simulation.d.ts +34 -0
  38. package/dist/codesys/simulation.js +57 -0
  39. package/dist/codesys/simulation.js.map +1 -0
  40. package/dist/codesys/st.d.ts +36 -0
  41. package/dist/codesys/st.js +69 -0
  42. package/dist/codesys/st.js.map +1 -0
  43. package/dist/daemon.d.ts +46 -0
  44. package/dist/daemon.js +173 -0
  45. package/dist/daemon.js.map +1 -0
  46. package/dist/driver.d.ts +25 -0
  47. package/dist/driver.js +33 -0
  48. package/dist/driver.js.map +1 -0
  49. package/dist/panel/sim-panel.html +357 -0
  50. package/dist/panel/viewer.html +126 -0
  51. package/dist/panel-types.d.ts +20 -0
  52. package/dist/panel-types.js +3 -0
  53. package/dist/panel-types.js.map +1 -0
  54. package/dist/panel.d.ts +27 -0
  55. package/dist/panel.js +208 -0
  56. package/dist/panel.js.map +1 -0
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +33 -974
  59. package/dist/server.js.map +1 -0
  60. package/dist/tools.d.ts +103 -0
  61. package/dist/tools.js +793 -0
  62. package/dist/tools.js.map +1 -0
  63. package/dist/viewer.d.ts +14 -0
  64. package/dist/viewer.js +125 -0
  65. package/dist/viewer.js.map +1 -0
  66. package/package.json +66 -51
  67. package/watcher/mcp_compat.py +147 -0
  68. package/watcher/mcp_ops.py +1178 -0
  69. package/watcher/mcp_sim.py +246 -0
  70. package/watcher/watcher.py +255 -0
  71. package/.claude/settings.local.json +0 -29
  72. package/CLAUDE.md +0 -26
  73. package/CONTRIBUTING.md +0 -72
  74. package/README.md.enhanced +0 -155
  75. package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
  76. package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
  77. package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
  78. package/codesys-mcp-toolkit-1.5.8.tgz +0 -0
  79. package/dist/bin.js +0 -75
  80. package/dist/check_session_paths.js +0 -149
  81. package/dist/codesys_interop.js +0 -435
  82. package/dist/direct_session_command.js +0 -293
  83. package/dist/scriptLoader.js +0 -165
  84. package/dist/scripts/check_status.py +0 -21
  85. package/dist/scripts/compile_project.py +0 -65
  86. package/dist/scripts/create_method.py +0 -80
  87. package/dist/scripts/create_pou.py +0 -102
  88. package/dist/scripts/create_project.py +0 -116
  89. package/dist/scripts/create_property.py +0 -78
  90. package/dist/scripts/ensure_project_open.py +0 -154
  91. package/dist/scripts/find_object_by_path.py +0 -111
  92. package/dist/scripts/get_pou_code.py +0 -91
  93. package/dist/scripts/get_project_structure.py +0 -72
  94. package/dist/scripts/open_project.py +0 -25
  95. package/dist/scripts/save_project.py +0 -29
  96. package/dist/scripts/session_status.py +0 -281
  97. package/dist/scripts/set_pou_code.py +0 -109
  98. package/dist/scripts/start_session.py +0 -809
  99. package/dist/scripts/stop_session.py +0 -324
  100. package/dist/sessionManager.js +0 -798
  101. package/docs/configuration.md +0 -141
  102. package/docs/index.md +0 -26
  103. package/docs/installation.md +0 -84
  104. package/docs/session_management.md +0 -110
  105. package/examples/README.md +0 -78
  106. package/examples/sample_config.json +0 -32
  107. package/replacements_session_status.txt +0 -16
  108. package/scriptDoc.txt +0 -8679
  109. package/scripts/build.js +0 -25
  110. package/scripts/fix_fstrings.py +0 -76
package/README.md CHANGED
@@ -1,152 +1,269 @@
1
-
2
- # @codesys/mcp-toolkit
3
-
4
- ![npm](https://img.shields.io/npm/v/@codesys/mcp-toolkit)
5
- ![License](https://img.shields.io/github/license/johannesPettersson80/codesys-mcp-toolkit)
6
- ![Node Version](https://img.shields.io/node/v/@codesys/mcp-toolkit)
7
-
8
- A Model Context Protocol (MCP) server for CODESYS V3 programming environments. This toolkit enables seamless interaction between MCP clients (like Claude Desktop) and CODESYS, allowing automation of project management, POU creation, code editing, and compilation tasks via the CODESYS Scripting Engine.
9
-
10
- ## 🌟 Features
11
-
12
- - **Project Management**
13
- - Open existing CODESYS projects (`open_project`)
14
- - Create new projects from standard templates (`create_project`)
15
- - Save project changes (`save_project`)
16
-
17
- - **POU Management**
18
- - Create Programs, Function Blocks, and Functions (`create_pou`)
19
- - Set declaration and implementation code (`set_pou_code`)
20
- - Create properties for Function Blocks (`create_property`)
21
- - Create methods for Function Blocks (`create_method`)
22
- - Compile projects (`compile_project`)
23
-
24
- - **Session Management** (New!)
25
- - Start a persistent CODESYS session (`start_session`)
26
- - Execute multiple operations without restarting CODESYS
27
- - Check session status (`session_status`)
28
- - Close the session when finished (`stop_session`)
29
- - See [Session Management Documentation](docs/session_management.md) for details
30
-
31
- - **MCP Resources**
32
- - `codesys://project/status`: Check scripting status and currently open project state.
33
- - `codesys://project/{+project_path}/structure`: Retrieve the object structure of a specified project.
34
- - `codesys://project/{+project_path}/pou/{+pou_path}/code`: Read the declaration and implementation code for a specified POU, Method, or Property accessor.
35
-
36
- ## 📋 Prerequisites
37
-
38
- - **CODESYS V3**: A working CODESYS V3 installation (tested with 3.5 SP21) with the **Scripting Engine** component enabled during installation.
39
- - **Node.js**: Version 18.0.0 or later is recommended.
40
- - **MCP Client**: An MCP-enabled application (e.g., Claude Desktop).
41
-
42
- *(Note: CODESYS uses Python 2.7 internally for its scripting engine, but this toolkit handles the interaction; you do not need to manage Python separately.)*
43
-
44
- ## 🚀 Installation
45
-
46
- The recommended way to install is globally using npm:
47
-
48
- ```bash
49
- npm install -g @codesys/mcp-toolkit
50
- ```
51
-
52
- This installs the package globally, making the `codesys-mcp-tool` command available in your system's terminal PATH.
53
-
54
- *(Advanced users can also install from source for development - see CONTRIBUTING.md if available).*
55
-
56
- ## 🔧 Configuration (IMPORTANT!)
57
-
58
- This toolkit needs to know where your CODESYS installation is and which profile to use. Configuration is typically done within your MCP Client application (like Claude Desktop).
59
-
60
- ### Recommended Configuration Method (Direct Command)
61
-
62
- Due to potential environment variable issues (especially with `PATH`) when launching Node.js tools via wrappers like `npx` within certain host applications (e.g., Claude Desktop), it is **strongly recommended** to configure your MCP client to run the installed command `codesys-mcp-tool` **directly**.
63
-
64
- **Example for Claude Desktop (`settings.json` -> `mcpServers`):**
65
-
66
- ```json
67
- {
68
- "mcpServers": {
69
- // ... other servers ...
70
- "codesys_local": {
71
- "command": "codesys-mcp-tool", // <<< Use the direct command name
72
- "args": [
73
- // Pass arguments directly to the tool using flags
74
- "--codesys-path", "C:\\Program Files\\Path\\To\\Your\\CODESYS\\Common\\CODESYS.exe",
75
- "--codesys-profile", "Your CODESYS Profile Name"
76
- // Optional: Add --workspace "/path/to/your/projects" if needed
77
- ]
78
- }
79
- // ... other servers ...
80
- }
81
- }
82
- ```
83
-
84
- **Key Steps:**
85
- 1. Replace `"C:\\Program Files\\Path\\To\\Your\\CODESYS\\Common\\CODESYS.exe"` with the **full, correct path** to your specific `CODESYS.exe` file.
86
- 2. Replace `"Your CODESYS Profile Name"` with the **exact name** of the CODESYS profile you want to use (visible in the CODESYS UI).
87
- 3. Ensure the `codesys-mcp-tool` command is accessible in the system PATH where the MCP Client application runs. Global installation via `npm install -g` usually handles this.
88
- 4. Restart your MCP Client application (e.g., Claude Desktop) to apply the settings changes.
89
-
90
- ### Alternative Configuration (Using `npx` - Not Recommended)
91
-
92
- Launching with `npx` has been observed to cause immediate errors (`'C:\Program' is not recognized...`) in some environments, likely due to how `npx` handles the execution environment. **Use the Direct Command method above if possible.** If you must use `npx`:
93
-
94
- ```json
95
- // Example using npx (POTENTIALLY PROBLEMATIC - USE WITH CAUTION):
96
- {
97
- "mcpServers": {
98
- "codesys_local": {
99
- "command": "npx",
100
- "args": [
101
- "-y", // Tells npx to install temporarily if not found globally
102
- "@codesys/mcp-toolkit",
103
- // Arguments for the tool MUST come AFTER the package name
104
- "--codesys-path", "C:\\Program Files\\Path\\To\\Your\\CODESYS\\Common\\CODESYS.exe",
105
- "--codesys-profile", "Your CODESYS Profile Name"
106
- ]
107
- }
108
- }
109
- }
110
- ```
111
- *(Note: The `--` separator after the package name might sometimes help `npx` but is not guaranteed to fix the environment issue.)*
112
-
113
- ## 🛠️ Command-Line Arguments
114
-
115
- When running `codesys-mcp-tool` directly or configuring it, you can use these arguments:
116
-
117
- * `-p, --codesys-path <path>`: Full path to `CODESYS.exe`. (Required, overrides `CODESYS_PATH` env var, has a default but relying on it is not recommended).
118
- * `-f, --codesys-profile <profile>`: Name of the CODESYS profile. (Required, overrides `CODESYS_PROFILE` env var, has a default but relying on it is not recommended).
119
- * `-w, --workspace <dir>`: Workspace directory for resolving relative project paths passed to tools. Defaults to the directory where the command was launched (which might be unpredictable when run by another application). Setting this explicitly might be needed if using relative paths.
120
- * `-h, --help`: Show help message.
121
- * `--version`: Show package version.
122
-
123
- ## 🔍 Troubleshooting
124
-
125
- * **`'C:\Program' is not recognized...` error immediately after connection:**
126
- * **Cause:** This typically happens when the tool is launched via `npx` within an environment like Claude Desktop. The execution environment (`PATH` variable) provided to the process likely causes an internal CODESYS command (like running Python) to fail.
127
- * **Solution:** Configure your MCP Client to run the command **directly** (`"command": "codesys-mcp-tool"`) instead of using `"command": "npx"`. See the **Recommended Configuration Method** section above.
128
-
129
- * **Tool Fails / Errors in Output:**
130
- * Check the logs from your MCP Client application (e.g., Claude Desktop logs). Look for `INTEROP:` messages or Python `DEBUG:` / `ERROR:` messages printed to stderr from the CODESYS script execution.
131
- * Ensure the `--codesys-path` and `--codesys-profile` arguments passed to the command are correct and point to a valid CODESYS installation with scripting enabled.
132
- * Verify the project paths and object paths you are passing to tools are correct (use forward slashes `/`).
133
- * Make sure no other CODESYS instances are running in conflicting ways (e.g., holding a lock on the profile).
134
-
135
- * **`command not found: codesys-mcp-tool`:**
136
- * Ensure the package was installed globally (`npm install -g @codesys/mcp-toolkit`).
137
- * Ensure the npm global bin directory is in your system's `PATH` environment variable. Find it with `npm config get prefix` and add the `bin` subdirectory (or the main directory itself on Windows) to your PATH.
138
-
139
- * **Check Logs:**
140
- * Claude Desktop logs: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\` (Windows)
141
-
142
- ## 🤝 Contributing
143
- Contributions, issues, and feature requests are welcome! Feel free to check the issues page. (Optionally add a CONTRIBUTING.md file with more details).
144
-
145
- ## 📝 License
146
- This project is licensed under the MIT License - see the LICENSE file for details.
147
-
148
- ## 🙏 Acknowledgements
149
- - The CODESYS GmbH team for the powerful CODESYS platform and its scripting engine.
150
- - The Model Context Protocol project for defining the interaction standard.
151
- - All contributors and users who help improve this toolkit.
152
-
1
+ <div align="center">
2
+
3
+ # CODESYS MCP Toolkit
4
+
5
+ **Let Claude read, write, compile and simulate Structured Text in your CODESYS V3.5 projects.**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
+ [![npm](https://img.shields.io/npm/v/@codesys/mcp-toolkit)](https://www.npmjs.com/package/@codesys/mcp-toolkit)
9
+ ![CODESYS V3.5](https://img.shields.io/badge/CODESYS-V3.5-c8102e)
10
+ ![Node.js 20 or later](https://img.shields.io/badge/node-%E2%89%A520-339933)
11
+ ![Platform: Windows](https://img.shields.io/badge/platform-Windows-0078d4)
12
+ [![Model Context Protocol](https://img.shields.io/badge/MCP-server-6e56cf)](https://modelcontextprotocol.io)
13
+
14
+ </div>
15
+
16
+ An [MCP](https://modelcontextprotocol.io) server that keeps one CODESYS session
17
+ open in the background and gives Claude, or any MCP client, direct access to
18
+ it. Claude works on your real project with the real CODESYS compiler, and you
19
+ can watch the code change as it happens.
20
+
21
+ ![The live workspace, showing a sample project with a change waiting for review](docs/images/workspace.png)
22
+
23
+ ## Highlights
24
+
25
+ - **Edits your real project.** POUs, methods, properties, interfaces, DUTs and
26
+ GVLs, as Structured Text.
27
+ - **Compiles with the real compiler.** Errors come back with object, line and
28
+ code, including in code that no task calls yet.
29
+ - **Tests in simulation.** Run, step exact cycles, read, write and force values
30
+ in CODESYS's own simulation, with nothing else to install.
31
+ - **Understands the project.** Outlines, I/O lists, the device tree and library
32
+ interfaces, without reading every object.
33
+ - **Shows its work.** A live panel with the project tree, the code as it is
34
+ written, compile results, and Accept or Revert for each change.
35
+ - **Never touches a real controller.** The only online connection is to
36
+ CODESYS's own simulation, and every online call checks for it first.
37
+
38
+ ## Quick start
39
+
40
+ ### Requirements
41
+
42
+ - Windows with **CODESYS V3.5** and its **Scripting** add-on. Tested with SP21
43
+ and SP22 on Scripting 4.2, and SP21 on 4.1. SP16 and later should work.
44
+ - **Node.js 20** or later.
45
+
46
+ Schneider EcoStruxure Machine Expert isn't supported yet. If you have it, the
47
+ [testing notes](docs/machine-expert-testing.md) help find out what works.
48
+
49
+ ### Install
50
+
51
+ ```bash
52
+ npm install -g @codesys/mcp-toolkit
53
+ ```
54
+
55
+ Coming from version 1? The tools have changed: see
56
+ [Migrating from v1](#migrating-from-v1).
57
+
58
+ ### Connect it to Claude Desktop
59
+
60
+ Add the server to `%APPDATA%\Claude\claude_desktop_config.json`, then restart
61
+ Claude Desktop:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "codesys": {
67
+ "command": "codesys-mcp-tool",
68
+ "args": [
69
+ "--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
70
+ "--codesys-profile", "CODESYS V3.5 SP21",
71
+ "--workspace", "C:\\PLC\\Projects"
72
+ ]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ `--codesys-path` and `--codesys-profile` are optional. Without them the server
79
+ uses the newest CODESYS install under Program Files and its newest profile.
80
+
81
+ ### Try it
82
+
83
+ > Create a project called `BlinkDemo.project`. Add a function block that toggles
84
+ > an output once per second, call it from `PLC_PRG`, compile it, and prove in
85
+ > simulation that the output toggles.
86
+
87
+ CODESYS starts in the background when the server launches. A cold start takes
88
+ 1 to 3 minutes; later starts take about 10 seconds.
89
+
90
+ Claude Desktop asks for approval per tool. Choose **Always allow** the first
91
+ time it asks about `codesys`, and once more for `open_sim_panel`. After that it
92
+ doesn't ask again.
93
+
94
+ ## What Claude can do
95
+
96
+ Everything goes through one MCP tool, `codesys`, with an action for each
97
+ operation. [The reference](docs/reference.md) describes each one.
98
+
99
+ | Area | Actions |
100
+ | --- | --- |
101
+ | **Projects** | `open_project` `create_project` `save_project` `close_project` `codesys_status` |
102
+ | **Read code** | `get_project_tree` `read_objects` `search_code` `get_outline` |
103
+ | **Change code** | `write_object` `create_object` `delete_object` `rename_object` |
104
+ | **Build** | `compile` |
105
+ | **Libraries** | `list_libraries` `add_library` `remove_library` `get_library_api` |
106
+ | **Tasks and I/O** | `list_tasks` `configure_task` `get_io_list` `get_devices` |
107
+ | **Change tracking** | `checkpoint` `get_changes` `revert_object` |
108
+ | **Simulation** | `codesys_sim_start` `codesys_sim_stop` `codesys_sim_status` `codesys_sim_control` `codesys_sim_cycle` `codesys_sim_read` `codesys_sim_write` `codesys_sim_force` `codesys_sim_unforce` |
109
+ | **Helpers** | `batch` `wait` `help` `open_workspace` |
110
+
111
+ `batch` runs up to 50 actions in one call, which is much faster than separate
112
+ calls.
113
+
114
+ Version 2 edits Structured Text only. Objects with graphical bodies (SFC, CFC,
115
+ LD, FBD) appear in the tree and can be read as PLCopenXML, but not edited.
116
+
117
+ ## Live panel
118
+
119
+ Right after opening or creating a project, Claude opens the panel as a card in
120
+ the chat. It shows the status, the object Claude is working on and how many
121
+ objects changed. **Open workspace** expands it into a CODESYS-style view with
122
+ the project tree, editor tabs, compile problems and a review list.
123
+
124
+ From the panel you can **Accept** or **Revert** each change, compare it with
125
+ the version you last reviewed, and **Pause Claude** while you look around.
126
+
127
+ The card needs a client that supports
128
+ [MCP Apps](https://modelcontextprotocol.io/extensions/apps). In our testing
129
+ Claude Desktop draws it in the Code tab but not in a normal chat
130
+ ([anthropics/claude-ai-mcp#1243](https://github.com/anthropics/claude-ai-mcp/issues/1243)).
131
+ Ask Claude to open the workspace in your browser if you'd rather have it there;
132
+ the browser never opens by itself.
133
+
134
+ [More about the panel](docs/reference.md#live-panel)
135
+
136
+ ## Safety
137
+
138
+ - **No real controller.** The toolkit doesn't log in or download to a
139
+ controller. It goes online only to CODESYS's own simulation, and every online
140
+ call first checks that simulation mode is on.
141
+ - **One approval covers every action.** That includes the actions that change
142
+ or delete project code, so work on a copy of any project you can't afford to
143
+ lose, or keep it in version control.
144
+ - **Changes can be undone.** A checkpoint is taken when a project is opened, and
145
+ `revert_object` puts an object back to it, including its methods and
146
+ properties.
147
+ - **Saved as it goes.** The project is saved after every change by default. Use
148
+ `--no-autosave` to save only on request.
149
+ - **The simulation can't change your project.** Editing is refused while it
150
+ runs, and the simulation setting on the device is never saved.
151
+
152
+ ## Configuration
153
+
154
+ | Option | Meaning |
155
+ | --- | --- |
156
+ | `--codesys-path <exe>` | `CODESYS.exe` to run |
157
+ | `--codesys-profile <name>` | Profile, e.g. `"CODESYS V3.5 SP21"` (file name in `CODESYS\Profiles`, without `.profile.xml`). A name that isn't installed is refused at start, with the installed ones listed |
158
+ | `--codesys-additional-folder <dir>` | CODESYS's add-on folder, passed as `--additionalfolder`. SP22 installs add-ons such as Scripting there; the toolkit finds `CODESYS\AdditionalFolders\Default` by itself |
159
+ | `--workspace <dir>` | Base folder for relative project paths (default: the working directory). Created if missing |
160
+ | `--project <file>` | Open this project as soon as CODESYS is up |
161
+ | `--session-dir <dir>` | Where session folders go (default: `%TEMP%\codesys-mcp-toolkit`) |
162
+ | `--no-autostart` | Start CODESYS on the first tool call instead of at launch |
163
+ | `--no-autosave` | Don't save after every change; use `save_project` |
164
+ | `--viewer-port <port>` | Port of the browser workspace on 127.0.0.1 (default 5180, or the next free one) |
165
+ | `--no-viewer` | Don't serve the browser workspace |
166
+ | `--no-shared` | Run CODESYS in this process instead of the shared session |
167
+
168
+ ### Several CODESYS versions
169
+
170
+ To keep SP21 and SP22 side by side, add one server entry per version, each with
171
+ its own `--codesys-path` and `--codesys-profile`.
172
+
173
+ The toolkit never updates a project's compiler or library versions, and
174
+ `open_project` says when they differ from the running CODESYS. A project that
175
+ needs a newer compiler opens, but can't compile until you open it in that
176
+ version. Each version keeps its own devices and libraries, so a project from an
177
+ older CODESYS may report missing libraries until you update it in the CODESYS
178
+ window.
179
+
180
+ ### Shared session
181
+
182
+ Claude Desktop may start the server more than once. All processes started with
183
+ the same options share one background session, so only one CODESYS runs and the
184
+ panel shows the project Claude is working on. The session stops two minutes
185
+ after the last client disconnects. Its log is `daemon-<id>.log` in
186
+ `%LOCALAPPDATA%\codesys-mcp-toolkit`.
187
+
188
+ ## How it works
189
+
190
+ ```
191
+ MCP client ── stdio ──▶ codesys-mcp-tool (Node.js)
192
+ │ requests/<id>.json ▶ operation name + JSON arguments
193
+ │ ◀ results/<id>.json
194
+ │ ◀ heartbeat.json idle or busy, with a deadline
195
+ ▼
196
+ CODESYS.exe --noUI --runscript=watcher.py
197
+ └─ fixed operation handlers (IronPython 2.7)
198
+ ```
199
+
200
+ - **No generated code.** Requests are an operation name plus JSON arguments,
201
+ handled by fixed functions in `watcher/mcp_ops.py`.
202
+ - **Hang detection.** The watcher reports `busy` with a deadline. If CODESYS
203
+ blocks past it, the server kills it and starts a fresh one on the next call.
204
+ - **No orphans.** The watcher exits when the server process goes away.
205
+ - **Modal dialogs** are suppressed and logged, so they can't freeze CODESYS.
206
+ - **UTF-8** from end to end.
207
+ - **Localised names.** Object kinds come from CODESYS type GUIDs, not names, so
208
+ a German task folder called `Taskkonfiguration` works.
209
+
210
+ ## Troubleshooting
211
+
212
+ | Message | What to do |
213
+ | --- | --- |
214
+ | **CODESYS is still starting** | The first start after boot can take minutes. Check `codesys_status`, then try again. |
215
+ | **`startup_failed`** or **`startup_timeout`** | Check the profile name and that CODESYS starts by hand. The error includes the last lines of `watcher.log`. |
216
+ | **Library errors after `create_project`** ([#4](https://github.com/johannesPettersson80/codesys-mcp-toolkit/issues/4)) | The default template is brought up to the running CODESYS, so its libraries should resolve. A template you pass yourself is used as it is. If libraries are still missing, install them with the CODESYS Library Repository. |
217
+ | **`hung`** or **`timeout`** | CODESYS was restarted. Re-open the project. |
218
+ | **`dialog`** | CODESYS opened a dialog, named in the error, that a session without a window can't answer. CODESYS was stopped and restarts on the next call. Do the same step once in the CODESYS window, answer the dialog there, and try again. |
219
+
220
+ Each session folder keeps a `watcher.log`.
221
+
222
+ ## Development
223
+
224
+ ```bash
225
+ npm install
226
+ npm test # unit, bridge and tool tests (no CODESYS needed)
227
+ CODESYS_LIVE=1 npx vitest run test/live # end-to-end against your CODESYS
228
+ npm run build # server and the single-file panel
229
+ ```
230
+
231
+ The normal tests run the real watcher under CPython 3 against a fake
232
+ `scriptengine` (`test/fake/`). Watcher code must stay valid IronPython 2.7:
233
+ ASCII-only, no f-strings.
234
+
235
+ To work on the panel without Claude Desktop, run the dev host, then open
236
+ http://localhost:5199:
237
+
238
+ ```bash
239
+ npm run build:panel && node scripts/dev-host/run.mjs 5199
240
+ ```
241
+
242
+ See [CONTRIBUTING.md](CONTRIBUTING.md) to get involved.
243
+
244
+ ## Migrating from v1
245
+
246
+ - Everything is now an action of the one `codesys` tool.
247
+ - Actions don't take `projectFilePath`. Call `open_project` once.
248
+ - `create_pou`, `create_method` and `create_property` are now `create_object`
249
+ with a `kind`.
250
+ - `set_pou_code` is now `write_object`.
251
+ - `compile_project` is now `compile`, and it returns the errors.
252
+ - Read access is through actions (`read_objects`, `get_project_tree`) instead of
253
+ resources, so it works in Claude Desktop
254
+ ([#6](https://github.com/johannesPettersson80/codesys-mcp-toolkit/issues/6)).
255
+
256
+ ## Acknowledgements
257
+
258
+ The persistent-watcher design borrows ideas from
259
+ [zbest1000/mcptoolkit-for-codesys](https://github.com/zbest1000/mcptoolkit-for-codesys)
260
+ (op-handler registry, heartbeat, prompt handling) and
261
+ [phobicdotno/Codesys-MCP-Master](https://github.com/phobicdotno/Codesys-MCP-Master)
262
+ (file-queue IPC, version-safe opening). Both are MIT-licensed.
263
+
264
+ ## License
265
+
266
+ [MIT](LICENSE)
267
+
268
+ CODESYS is a trademark of CODESYS GmbH. This project is independent and isn't
269
+ affiliated with or endorsed by CODESYS GmbH.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Every CODESYS action behind one MCP tool, `codesys`.
3
+ *
4
+ * Claude Desktop asks for approval per tool, read-only or not, and remembers
5
+ * "Always allow" per tool. With one tool the user presses once. That press
6
+ * also covers writes and deletes, so the tool declares itself destructive.
7
+ */
8
+ import type { McpServer } from "@modelcontextprotocol/server";
9
+ import * as z from "zod/v4";
10
+ export declare const TOOL_NAME = "codesys";
11
+ export type ActionResult = {
12
+ content: {
13
+ type: "text";
14
+ text: string;
15
+ }[];
16
+ isError?: boolean;
17
+ };
18
+ export interface ActionConfig<S extends z.ZodObject = z.ZodObject> {
19
+ description: string;
20
+ inputSchema: S;
21
+ }
22
+ export interface Action extends ActionConfig {
23
+ run: (args: unknown) => Promise<ActionResult>;
24
+ }
25
+ export type RegisterAction = <S extends z.ZodObject>(name: string, config: ActionConfig<S>, run: (args: z.infer<S>) => ActionResult | Promise<ActionResult>) => void;
26
+ /** One action's description and arguments, as the model reads them. */
27
+ export declare function usage(name: string, a: ActionConfig): string;
28
+ export declare function registerActionTool(server: McpServer, actions: Map<string, Action>): void;
@@ -0,0 +1,98 @@
1
+ import * as z from "zod/v4";
2
+ export const TOOL_NAME = "codesys";
3
+ const fields = (s) => {
4
+ const required = new Set(s.required ?? []);
5
+ return Object.entries(s.properties ?? {}).map(([k, p]) => [k, p, `${k}${required.has(k) ? "" : "?"}`]);
6
+ };
7
+ function typeName(s) {
8
+ if (s.enum)
9
+ return s.enum.map((v) => JSON.stringify(v)).join(" | ");
10
+ if (s.const !== undefined)
11
+ return JSON.stringify(s.const);
12
+ if (s.anyOf)
13
+ return s.anyOf.map(typeName).join(" | ");
14
+ if (Array.isArray(s.type))
15
+ return s.type.join(" | ");
16
+ if (s.type === "array")
17
+ return `${s.items ? typeName(s.items) : "any"}[]`;
18
+ if (s.type === "object" && s.properties)
19
+ return `{ ${fields(s).map(([, p, label]) => `${label}: ${typeName(p)}`).join(", ")} }`;
20
+ if (s.type === "object" && typeof s.additionalProperties === "object")
21
+ return `{ [name]: ${typeName(s.additionalProperties)} }`;
22
+ return s.type ?? "any";
23
+ }
24
+ /** One action's description and arguments, as the model reads them. */
25
+ export function usage(name, a) {
26
+ const s = z.toJSONSchema(a.inputSchema, { io: "input" });
27
+ const params = fields(s).map(([, p, label]) => ` ${label}: ${typeName(p)}${p.description ? ` - ${p.description}` : ""}`);
28
+ return [`${name}: ${a.description}`, ...params].join("\n");
29
+ }
30
+ const HOW_TO_CALL = `Everything this toolkit does in CODESYS V3.5. Call it as {"action": "<name>", "args": {...}}, with the action's arguments in args. Leave args out when the action takes none. Example: {"action": "read_objects", "args": {"paths": ["PLC_PRG"]}}. Use batch to send several actions in one call: it is much faster than separate calls. Some actions change or delete project code. {"action": "help"} returns this list, and {"action": "help", "args": {"action": "write_object"}} one action, for when a client shortens this text.`;
31
+ const MAX_BATCH = 50;
32
+ const reply = (text, isError = false) => ({ content: [{ type: "text", text }], ...(isError ? { isError: true } : {}) });
33
+ // Strict: nothing else checks the arguments, and a misspelt optional one must not be dropped silently.
34
+ const checkArgs = (a, args) => a.inputSchema.strict().safeParse(args ?? {});
35
+ const batchInput = z.object({
36
+ calls: z.array(z.object({ action: z.string(), args: z.record(z.string(), z.unknown()).optional() })).min(1).max(MAX_BATCH).describe(`The steps, in order, at most ${MAX_BATCH}`),
37
+ continueOnError: z.boolean().optional().describe("Run the remaining steps after one fails (default false)"),
38
+ });
39
+ /** Most of a session's time is the round trip between calls, not CODESYS: this saves those. */
40
+ function batchAction(actions) {
41
+ return {
42
+ description: "Run several actions in one call, in order, and get all their results back. Use this whenever you already know the next steps, for example several create_object or write_object calls and then compile, or a simulation step (codesys_sim_force, wait, codesys_sim_cycle, codesys_sim_read). Every step is checked first, and nothing runs if one is invalid. It stops at the first step that fails, unless continueOnError is true. A batch can't contain help or another batch.",
43
+ inputSchema: batchInput,
44
+ run: async (raw) => {
45
+ const { calls, continueOnError } = raw;
46
+ const steps = [];
47
+ for (const [i, c] of calls.entries()) {
48
+ const action = actions.get(c.action);
49
+ if (!action)
50
+ return reply(`Step ${i + 1}: '${c.action}' can't run in a batch, so nothing was run. Actions: ${[...actions.keys()].join(", ")}.`, true);
51
+ const parsed = checkArgs(action, c.args);
52
+ if (!parsed.success)
53
+ return reply(`Step ${i + 1} (${c.action}) has invalid arguments, so nothing was run:\n${z.prettifyError(parsed.error)}\n\n${usage(c.action, action)}`, true);
54
+ steps.push({ name: c.action, action, args: parsed.data });
55
+ }
56
+ const out = [];
57
+ let failed = false;
58
+ for (const [i, s] of steps.entries()) {
59
+ const r = await s.action.run(s.args);
60
+ out.push(`[${i + 1}/${steps.length}] ${s.name}${r.isError ? " FAILED" : ""}:\n${r.content.map((c) => c.text).join("\n")}`);
61
+ failed ||= !!r.isError;
62
+ if (r.isError && !continueOnError) {
63
+ if (i + 1 < steps.length)
64
+ out.push(`Stopped: the remaining ${steps.length - i - 1} step(s) were not run.`);
65
+ break;
66
+ }
67
+ }
68
+ return reply(out.join("\n\n"), failed);
69
+ },
70
+ };
71
+ }
72
+ export function registerActionTool(server, actions) {
73
+ const all = new Map([["batch", batchAction(actions)], ...actions]);
74
+ const names = [...all.keys()];
75
+ const catalog = names.map((n) => usage(n, all.get(n))).join("\n\n");
76
+ server.registerTool(TOOL_NAME, {
77
+ description: `${HOW_TO_CALL}\n\nActions:\n\n${catalog}`,
78
+ inputSchema: z.object({
79
+ action: z.enum(["help", ...names]).describe("The action to run"),
80
+ args: z.record(z.string(), z.unknown()).optional().describe("The action's arguments, as listed for it in this tool's description"),
81
+ }),
82
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
83
+ }, async ({ action, args }) => {
84
+ if (action === "help") {
85
+ if (args?.action === undefined)
86
+ return reply(catalog);
87
+ const wanted = String(args.action);
88
+ const known = all.get(wanted);
89
+ return known ? reply(usage(wanted, known)) : reply(`Unknown action '${wanted}'. Actions: ${names.join(", ")}.`, true);
90
+ }
91
+ const a = all.get(action);
92
+ const parsed = checkArgs(a, args);
93
+ if (!parsed.success)
94
+ return reply(`Invalid arguments for ${action}:\n${z.prettifyError(parsed.error)}\n\n${usage(action, a)}`, true);
95
+ return a.run(parsed.data);
96
+ });
97
+ }
98
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}