@codesys/mcp-toolkit 2.5.0 → 2.7.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 +50 -0
- package/README.md +13 -9
- package/README.zh-CN.md +11 -6
- package/dist/codesys/device-tools.d.ts +21 -0
- package/dist/codesys/device-tools.js +60 -0
- package/dist/codesys/device-tools.js.map +1 -1
- package/dist/codesys/devices.d.ts +6 -0
- package/dist/codesys/devices.js +24 -1
- package/dist/codesys/devices.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 +14 -2
- package/dist/hmi/native.js +116 -5
- 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 +69 -15
- 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 +1 -1
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +44 -4
- 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_devices.py +158 -0
- package/watcher/mcp_lang.py +13 -1
- package/watcher/mcp_visu.py +81 -1
- package/watcher/watcher.py +2 -0
|
@@ -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_devices.py
CHANGED
|
@@ -79,5 +79,163 @@ def op_devices_add(args):
|
|
|
79
79
|
return {"path": c.path_of(dev), "saved": c.save_if(args)}
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _param_sets(dev):
|
|
83
|
+
"""(prefix, parameter set) for the device itself and each of its connectors."""
|
|
84
|
+
out = []
|
|
85
|
+
ps = _safe(lambda: dev.device_parameters)
|
|
86
|
+
if ps is not None:
|
|
87
|
+
out.append(("", ps))
|
|
88
|
+
conns = _safe(lambda: dev.connectors) or []
|
|
89
|
+
for i, con in enumerate(conns):
|
|
90
|
+
hp = _safe(lambda: con.host_parameters)
|
|
91
|
+
if hp is not None and len(hp):
|
|
92
|
+
out.append(("c%d/" % i, hp))
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _walk_elements(prefix, el, depth, out, limit):
|
|
97
|
+
"""Every data element under el, depth-first, keyed by its identifier path."""
|
|
98
|
+
if len(out) >= limit:
|
|
99
|
+
return
|
|
100
|
+
ident = to_text(_safe(lambda: el.identifier, "") or "")
|
|
101
|
+
key = prefix + ident
|
|
102
|
+
has_sub = bool(_safe(lambda: el.has_sub_elements, False))
|
|
103
|
+
item = {
|
|
104
|
+
"key": key,
|
|
105
|
+
"name": to_text(_safe(lambda: el.visible_name, "") or ""),
|
|
106
|
+
"depth": depth,
|
|
107
|
+
}
|
|
108
|
+
desc = to_text(_safe(lambda: el.description, "") or "")
|
|
109
|
+
if desc and desc != item["name"]:
|
|
110
|
+
item["description"] = desc[:160]
|
|
111
|
+
unit = to_text(_safe(lambda: el.unit, "") or "")
|
|
112
|
+
if unit:
|
|
113
|
+
item["unit"] = unit
|
|
114
|
+
param = _safe(lambda: el.parameter)
|
|
115
|
+
if depth == 0 and param is not None:
|
|
116
|
+
sec = to_text(_safe(lambda: param.section, "") or "")
|
|
117
|
+
if sec:
|
|
118
|
+
item["section"] = sec
|
|
119
|
+
t = to_text(_safe(lambda: param.type_string, "") or "")
|
|
120
|
+
if t:
|
|
121
|
+
item["type"] = t
|
|
122
|
+
acc = to_text(_safe(lambda: param.offline_access_rights, "") or "")
|
|
123
|
+
if acc:
|
|
124
|
+
item["access"] = acc
|
|
125
|
+
ch = to_text(_safe(lambda: el.parameter.channel_type, "") or "")
|
|
126
|
+
if ch and ch.lower() not in ("none", "0"):
|
|
127
|
+
item["channel"] = ch
|
|
128
|
+
if not has_sub:
|
|
129
|
+
v = _safe(lambda: el.value)
|
|
130
|
+
if v is not None:
|
|
131
|
+
item["value"] = to_text(v)
|
|
132
|
+
d = _safe(lambda: el.default_value)
|
|
133
|
+
if d:
|
|
134
|
+
item["default"] = to_text(d)
|
|
135
|
+
if _safe(lambda: el.is_enumeration, False):
|
|
136
|
+
item["choices"] = [to_text(_safe(lambda: e.identifier, "")) + (("=" + to_text(e.visible_name)) if _safe(lambda: e.visible_name) else "") for e in (_safe(lambda: el.allowed_values) or [])][:40]
|
|
137
|
+
if _safe(lambda: el.is_mappable_io, False):
|
|
138
|
+
m = _safe(lambda: el.io_mapping)
|
|
139
|
+
if m is not None:
|
|
140
|
+
item["mapping"] = {
|
|
141
|
+
"variable": to_text(_safe(lambda: m.variable, "") or ""),
|
|
142
|
+
"address": to_text(_safe(lambda: m.manual_iec_address, "") or ""),
|
|
143
|
+
"automatic_address": bool(_safe(lambda: m.automatic_iec_address, True)),
|
|
144
|
+
}
|
|
145
|
+
out.append(item)
|
|
146
|
+
if has_sub:
|
|
147
|
+
for sub in list(el):
|
|
148
|
+
_walk_elements(key + "/", sub, depth + 1, out, limit)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _device(c, path):
|
|
152
|
+
dev = c.resolve(path)
|
|
153
|
+
if not _safe(lambda: dev.is_device, False):
|
|
154
|
+
raise OpError("bad_path", "%s is not a device (get_devices lists them)." % path)
|
|
155
|
+
return dev
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def op_devices_params(args):
|
|
159
|
+
c = Ctx()
|
|
160
|
+
dev = _device(c, args["path"])
|
|
161
|
+
limit = int(args.get("limit") or 600)
|
|
162
|
+
out = []
|
|
163
|
+
for prefix, ps in _param_sets(dev):
|
|
164
|
+
for p in ps:
|
|
165
|
+
_walk_elements(prefix, p, 0, out, limit)
|
|
166
|
+
return {"path": c.path_of(dev), "parameters": out, "truncated": len(out) >= limit}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _element(dev, key):
|
|
170
|
+
"""The data element with this key (as devices.params gives it)."""
|
|
171
|
+
sets = dict(_param_sets(dev))
|
|
172
|
+
prefix = ""
|
|
173
|
+
rest = key
|
|
174
|
+
if key.startswith("c") and "/" in key and key.split("/")[0][1:].isdigit():
|
|
175
|
+
prefix = key.split("/")[0] + "/"
|
|
176
|
+
rest = key[len(prefix):]
|
|
177
|
+
ps = sets.get(prefix)
|
|
178
|
+
if ps is None:
|
|
179
|
+
raise OpError("not_found", "No parameter set '%s' on this device." % (prefix or "device"))
|
|
180
|
+
parts = rest.split("/")
|
|
181
|
+
el = None
|
|
182
|
+
for p in ps:
|
|
183
|
+
if to_text(_safe(lambda: p.identifier, "")) == parts[0]:
|
|
184
|
+
el = p
|
|
185
|
+
break
|
|
186
|
+
if el is None:
|
|
187
|
+
raise OpError("not_found", "No parameter '%s' (device_parameters lists the keys)." % key)
|
|
188
|
+
for part in parts[1:]:
|
|
189
|
+
nxt = None
|
|
190
|
+
for sub in list(el):
|
|
191
|
+
if to_text(_safe(lambda: sub.identifier, "")) == part:
|
|
192
|
+
nxt = sub
|
|
193
|
+
break
|
|
194
|
+
if nxt is None:
|
|
195
|
+
raise OpError("not_found", "No element '%s' in parameter '%s'." % (part, key))
|
|
196
|
+
el = nxt
|
|
197
|
+
return el
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def op_devices_configure(args):
|
|
201
|
+
c = Ctx()
|
|
202
|
+
dev = _device(c, args["path"])
|
|
203
|
+
done = []
|
|
204
|
+
errors = []
|
|
205
|
+
for v in args.get("values") or []:
|
|
206
|
+
try:
|
|
207
|
+
el = _element(dev, v["key"])
|
|
208
|
+
val = to_text(v["value"])
|
|
209
|
+
if _safe(lambda: el.is_enumeration, False):
|
|
210
|
+
match = None
|
|
211
|
+
for e in el.allowed_values or []:
|
|
212
|
+
if val in (to_text(e.identifier), to_text(_safe(lambda: e.visible_name, "")), to_text(_safe(lambda: e.value, ""))):
|
|
213
|
+
match = e
|
|
214
|
+
break
|
|
215
|
+
if match is None:
|
|
216
|
+
raise OpError("bad_value", "'%s' isn't one of %s" % (val, ", ".join(to_text(e.identifier) for e in el.allowed_values)))
|
|
217
|
+
el.enum_value = match
|
|
218
|
+
else:
|
|
219
|
+
el.value = val
|
|
220
|
+
done.append({"key": v["key"], "value": to_text(_safe(lambda: el.value, val))})
|
|
221
|
+
except Exception as e:
|
|
222
|
+
errors.append({"key": v.get("key"), "error": to_text(e)[:200]})
|
|
223
|
+
for m in args.get("mappings") or []:
|
|
224
|
+
try:
|
|
225
|
+
el = _element(dev, m["key"])
|
|
226
|
+
if not _safe(lambda: el.is_mappable_io, False):
|
|
227
|
+
raise OpError("bad_value", "'%s' is not an I/O channel." % m["key"])
|
|
228
|
+
io = el.io_mapping
|
|
229
|
+
io.variable = to_text(m.get("variable") or "")
|
|
230
|
+
if "address" in m:
|
|
231
|
+
io.manual_iec_address = to_text(m["address"]) if m["address"] else None
|
|
232
|
+
done.append({"key": m["key"], "variable": to_text(_safe(lambda: io.variable, "")), "address": to_text(_safe(lambda: io.manual_iec_address, "") or "")})
|
|
233
|
+
except Exception as e:
|
|
234
|
+
errors.append({"key": m.get("key"), "error": to_text(e)[:200]})
|
|
235
|
+
return {"path": c.path_of(dev), "done": done, "errors": errors, "saved": c.save_if(args)}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
mcp_ops.OPS["devices.params"] = op_devices_params
|
|
239
|
+
mcp_ops.OPS["devices.configure"] = mcp_sim._locked(op_devices_configure)
|
|
82
240
|
mcp_ops.OPS["devices.search"] = op_devices_search
|
|
83
241
|
mcp_ops.OPS["devices.add"] = mcp_sim._locked(op_devices_add)
|
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/mcp_visu.py
CHANGED
|
@@ -198,6 +198,51 @@ def _script_element(visu, eid):
|
|
|
198
198
|
return None
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
def _editor_context(c, visu, cache):
|
|
202
|
+
"""The open visual editor's element context for this visualization (needs CODESYS's window)."""
|
|
203
|
+
if "ctx" in cache:
|
|
204
|
+
return cache["ctx"]
|
|
205
|
+
import os
|
|
206
|
+
if os.environ.get("CODESYS_MCP_UI") != "1":
|
|
207
|
+
raise OpError("unavailable", "needs CODESYS's window (start with --show-window)")
|
|
208
|
+
from System.Reflection import BindingFlags
|
|
209
|
+
from System import Guid
|
|
210
|
+
allf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static
|
|
211
|
+
eng = _static("_3S.CoDeSys.VisualEditor.APEnvironment", "Engine")
|
|
212
|
+
frame = getattr(eng, "Frame", None) if eng is not None else None
|
|
213
|
+
if frame is None:
|
|
214
|
+
raise OpError("unavailable", "needs CODESYS's window (start with --show-window)")
|
|
215
|
+
view = frame.OpenEditorView(_object_mgr().GetMetaObjectStub(c.proj.handle, visu.guid), Guid.Empty, None)
|
|
216
|
+
se.system.delay(800)
|
|
217
|
+
ctl = view.Control
|
|
218
|
+
ve = ctl.GetType().GetField("_visualEditor", allf).GetValue(ctl)
|
|
219
|
+
ctx = ve.GetType().GetField("_editorCtx", allf).GetValue(ve)
|
|
220
|
+
vom = _find_type("_3S.CoDeSys.VisualEditor.VisualObjectManager").GetProperty("Singleton", allf).GetValue(None, None)
|
|
221
|
+
cache["ctx"] = (ctx, vom)
|
|
222
|
+
return cache["ctx"]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _complex_element(c, visu, el, label, cache):
|
|
226
|
+
ctx, vom = _editor_context(c, visu, cache)
|
|
227
|
+
mgr = _object_mgr()
|
|
228
|
+
mo = mgr.GetObjectToModify(c.proj.handle, visu.guid)
|
|
229
|
+
ok = False
|
|
230
|
+
try:
|
|
231
|
+
vo = mo.Object
|
|
232
|
+
nid = vo.UniqueId
|
|
233
|
+
elem = vom.CreateElementByTypeName(el["type_name"], None, label, nid, ctx, True, ctx.AvailableHotkeysProvider, ctx.HotkeysConfiguration)
|
|
234
|
+
elem.Id = nid
|
|
235
|
+
elem.OwningObjectGuid = vo.MetaObject.ObjectGuid
|
|
236
|
+
vo.VisualElemList.Add(elem)
|
|
237
|
+
ok = True
|
|
238
|
+
finally:
|
|
239
|
+
mgr.SetObject(mo, ok, None)
|
|
240
|
+
e = _script_element(visu, nid)
|
|
241
|
+
for p, v in (("Position.X", el["x"]), ("Position.Y", el["y"]), ("Position.Width", el["w"]), ("Position.Height", el["h"])):
|
|
242
|
+
e.set_property(p, int(v))
|
|
243
|
+
return e
|
|
244
|
+
|
|
245
|
+
|
|
201
246
|
def _find_child(c, name):
|
|
202
247
|
for ch in c.app.get_children(False):
|
|
203
248
|
if name_of(ch).lower() == name.lower():
|
|
@@ -234,8 +279,28 @@ def op_visu_build(args):
|
|
|
234
279
|
warnings.append("size/dialog: %s" % to_text(ex))
|
|
235
280
|
T = se.VisualElementType
|
|
236
281
|
corners = []
|
|
237
|
-
|
|
282
|
+
queue = list(args.get("elements") or [])
|
|
283
|
+
editor = {}
|
|
284
|
+
i = -1
|
|
285
|
+
while queue:
|
|
286
|
+
el = queue.pop(0)
|
|
287
|
+
i += 1
|
|
238
288
|
label = el.get("name") or "E%d" % i
|
|
289
|
+
if el["kind"] == "complex":
|
|
290
|
+
# Meters, bar displays and alarm tables need the visual editor's context, which only
|
|
291
|
+
# exists with CODESYS's window. Without it, the plan's fallback elements are drawn.
|
|
292
|
+
try:
|
|
293
|
+
e = _complex_element(c, visu, el, label, editor)
|
|
294
|
+
except Exception as ex:
|
|
295
|
+
warnings.append("%s: drawn without %s (%s)" % (label, el.get("type_name"), to_text(ex)[:160]))
|
|
296
|
+
queue = list(el.get("fallback") or []) + queue
|
|
297
|
+
continue
|
|
298
|
+
for pair in el.get("pairs") or []:
|
|
299
|
+
try:
|
|
300
|
+
e.set_property(pair[0], _value(pair[1]))
|
|
301
|
+
except Exception as ex:
|
|
302
|
+
warnings.append("%s %s: %s" % (label, pair[0], to_text(ex)[:120]))
|
|
303
|
+
continue
|
|
239
304
|
try:
|
|
240
305
|
if el["kind"] in _SIMPLE:
|
|
241
306
|
eid = _add_created(c.proj, visu, _simple_creator(el), label)
|
|
@@ -415,7 +480,22 @@ def op_visu_render(args):
|
|
|
415
480
|
return {"path": path}
|
|
416
481
|
|
|
417
482
|
|
|
483
|
+
def op_ui_open(args):
|
|
484
|
+
"""With CODESYS's window: open an object in its editor (brings its tab forward)."""
|
|
485
|
+
import os
|
|
486
|
+
import mcp_ui
|
|
487
|
+
if os.environ.get("CODESYS_MCP_UI") != "1":
|
|
488
|
+
raise OpError("unavailable", "CODESYS runs without its window. Start the toolkit with --show-window to see objects in CODESYS.")
|
|
489
|
+
c = Ctx()
|
|
490
|
+
obj = c.resolve(args["path"])
|
|
491
|
+
state = mcp_ui.open_editor(se, obj)
|
|
492
|
+
if state == "unavailable":
|
|
493
|
+
raise OpError("unavailable", "This CODESYS doesn't let scripts open editors.")
|
|
494
|
+
return {"path": c.path_of(obj), "opened": state}
|
|
495
|
+
|
|
496
|
+
|
|
418
497
|
mcp_ops.OPS.update({
|
|
498
|
+
"ui.open": op_ui_open,
|
|
419
499
|
"visu.manager": op_visu_manager,
|
|
420
500
|
"visu.build": op_visu_build,
|
|
421
501
|
"visu.render": op_visu_render,
|
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)
|