@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.
- package/CHANGELOG.md +153 -189
- package/LICENSE +21 -21
- package/README.md +269 -152
- package/dist/actions.d.ts +28 -0
- package/dist/actions.js +98 -0
- package/dist/actions.js.map +1 -0
- package/dist/bridge/bridge.d.ts +105 -0
- package/dist/bridge/bridge.js +400 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/codesys/cache.d.ts +48 -0
- package/dist/codesys/cache.js +82 -0
- package/dist/codesys/cache.js.map +1 -0
- package/dist/codesys/changes.d.ts +57 -0
- package/dist/codesys/changes.js +133 -0
- package/dist/codesys/changes.js.map +1 -0
- package/dist/codesys/decl.d.ts +45 -0
- package/dist/codesys/decl.js +251 -0
- package/dist/codesys/decl.js.map +1 -0
- package/dist/codesys/devices.d.ts +34 -0
- package/dist/codesys/devices.js +82 -0
- package/dist/codesys/devices.js.map +1 -0
- package/dist/codesys/diff.d.ts +13 -0
- package/dist/codesys/diff.js +61 -0
- package/dist/codesys/diff.js.map +1 -0
- package/dist/codesys/install.d.ts +40 -0
- package/dist/codesys/install.js +144 -0
- package/dist/codesys/install.js.map +1 -0
- package/dist/codesys/library-browse.d.ts +19 -0
- package/dist/codesys/library-browse.js +57 -0
- package/dist/codesys/library-browse.js.map +1 -0
- package/dist/codesys/outline.d.ts +32 -0
- package/dist/codesys/outline.js +219 -0
- package/dist/codesys/outline.js.map +1 -0
- package/dist/codesys/simulation.d.ts +34 -0
- package/dist/codesys/simulation.js +57 -0
- package/dist/codesys/simulation.js.map +1 -0
- package/dist/codesys/st.d.ts +36 -0
- package/dist/codesys/st.js +69 -0
- package/dist/codesys/st.js.map +1 -0
- package/dist/daemon.d.ts +46 -0
- package/dist/daemon.js +173 -0
- package/dist/daemon.js.map +1 -0
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +33 -0
- package/dist/driver.js.map +1 -0
- package/dist/panel/sim-panel.html +357 -0
- package/dist/panel/viewer.html +126 -0
- package/dist/panel-types.d.ts +20 -0
- package/dist/panel-types.js +3 -0
- package/dist/panel-types.js.map +1 -0
- package/dist/panel.d.ts +27 -0
- package/dist/panel.js +208 -0
- package/dist/panel.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +33 -974
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +103 -0
- package/dist/tools.js +793 -0
- package/dist/tools.js.map +1 -0
- package/dist/viewer.d.ts +14 -0
- package/dist/viewer.js +125 -0
- package/dist/viewer.js.map +1 -0
- package/package.json +66 -51
- package/watcher/mcp_compat.py +147 -0
- package/watcher/mcp_ops.py +1178 -0
- package/watcher/mcp_sim.py +246 -0
- package/watcher/watcher.py +255 -0
- package/.claude/settings.local.json +0 -29
- package/CLAUDE.md +0 -26
- package/CONTRIBUTING.md +0 -72
- package/README.md.enhanced +0 -155
- package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
- package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.8.tgz +0 -0
- package/dist/bin.js +0 -75
- package/dist/check_session_paths.js +0 -149
- package/dist/codesys_interop.js +0 -435
- package/dist/direct_session_command.js +0 -293
- package/dist/scriptLoader.js +0 -165
- package/dist/scripts/check_status.py +0 -21
- package/dist/scripts/compile_project.py +0 -65
- package/dist/scripts/create_method.py +0 -80
- package/dist/scripts/create_pou.py +0 -102
- package/dist/scripts/create_project.py +0 -116
- package/dist/scripts/create_property.py +0 -78
- package/dist/scripts/ensure_project_open.py +0 -154
- package/dist/scripts/find_object_by_path.py +0 -111
- package/dist/scripts/get_pou_code.py +0 -91
- package/dist/scripts/get_project_structure.py +0 -72
- package/dist/scripts/open_project.py +0 -25
- package/dist/scripts/save_project.py +0 -29
- package/dist/scripts/session_status.py +0 -281
- package/dist/scripts/set_pou_code.py +0 -109
- package/dist/scripts/start_session.py +0 -809
- package/dist/scripts/stop_session.py +0 -324
- package/dist/sessionManager.js +0 -798
- package/docs/configuration.md +0 -141
- package/docs/index.md +0 -26
- package/docs/installation.md +0 -84
- package/docs/session_management.md +0 -110
- package/examples/README.md +0 -78
- package/examples/sample_config.json +0 -32
- package/replacements_session_status.txt +0 -16
- package/scriptDoc.txt +0 -8679
- package/scripts/build.js +0 -25
- package/scripts/fix_fstrings.py +0 -76
package/README.md
CHANGED
|
@@ -1,152 +1,269 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- **
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
- The
|
|
150
|
-
|
|
151
|
-
|
|
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)
|
|
8
|
+
[](https://www.npmjs.com/package/@codesys/mcp-toolkit)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
[](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
|
+

|
|
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;
|
package/dist/actions.js
ADDED
|
@@ -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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"}
|