@codesys/mcp-toolkit 1.5.7 → 2.0.0-alpha.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 (109) hide show
  1. package/CHANGELOG.md +153 -183
  2. package/LICENSE +21 -21
  3. package/README.md +224 -107
  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 -941
  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 +37 -21
  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/dist/bin.js +0 -75
  79. package/dist/check_session_paths.js +0 -149
  80. package/dist/codesys_interop.js +0 -435
  81. package/dist/direct_session_command.js +0 -293
  82. package/dist/scriptLoader.js +0 -165
  83. package/dist/scripts/check_status.py +0 -21
  84. package/dist/scripts/compile_project.py +0 -65
  85. package/dist/scripts/create_method.py +0 -80
  86. package/dist/scripts/create_pou.py +0 -102
  87. package/dist/scripts/create_project.py +0 -116
  88. package/dist/scripts/create_property.py +0 -78
  89. package/dist/scripts/ensure_project_open.py +0 -154
  90. package/dist/scripts/find_object_by_path.py +0 -111
  91. package/dist/scripts/get_pou_code.py +0 -91
  92. package/dist/scripts/get_project_structure.py +0 -72
  93. package/dist/scripts/open_project.py +0 -25
  94. package/dist/scripts/save_project.py +0 -29
  95. package/dist/scripts/session_status.py +0 -281
  96. package/dist/scripts/set_pou_code.py +0 -109
  97. package/dist/scripts/start_session.py +0 -809
  98. package/dist/scripts/stop_session.py +0 -324
  99. package/dist/sessionManager.js +0 -798
  100. package/docs/configuration.md +0 -141
  101. package/docs/index.md +0 -26
  102. package/docs/installation.md +0 -84
  103. package/docs/session_management.md +0 -110
  104. package/examples/README.md +0 -78
  105. package/examples/sample_config.json +0 -32
  106. package/replacements_session_status.txt +0 -16
  107. package/scriptDoc.txt +0 -8679
  108. package/scripts/build.js +0 -25
  109. package/scripts/fix_fstrings.py +0 -76
package/README.md CHANGED
@@ -1,152 +1,269 @@
1
+ <div align="center">
1
2
 
2
- # @codesys/mcp-toolkit
3
+ # CODESYS MCP Toolkit
3
4
 
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)
5
+ **Let Claude read, write, compile and simulate Structured Text in your CODESYS V3.5 projects.**
7
6
 
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.
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
+ ![Status: 2.0 alpha](https://img.shields.io/badge/status-2.0%20alpha-orange)
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)
9
13
 
10
- ## 🌟 Features
14
+ </div>
11
15
 
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
+ 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.
16
20
 
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`)
21
+ ![The live workspace, showing a sample project with a change waiting for review](docs/images/workspace.png)
23
22
 
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
23
+ ## Highlights
30
24
 
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.
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.
35
37
 
36
- ## 📋 Prerequisites
38
+ ## Quick start
37
39
 
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).
40
+ ### Requirements
41
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.)*
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.
43
45
 
44
- ## 🚀 Installation
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.
45
48
 
46
- The recommended way to install is globally using npm:
49
+ ### Install
47
50
 
48
51
  ```bash
49
- npm install -g @codesys/mcp-toolkit
52
+ npm install -g @codesys/mcp-toolkit@next
50
53
  ```
51
54
 
52
- This installs the package globally, making the `codesys-mcp-tool` command available in your system's terminal PATH.
55
+ Version 2 is in alpha, so it's published under the `next` tag. Without `@next`,
56
+ npm installs version 1.
53
57
 
54
- *(Advanced users can also install from source for development - see CONTRIBUTING.md if available).*
58
+ ### Connect it to Claude Desktop
55
59
 
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`):**
60
+ Add the server to `%APPDATA%\Claude\claude_desktop_config.json`, then restart
61
+ Claude Desktop:
65
62
 
66
63
  ```json
67
64
  {
68
65
  "mcpServers": {
69
- // ... other servers ...
70
- "codesys_local": {
71
- "command": "codesys-mcp-tool", // <<< Use the direct command name
66
+ "codesys": {
67
+ "command": "codesys-mcp-tool",
72
68
  "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
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"
77
72
  ]
78
73
  }
79
- // ... other servers ...
80
74
  }
81
75
  }
82
76
  ```
83
77
 
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.
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
89
189
 
90
- ### Alternative Configuration (Using `npx` - Not Recommended)
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
+ ```
91
199
 
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`:
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.
93
209
 
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.)*
210
+ ## Troubleshooting
112
211
 
113
- ## 🛠️ Command-Line Arguments
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. |
114
219
 
115
- When running `codesys-mcp-tool` directly or configuring it, you can use these arguments:
220
+ Each session folder keeps a `watcher.log`.
116
221
 
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.
222
+ ## Development
122
223
 
123
- ## 🔍 Troubleshooting
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
+ ```
124
241
 
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.
242
+ See [CONTRIBUTING.md](CONTRIBUTING.md) to get involved.
128
243
 
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).
244
+ ## Migrating from v1
134
245
 
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.
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)).
138
255
 
139
- * **Check Logs:**
140
- * Claude Desktop logs: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\` (Windows)
256
+ ## Acknowledgements
141
257
 
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).
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.
144
263
 
145
- ## 📝 License
146
- This project is licensed under the MIT License - see the LICENSE file for details.
264
+ ## License
147
265
 
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.
266
+ [MIT](LICENSE)
152
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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"}
@@ -0,0 +1,105 @@
1
+ export interface BridgeOptions {
2
+ /** Executable: CODESYS.exe in production, python in tests. */
3
+ command: string;
4
+ /** Full argument list; when omitted, CODESYS arguments are built from profile/noUI. */
5
+ args?: (scriptPath: string) => string[];
6
+ profile?: string;
7
+ /** CODESYS's additional folder (SP22 keeps its add-ons there, e.g. Scripting). */
8
+ additionalFolder?: string;
9
+ /** Run CODESYS without UI (default true). */
10
+ noUI?: boolean;
11
+ /** Folder that holds watcher.py and its modules (copied into the session). */
12
+ watcherSource: string;
13
+ /** Base folder for session folders. */
14
+ sessionRoot: string;
15
+ /** Extra environment (tests point PYTHONPATH at a fake scriptengine). */
16
+ env?: Record<string, string>;
17
+ startupTimeoutMs?: number;
18
+ defaultTimeoutMs?: number;
19
+ /** Extra time after a busy op's deadline before the watcher counts as hung. */
20
+ hungGraceMs?: number;
21
+ /** An idle heartbeat older than this means the watcher is hung. */
22
+ staleHeartbeatMs?: number;
23
+ log?: (msg: string) => void;
24
+ }
25
+ export type BridgeState = "stopped" | "starting" | "ready" | "dead";
26
+ export interface Heartbeat {
27
+ pid: number;
28
+ ts: number;
29
+ state: "idle" | "busy" | "stopped" | "crashed";
30
+ op?: string | null;
31
+ id?: string | null;
32
+ deadline?: number | null;
33
+ count?: number;
34
+ }
35
+ export interface BridgeHealth {
36
+ state: BridgeState;
37
+ pid?: number;
38
+ sessionDir?: string;
39
+ startedAt?: string;
40
+ readyAfterMs?: number;
41
+ heartbeat?: Heartbeat & {
42
+ ageMs: number;
43
+ };
44
+ verdict: "healthy" | "busy" | "hung" | "dead" | "starting" | "stopped";
45
+ /** Version of the CODESYS Scripting add-on, once CODESYS has started. */
46
+ scripting?: string;
47
+ calls: number;
48
+ lastError?: string;
49
+ restarts: number;
50
+ }
51
+ export declare class BridgeError extends Error {
52
+ code: string;
53
+ detail?: string | undefined;
54
+ constructor(code: string, message: string, detail?: string | undefined);
55
+ }
56
+ /** What to do about a start-up failure CODESYS explains in its output. */
57
+ export declare function startupHint(output: string): string;
58
+ export declare class CodesysBridge {
59
+ private opts;
60
+ private child?;
61
+ private state;
62
+ private startPromise?;
63
+ private sessionDir?;
64
+ private queue;
65
+ private seq;
66
+ private calls;
67
+ private restarts;
68
+ private startedAt?;
69
+ private readyAt?;
70
+ private scripting?;
71
+ private lastError?;
72
+ private exitInfo?;
73
+ /** The end of CODESYS's own console output (for start-up errors). */
74
+ private output;
75
+ constructor(options: BridgeOptions);
76
+ get currentState(): BridgeState;
77
+ /** Start CODESYS + watcher (idempotent). Resolves when the watcher is ready. */
78
+ start(): Promise<void>;
79
+ /** Wait at most `ms` for the bridge to be ready; false if still starting. */
80
+ waitReady(ms: number): Promise<boolean>;
81
+ private doStart;
82
+ /** Run one watcher op. Calls are serialised; the watcher is single-threaded. */
83
+ call<T = unknown>(op: string, args?: Record<string, unknown>, opts?: {
84
+ timeoutMs?: number;
85
+ }): Promise<T>;
86
+ private exchange;
87
+ readHeartbeat(): (Heartbeat & {
88
+ ageMs: number;
89
+ }) | undefined;
90
+ /** The end of what the program printed, for error messages. */
91
+ private consoleTail;
92
+ /** The title of a dialog the watcher found CODESYS waiting on (dialog.json). */
93
+ private pendingDialog;
94
+ private verdict;
95
+ /** Snapshot that never waits on the watcher. */
96
+ health(): BridgeHealth;
97
+ /** Ask the watcher to exit, then make sure the process is gone. */
98
+ stop(): Promise<void>;
99
+ private kill;
100
+ /** Last lines of watcher.log, for error messages. */
101
+ logTail(lines?: number): string;
102
+ private log;
103
+ }
104
+ /** Remove session folders older than `maxAgeMs` (our own folders only). */
105
+ export declare function pruneSessions(root: string, maxAgeMs?: number): Promise<void>;