@codesys/mcp-toolkit 2.6.0 → 2.8.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 +53 -0
- package/README.md +11 -8
- package/README.zh-CN.md +7 -6
- package/dist/codesys/compare.d.ts +17 -0
- package/dist/codesys/compare.js +105 -0
- package/dist/codesys/compare.js.map +1 -0
- package/dist/codesys/debug.d.ts +2 -0
- package/dist/codesys/debug.js +96 -2
- package/dist/codesys/debug.js.map +1 -1
- package/dist/codesys/graphical-read.d.ts +7 -4
- package/dist/codesys/graphical-read.js +103 -3
- package/dist/codesys/graphical-read.js.map +1 -1
- package/dist/codesys/graphical.d.ts +51 -4
- package/dist/codesys/graphical.js +247 -44
- package/dist/codesys/graphical.js.map +1 -1
- package/dist/codesys/recipes.d.ts +45 -0
- package/dist/codesys/recipes.js +130 -0
- package/dist/codesys/recipes.js.map +1 -0
- package/dist/hmi/native.d.ts +1 -1
- package/dist/hmi/native.js +20 -1
- package/dist/hmi/native.js.map +1 -1
- package/dist/hmi/render.d.ts +4 -0
- package/dist/hmi/render.js +13 -1
- package/dist/hmi/render.js.map +1 -1
- package/dist/hmi/service.d.ts +3 -0
- package/dist/hmi/service.js +29 -2
- package/dist/hmi/service.js.map +1 -1
- package/dist/hmi/spec.d.ts +18 -0
- package/dist/hmi/spec.js +45 -0
- package/dist/hmi/spec.js.map +1 -1
- package/dist/hmi/tools.js +50 -5
- package/dist/hmi/tools.js.map +1 -1
- package/dist/hmi/value.js +3 -0
- package/dist/hmi/value.js.map +1 -1
- package/dist/panel/hmi.js +66 -61
- package/dist/panel.js +1 -1
- package/dist/panel.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +100 -13
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/watcher/mcp_alarms.py +42 -0
- package/watcher/mcp_caps.py +66 -0
- package/watcher/mcp_debug.py +54 -2
- package/watcher/mcp_lang.py +13 -1
- package/watcher/watcher.py +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codesys/mcp-toolkit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "MCP server for CODESYS V3.5: read, write, compile and simulate Structured Text through a persistent CODESYS session",
|
|
5
5
|
"mcpName": "io.github.johannesPettersson80/codesys-mcp-toolkit",
|
|
6
6
|
"type": "module",
|
package/watcher/mcp_alarms.py
CHANGED
|
@@ -151,6 +151,47 @@ def op_alarms_list(args):
|
|
|
151
151
|
return {"groups": groups}
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def op_textlists_translate(args):
|
|
155
|
+
"""Translations into CODESYS's text lists (documented text-list scripting): the
|
|
156
|
+
GlobalTextList, where a visualization looks up its static texts by the text itself,
|
|
157
|
+
and the HMI's alarm group list (alarm messages, matched by their default text)."""
|
|
158
|
+
c = Ctx()
|
|
159
|
+
out = {"languages": [], "rows": 0, "added": 0, "errors": []}
|
|
160
|
+
lists = []
|
|
161
|
+
names = (["GlobalTextList"] if args.get("global", True) else []) + ([args["alarm_group"]] if args.get("alarm_group") else [])
|
|
162
|
+
for name in names:
|
|
163
|
+
for tl in c.proj.find(name, True):
|
|
164
|
+
if hasattr(tl, "rows"):
|
|
165
|
+
lists.append((name, tl))
|
|
166
|
+
break
|
|
167
|
+
for lang, texts in (args.get("translations") or {}).items():
|
|
168
|
+
out["languages"].append(lang)
|
|
169
|
+
for name, tl in lists:
|
|
170
|
+
try:
|
|
171
|
+
tl.addlanguage(lang)
|
|
172
|
+
except Exception:
|
|
173
|
+
pass # it has the language already
|
|
174
|
+
rows = {}
|
|
175
|
+
for r in tl.rows:
|
|
176
|
+
rows[to_text(r.defaulttext)] = r
|
|
177
|
+
for text, tr in texts.items():
|
|
178
|
+
try:
|
|
179
|
+
r = rows.get(text)
|
|
180
|
+
if r is None:
|
|
181
|
+
if name != "GlobalTextList":
|
|
182
|
+
continue
|
|
183
|
+
r = tl.rows.add(text, text)
|
|
184
|
+
rows[text] = r
|
|
185
|
+
out["added"] += 1
|
|
186
|
+
r.setlanguagetext(lang, tr)
|
|
187
|
+
out["rows"] += 1
|
|
188
|
+
except Exception as e:
|
|
189
|
+
if len(out["errors"]) < 8:
|
|
190
|
+
out["errors"].append("%s/%s: %s" % (name, text[:30], to_text(e)[:120]))
|
|
191
|
+
out["saved"] = c.save_if(args)
|
|
192
|
+
return out
|
|
193
|
+
|
|
194
|
+
|
|
154
195
|
def op_visu_users(args):
|
|
155
196
|
"""Visualization user groups (no users: their passwords are the engineer's to set)."""
|
|
156
197
|
import System
|
|
@@ -190,3 +231,4 @@ def op_visu_users(args):
|
|
|
190
231
|
mcp_ops.OPS["alarms.configure"] = mcp_sim._locked(op_alarms_configure)
|
|
191
232
|
mcp_ops.OPS["visu.users"] = mcp_sim._locked(op_visu_users)
|
|
192
233
|
mcp_ops.OPS["alarms.list"] = op_alarms_list
|
|
234
|
+
mcp_ops.OPS["textlists.translate"] = mcp_sim._locked(op_textlists_translate)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Which optional features this CODESYS offers. Several actions rely on CODESYS's
|
|
3
|
+
# services beyond its documented scripting API, or on add-ons (visualization, alarm
|
|
4
|
+
# management). Each check reaches the service the same way its module does, so a
|
|
5
|
+
# feature reported available is one that action can use.
|
|
6
|
+
#
|
|
7
|
+
# Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
import mcp_ops
|
|
11
|
+
from mcp_ops import _safe
|
|
12
|
+
|
|
13
|
+
import scriptengine as se
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _type(name):
|
|
17
|
+
from System import AppDomain
|
|
18
|
+
for a in AppDomain.CurrentDomain.GetAssemblies():
|
|
19
|
+
try:
|
|
20
|
+
t = a.GetType(name)
|
|
21
|
+
except Exception:
|
|
22
|
+
t = None
|
|
23
|
+
if t is not None:
|
|
24
|
+
return t
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _assembly(name):
|
|
29
|
+
from System import AppDomain
|
|
30
|
+
for a in AppDomain.CurrentDomain.GetAssemblies():
|
|
31
|
+
if a.GetName().Name == name:
|
|
32
|
+
return True
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _check(fn):
|
|
37
|
+
try:
|
|
38
|
+
return bool(fn())
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def op_capabilities(args):
|
|
44
|
+
import mcp_lang
|
|
45
|
+
import mcp_debug
|
|
46
|
+
caps = {
|
|
47
|
+
"visualization": _check(lambda: hasattr(se, "VisualElementType")),
|
|
48
|
+
"signatures": _check(lambda: mcp_lang._service("LanguageModelMgr") is not None),
|
|
49
|
+
"cross_references": _check(lambda: mcp_lang._service("CrossReferenceService") is not None),
|
|
50
|
+
"debugging": _check(lambda: mcp_debug._bpm() is not None),
|
|
51
|
+
"trace": _check(lambda: hasattr(se, "trace")),
|
|
52
|
+
"alarms": _check(lambda: _assembly("AlarmConfigurationObjects.plugin")),
|
|
53
|
+
"visu_users": _check(lambda: _type("_3S.CoDeSys.VisualObject.VisuUserMgmtLibraryInserter") is not None),
|
|
54
|
+
"device_repository": _check(lambda: _safe(lambda: se.device_repository) is not None),
|
|
55
|
+
"library_manager": _check(lambda: getattr(se, "librarymanager", None) is not None),
|
|
56
|
+
"editor_window": os.environ.get("CODESYS_MCP_UI") == "1",
|
|
57
|
+
}
|
|
58
|
+
fpu = None
|
|
59
|
+
glm = getattr(se, "librarymanager", None)
|
|
60
|
+
if glm is not None:
|
|
61
|
+
fpu = _check(lambda: any((l.title or "").lower() == "floatingpointutils" for l in glm.get_all_libraries()))
|
|
62
|
+
caps["floating_point_utils"] = fpu
|
|
63
|
+
return {"capabilities": caps}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
mcp_ops.OPS["capabilities"] = op_capabilities
|
package/watcher/mcp_debug.py
CHANGED
|
@@ -83,6 +83,55 @@ def op_debug_set(args):
|
|
|
83
83
|
return {"id": key}
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def op_debug_set_data(args):
|
|
87
|
+
"""A data breakpoint: halt when a variable is written (optionally only when a condition holds)."""
|
|
88
|
+
import System
|
|
89
|
+
c = Ctx()
|
|
90
|
+
mcp_sim._online()
|
|
91
|
+
bpm = _bpm()
|
|
92
|
+
expr = args["expression"]
|
|
93
|
+
tasks = System.Array[str](args.get("tasks") or [])
|
|
94
|
+
try:
|
|
95
|
+
bp = bpm.CreateDataBreakpoint(c.app.guid, c.proj.handle, expr, int(args.get("size") or 0), tasks, _hit_always(), 0, False, args.get("condition") or "", "", "")
|
|
96
|
+
except AttributeError:
|
|
97
|
+
raise OpError("unavailable", "This CODESYS has no data breakpoints for scripts.")
|
|
98
|
+
bpm.SetBreakpoint(bp)
|
|
99
|
+
bpm.EnableBreakpoint(bp)
|
|
100
|
+
key = "data:%s" % expr
|
|
101
|
+
BPS[key] = bp
|
|
102
|
+
# With size 0 CODESYS keeps the breakpoint disabled; the server passes the variable's size.
|
|
103
|
+
return {"id": key, "enabled": bool(bp.IsEnabled)}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _call_stack(c):
|
|
107
|
+
"""The call stack while halted: each entry's POU and instance, innermost first."""
|
|
108
|
+
out = []
|
|
109
|
+
try:
|
|
110
|
+
from System.Reflection import BindingFlags
|
|
111
|
+
om = None
|
|
112
|
+
for tname in ("_3S.CoDeSys.MemoryTools.APEnvironment", "_3S.CoDeSys.VisualEditor.APEnvironment", "_3S.CoDeSys.RecipeManObject.APEnvironment"):
|
|
113
|
+
t = _find_type(tname)
|
|
114
|
+
p = t.GetProperty("OnlineMgr", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) if t is not None else None
|
|
115
|
+
if p is not None:
|
|
116
|
+
om = p.GetValue(None, None)
|
|
117
|
+
if om is not None:
|
|
118
|
+
break
|
|
119
|
+
if om is None:
|
|
120
|
+
return []
|
|
121
|
+
app = om.GetApplication(c.app.guid)
|
|
122
|
+
for e in list(app.CallStack or []):
|
|
123
|
+
# LocationInformation.EditorInstancePath: 'PLCWinNT.Application.PLC_PRG.fbCount';
|
|
124
|
+
# the task's own frames ('CallTask__MainTask', '__cycle__code__MainTask') are left out.
|
|
125
|
+
info = e.LocationInformation
|
|
126
|
+
path = to_text(info.EditorInstancePath or "")
|
|
127
|
+
if not path or path.split(".")[-1].startswith("__") or path.split(".")[-1].startswith("CallTask__"):
|
|
128
|
+
continue
|
|
129
|
+
out.append({"instance": path})
|
|
130
|
+
except Exception as ex:
|
|
131
|
+
out.append({"error": to_text(ex)[:160]})
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
86
135
|
def op_debug_clear(args):
|
|
87
136
|
mcp_sim._online()
|
|
88
137
|
bpm = _bpm()
|
|
@@ -120,6 +169,7 @@ def op_debug_status(args):
|
|
|
120
169
|
out["at"]["implementation"] = pos["implementation"]
|
|
121
170
|
except Exception as e:
|
|
122
171
|
out["at_error"] = to_text(e)
|
|
172
|
+
out["call_stack"] = _call_stack(c)
|
|
123
173
|
return out
|
|
124
174
|
|
|
125
175
|
|
|
@@ -135,14 +185,16 @@ def op_debug_step(args):
|
|
|
135
185
|
|
|
136
186
|
def op_debug_continue(args):
|
|
137
187
|
online = mcp_sim._online()
|
|
138
|
-
online.
|
|
139
|
-
|
|
188
|
+
if to_text(online.application_state).lower() != "run":
|
|
189
|
+
online.start()
|
|
190
|
+
mcp_sim._pause(200)
|
|
140
191
|
return {"state": to_text(online.application_state)}
|
|
141
192
|
|
|
142
193
|
|
|
143
194
|
mcp_ops.OPS.update({
|
|
144
195
|
"debug.positions": op_debug_positions,
|
|
145
196
|
"debug.set": op_debug_set,
|
|
197
|
+
"debug.set_data": op_debug_set_data,
|
|
146
198
|
"debug.clear": op_debug_clear,
|
|
147
199
|
"debug.status": op_debug_status,
|
|
148
200
|
"debug.step": op_debug_step,
|
package/watcher/mcp_lang.py
CHANGED
|
@@ -101,7 +101,19 @@ def _signature(lmm, c, sig):
|
|
|
101
101
|
ins = set(to_text(_get(v, "OrgName")) for v in (_get(sig, "Inputs") or []))
|
|
102
102
|
outs = set(to_text(_get(v, "OrgName")) for v in (_get(sig, "Outputs") or []))
|
|
103
103
|
variables = []
|
|
104
|
-
|
|
104
|
+
found = list(lmm.GetAllVariables(sig, c.app.guid) or [])
|
|
105
|
+
if not found:
|
|
106
|
+
# A method of a library FB: the language model keeps its variables on the signature.
|
|
107
|
+
for prop in ("All", "Variables", "AllVariables"):
|
|
108
|
+
vs = _get(sig, prop)
|
|
109
|
+
if vs:
|
|
110
|
+
try:
|
|
111
|
+
found = list(vs)
|
|
112
|
+
except Exception:
|
|
113
|
+
found = []
|
|
114
|
+
if found:
|
|
115
|
+
break
|
|
116
|
+
for v in found:
|
|
105
117
|
n = to_text(_get(v, "OrgName"))
|
|
106
118
|
variables.append(_var(v, "input" if n in ins else "output" if n in outs else "other"))
|
|
107
119
|
subs = []
|
package/watcher/watcher.py
CHANGED
|
@@ -260,6 +260,7 @@ def main(ui=False):
|
|
|
260
260
|
import mcp_debug # noqa: F401 (adds breakpoints and stepping)
|
|
261
261
|
import mcp_devices # noqa: F401 (adds device search and add)
|
|
262
262
|
import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
|
|
263
|
+
import mcp_caps # noqa: F401 (adds the capability check)
|
|
263
264
|
prompts = setup_prompts(se)
|
|
264
265
|
log(prompts)
|
|
265
266
|
if not hasattr(se, "FAKE"):
|
|
@@ -311,6 +312,7 @@ def main_attach():
|
|
|
311
312
|
import mcp_debug # noqa: F401 (adds breakpoints and stepping)
|
|
312
313
|
import mcp_devices # noqa: F401 (adds device search and add)
|
|
313
314
|
import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
|
|
315
|
+
import mcp_caps # noqa: F401 (adds the capability check)
|
|
314
316
|
prompts = setup_prompts(se)
|
|
315
317
|
log(prompts)
|
|
316
318
|
old = getattr(__builtin__, "_mcp_attach_timer", None)
|