0
0
Fork 0
Commit graph

6 commits

Author SHA1 Message Date
Karstein Phobic Nyvold Kvistad
d4e71f61ca fix(compile_messages): discover real category GUIDs via system.get_message_categories()
The 0.6.3 fix replaced 'first-non-empty-pattern-wins' with category
iteration but used a HARDCODED list of 'well-known V3.5 GUIDs' that was
wrong. None of those GUIDs matched the actual Build category, so
compile errors stayed invisible (compile_project still reported '0
errors' even when the IDE-side download path saw them).

Diagnosed via a one-shot probe injected into compile_project on
2026-04-29: dumped attrs of script_engine.system, then called
script_engine.system.get_message_categories() (the METHOD) directly.
That returned the actual 7 category GUIDs in this CODESYS V3.5 SP22
Patch 1 install:

  05581bd1-66d3-4251-aff2-047cc8e9adf7  Offline Help
  936e1a33-3af8-47fa-b40b-903f0ae0b6cc  Application Composer
  a9b26e07-6ae1-4c06-9cd1-9ddddd397a2d  SVN
  0a6fcb64-7f24-43c6-a6d3-f70cb5d31114  (no parameterless ctor; Git)
  194b48a9-ab51-43ae-b9a9-51d3edaaddf3  Script Messages
  97f48d64-a2a3-4856-b640-75c046e37ea9  Build              <-- the one we needed
  220493a1-f49b-4416-9a3f-a545db707cbe  Additional code checks

Real fix: replace the hardcoded list in _enumerate_categories() in both
compile_project.py and get_compile_messages.py with a call to
system.get_message_categories(); label each one via
get_message_category_description(guid). Iterate per category as before.
GUIDs are now discovered at runtime so the same code works on any SP
and any locale.

Verification: injected `THIS_IS_NOT_VALID_IEC_KEYWORD;` into
PLC_PRG.implementation, ran compile_project; output now reads
"1 error(s), 2 warning(s). ERROR: Identifier
'THIS_IS_NOT_VALID_IEC_KEYWORD' not defined". Restored PLC_PRG;
0 error(s) again. End-to-end fix confirmed.

Bumped 0.6.3 -> 0.6.4.
2026-04-29 07:39:31 +02:00
Karstein Phobic Nyvold Kvistad
41818283f4 fix(compile_messages): probe every category + tighten _is_resolved hollow-ref check
Two real bugs surfaced today:

1. compile_project / get_compile_messages reported 0 errors even when
   the IDE's download path saw real compile errors. Root cause: both
   scripts queried get_message_objects() with no category arg, which
   returns only the IDE's last-active message tab (typically "Other"
   for the WATCHER startup messages). Build/Code-Generation errors
   live in a different category and were never queried.

   Fix: enumerate script_engine.system.message_categories AND probe a
   set of well-known V3.5 category GUIDs (Compile, Build, Online,
   LibMan); query target_app.get_message_objects(cat) and
   system.get_message_objects(cat) per category; aggregate dedup'd
   entries (severity, text, object, line). Each entry now carries its
   originating category label.

2. add_library's post-add _is_resolved() check trusted is_placeholder
   == False as proof of resolution. CODESYS lets you call
   add_placeholder(name_str) for a name that is NOT in the installed
   Library Repository -- the resulting reference reports
   is_placeholder=False yet has empty effective_version and the IDE
   shows it with a yellow-warning triangle in Library Manager (no
   Effective Version column populated). Karstein hit this with
   "OPC UA PubSub SL": list_project_libraries reported it as
   [managed] but compile failed because the IDE couldn't resolve it.

   Fix: for the non-placeholder branch, probe effective_version /
   resolved_version / version / resolved_library / managed_library /
   library. ALL must be empty/None for the ref to be considered
   hollow. Logs a DEBUG with the ref's attribute list so the next
   such bug is diagnosable without source spelunking.

Bumped 0.6.2 -> 0.6.3.
2026-04-28 23:35:24 +02:00
Karstein Phobic Nyvold Kvistad
763a30761d fix(compile): deep-walk message structures with _coerce_for_json before json.dumps
Empirical failure: TypeError 281474976710655L is not JSON serializable from get_message_objects().
Root cause: previous per-attribute coercion only flattened known fields (severity/text/line); nested dicts/lists carrying CLR longs slipped through.
Fix: add a recursive _coerce_for_json helper that walks dicts/lists/tuples and downcasts long->int (or str if >Int64), keeps bool, then call it before every json.dumps in both compile_project.py and get_compile_messages.py.
Docs: https://content.helpme-codesys.com/en/ScriptingEngine/ScriptSystem.html (get_message_objects), https://content.helpme-codesys.com/en/ScriptingEngine/ScriptObject.html

Note: previous commit 418f678 added the per-field coercers; this commit
strengthens that with a recursive walker per the bug doc's proposal --
defensive against any future API change that nests longs deeper.

Manual smoke test only -- requires CODESYS-in-the-loop with a project
that produces compile messages whose severity bitmask is 0xFFFFFFFFFFFF.

### Manual smoke test
1. Open a project with at least one warning or error.
2. Call mcp__codesys__compile_project on it.
3. Expect SCRIPT_SUCCESS and a populated COMPILE_MESSAGES_START block;
   no TypeError 'is not JSON serializable' anywhere in the output.
4. Repeat with mcp__codesys__get_compile_messages.
2026-04-28 20:50:15 +02:00
Karstein Kvistad
2607063306 fix: 4 broken-tool fixes (compile json long, connect_to_device LoginMode,
create_folder SP21+ fallback, ensure_project_open cross-project switch)
+ bench results

compile_project.py + get_compile_messages.py:
- IronPython 2.7's json.dumps cannot serialize System.Int64-backed `long`
  values, which is what CODESYS's compile-message objects expose as
  line_number / position. Added _coerce_int + _coerce_str helpers and a
  shared _build_message_entry function. Three duplicated message-building
  blocks collapsed into single helper calls.
- Defensive `try: json.dumps(...) except TypeError: json.dumps(default=str)`
  so a stray field that slips past the helpers doesn't kill the emit.

connect_to_device.py:
- SP21+ may expose the login enum as LoginMode rather than
  OnlineChangeOption. Extended the candidate sweep to probe both
  script_engine.LoginMode and script_engine.OnlineChangeOption AND
  online_app.LoginMode/OnlineChangeOption (some builds attach it to the
  app object). Added "OnlineChange" + "Login" + "Download" to the
  preferred-priority list. Added a 3-arg call shape variant for SPs
  that take (mode, secondary-mode, force-bool).

create_folder.py:
- parent_object.create_folder() is no longer exposed on every parent type
  in SP21+. Added two fallback factories tried in order:
    1. parent.create_object(typeUuid='85d1215e-6520-4983-9a55-2d39d1f24cb4', name=...)
    2. parent.add(script_engine.types.IecFolder, name=...)
  with detailed warnings when each path fails. Final TypeError now lists
  every factory tried so a future SP rotation surfaces clearly.

ensure_project_open.py:
- Uncommented the close-prior-project branch (was a TODO since the
  initial fork). Cross-project switches in a persistent CODESYS now do
  save() -> close() -> 500ms pump -> open(target). Without this,
  projects.open against a different already-primary project fails
  intermittently on file lock contention or pops a "project in use"
  modal that freezes the IDE thread.
- save() is best-effort: if it raises (transient lock, save-as required)
  we still proceed with close + open rather than getting stuck in a
  half-switched state forever.

tests/bench-results.json:
- Captured timings from a clean run on MCPTest2 (PLCWinNT, 5 lib refs,
  ~12 POUs). 9 tools x 2 modes x iterations.
  Headers (mean ms): persistent vs headless --
    open_project              7700  vs   40041   (~5x; first call cold)
    mirror_export             1547  vs   23723   (~15x)
    list_project_libraries    1565  vs   23322   (~15x)
    get_all_pou_code          1607  vs   23376   (~15x)
    save_project              2095  vs   23321   (~11x)
    create_pou (FB)           1540  vs   23903   (~16x)
    delete_object             1544  vs   27420   (~18x)
    bump_project_version      1540  vs   30678   (~20x)
    bump_project_version #2   1556  vs   37769   (~24x)
- set_pou_code FAILED in both modes -- bench harness param-shape issue
  (multi-line code passed verbatim to triple-quoted-string interpolation
  doesn't survive the round-trip). Tool itself works fine through the
  MCP tool call path; bench needs to escape newlines / use the same
  prepareScriptWithHelpers shape the server uses. Filed for follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 16:53:25 +02:00
Luke
aad246576c Add 17 new MCP tools for v0.4.0: compiler diagnostics, project authoring, runtime monitoring, library management
- Phase 1: Structured compiler diagnostics — compile_project now returns parsed errors/warnings with object name and line number; new get_compile_messages tool reads last build messages without recompiling
- Phase 2: Project authoring — create_dut, create_gvl, create_folder, delete_object, rename_object, get_all_pou_code
- Phase 3: Online/runtime — connect_to_device, disconnect_from_device, get_application_state, read_variable, write_variable, download_to_device, start_stop_application (with ensure_online_connection helper)
- Phase 4: Library management — list_project_libraries, add_library
- Bump version to 0.4.0, update README with full tool reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 21:05:12 +10:00
Luke
e374519fe1 Initial release: MCP server for CODESYS with persistent UI instance
v0.3.0 returning-watcher architecture — background thread polls for commands
and marshals execution onto the CODESYS UI thread, keeping the IDE fully
responsive between operations. File-based IPC with atomic writes, async mutex
command serialization, headless fallback, and 35 passing tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:59:16 +10:00