0
0
Fork 0
An MCP server for Codesys
Find a file
steel 320646b8fe
Some checks failed
CI / build-and-test (18.x) (push) Has been cancelled
CI / build-and-test (20.x) (push) Has been cancelled
CI / build-and-test (22.x) (push) Has been cancelled
fixup
2026-08-19 18:39:47 +08:00
.github/workflows Initial release: MCP server for CODESYS with persistent UI instance 2026-02-08 18:59:16 +10:00
docs fixup 2026-08-19 18:39:47 +08:00
src fixup 2026-08-19 18:39:47 +08:00
tests fixup 2026-08-19 18:39:47 +08:00
.gitignore fixup 2026-08-19 18:39:47 +08:00
ARCHITECTURE.md fixup 2026-08-19 18:39:47 +08:00
inject-once.mjs fixup 2026-08-19 18:39:47 +08:00
LICENSE Initial release: MCP server for CODESYS with persistent UI instance 2026-02-08 18:59:16 +10:00
package-lock.json fixup 2026-08-19 18:39:47 +08:00
package.json fixup 2026-08-19 18:39:47 +08:00
README.md fixup 2026-08-19 18:39:47 +08:00
tsconfig.json tui: scaffold phobiCS-tui ESM subpackage with ink 2026-04-28 20:52:02 +02:00
vitest.config.ts Initial release: MCP server for CODESYS with persistent UI instance 2026-02-08 18:59:16 +10:00

MSP-Con-MCP-SP21+

This is a fork. It is not the upstream luke-harriman/Codesys-MCP.

Why fork. Upstream's watcher relies on system.execute_on_primary_thread() to marshal work from a background thread back to the MSP-Con UI thread. That API was removed in MSP-Con V3.5 SP21+, so on SP21 / SP22 every tool call returned the same Marshal error: The functionality 'system.execute_on_primary_thread(...)' is no longer supported and the server was effectively unusable on current MSP-Con releases. Several other upstream tools were also broken by unrelated script-engine API drift. This fork fixes all of that and adds a release pipeline on top.

MCP server for MSP-Con with a persistent UI instance and file-based IPC. Unlike headless-only approaches that spawn a new MSP-Con process per command, this server launches MSP-Con with its UI visible and keeps it running. MCP tool calls are sent to the same instance via a file-based IPC watcher, so changes appear in real-time and the user can interact with the IDE alongside AI-driven automation.


Quick Start

1. Install globally from npm:

npm install -g msp-con-mcp-sp21-plus

2. Generate your .mcp.json snippet--print-config scans your installed MSP-Con versions and emits a ready-to-paste block per install:

msp-con-mcp-sp21-plus --print-config           # one entry per detected install
msp-con-mcp-sp21-plus --print-config --sp 21   # only the SP21 entry, named "codesys"

Output looks like this on a machine with two installs:

// Auto-generated by `msp-con-mcp-sp21-plus --print-config` on 2026-04-27.
// Detected 2 MSP-Con installations. Add the entries you want; remove the rest.
//
// Multiple entries can be active at the same time -- different MSP-Con
// installs (e.g. SP21 + SP22) spawn separate processes and run side by side.
// The only hard rule: don't open the SAME .project file from two MSP-Con
// instances simultaneously -- file-lock contention pops a "project is
// currently in use" modal that blocks all script execution.

{
  "mcpServers": {
    "msp-con-sp21-patch5": {
      "command": "msp-con-mcp-sp21-plus",
      "args": [
        "--msp-con-path", "C:\\Program Files\\MSP-Con 3.5.21.50\\MSP-Con\\Common\\MSP-Con.exe",
        "--msp-con-profile", "MSP-Con V3.5 SP21 Patch 5",
        "--mode", "persistent"
      ]
    },
    "msp-con-sp22-patch1": { ... }
  }
}

3. Paste into your MCP config:

  • Project-scoped (recommended, shareable via git): <your-project-root>/.mcp.json. Create it if it doesn't exist; merge the mcpServers entries into the existing object if it does.
  • User-scoped (applies to every Claude Code session): %USERPROFILE%/.claude.json, or use claude mcp add codesys msp-con-mcp-sp21-plus -- --msp-con-path ... --msp-con-profile ....
  • Using OpenAI Codex instead of Claude Code? It's the same stdio server — see Use with OpenAI Codex CLI for the config.toml equivalent.

The snippet is JSON with // comment headers — strip the comments before parsing if your tooling is strict.

4. Restart Claude Code so it re-reads the MCP config.

See Installation for source-install / upgrade / multi-install setups.


Use with OpenAI Codex CLI

msp-con-mcp-sp21-plus is a standard stdio MCP server, so any MCP-capable client can drive it — not just Claude Code. OpenAI's Codex CLI stores MCP servers in a TOML file rather than a JSON .mcp.json.

Config file location:

  • Global: ~/.codex/config.toml (Windows: %USERPROFILE%\.codex\config.toml)
  • Per-project (trusted projects only): .codex/config.toml in the repo root

Translate the --print-config output into a [mcp_servers.<name>] table. --print-config emits Claude Code's JSON, but the command and args are identical — only the wrapper format differs. Run msp-con-mcp-sp21-plus --detect first to get the exact path + profile string, then:

[mcp_servers.msp-con-sp21]
command = "msp-con-mcp-sp21-plus"
args = [
  "--msp-con-path", "C:\\Program Files\\MSP-Con 3.5.21.50\\MSP-Con\\Common\\MSP-Con.exe",
  "--msp-con-profile", "MSP-Con V3.5 SP21 Patch 5",
  "--mode", "persistent",
  "--no-auto-launch",
]
# MSP-Con's first launch / a full compile can take 60s+. Codex defaults are
# startup_timeout_sec = 10 and tool_timeout_sec = 60 — raise the per-tool one.
tool_timeout_sec = 180

# Optional: pre-register PLC credentials so the "Device User Login" modal is
# suppressed on connect_to_device / download_to_device.
[mcp_servers.msp-con-sp21.env]
CODESYS_DEVICE_USER = "<user>"
CODESYS_DEVICE_PASSWORD = "<password>"

For multiple MSP-Con installs, add a second table ([mcp_servers.msp-con-sp22]) with its own --msp-con-path / --msp-con-profile — the TOML equivalent of the multi-install .mcp.json block under Multiple MSP-Con installations.

Or let Codex write the entry for you instead of editing the file by hand:

codex mcp add msp-con-sp21 \
  --env CODESYS_DEVICE_USER=<user> --env CODESYS_DEVICE_PASSWORD=<password> \
  -- msp-con-mcp-sp21-plus \
     --msp-con-path "C:\Program Files\MSP-Con 3.5.21.50\MSP-Con\Common\MSP-Con.exe" \
     --msp-con-profile "MSP-Con V3.5 SP21 Patch 5" \
     --mode persistent --no-auto-launch

Notes:

  • Differences from Claude Code's config: a TOML [mcp_servers.<name>] table (note the underscore) instead of the JSON mcpServers object; env is a TOML table (or inline env = { KEY = "val" }); there's no outer wrapper object.
  • --no-auto-launch is recommended here too so MSP-Con opens on the first tool call rather than when Codex spawns the server (otherwise the launch runs during startup and can exceed startup_timeout_sec).
  • Restart Codex after editing config.toml — the MCP client only reads it at startup.
  • Everything else is identical because it's the same binary: one MSP-Con.exe at a time, exact --msp-con-profile strings (copy from --detect), --for-project, --auto-mirror, etc.

Live source-control diff (--auto-mirror)

When --auto-mirror is added to the server args, every successful modifying tool call (set_pou_code, create_pou, rename_object, add_library, ...) is followed by an automatic mirror_export. The textual <projectDir>/mcp-mirror/ tree is refreshed on disk in lock-step with the binary .project, so an external editor watching the folder sees the change immediately. The first refresh on a given project also fires code --add <mirrorDir> once, which appends the mirror folder to your active VSCode window so the diff shows up in the Source Control panel.

Multiple projects in one folder: when two or more .project files share a parent dir, each project gets its own mirror at <projectDir>/<projectname>_mcp_mirror/ instead (e.g. ProjectA.project -> ProjectA_mcp_mirror/, ProjectB.project -> ProjectB_mcp_mirror/) so the exports don't clobber each other. Existing setups with a mcp-mirror/ directory keep using it as-is, regardless of how many .project siblings are present, so v* tag history stays intact.

Enable it by adding the flag to the relevant entry's args in .mcp.json:

"args": [
  "--msp-con-path", "...",
  "--msp-con-profile", "...",
  "--mode", "persistent",
  "--auto-mirror"
]

Recommended one-time setup so the Source Control panel has a baseline to diff against:

cd <projectDir>/mcp-mirror
git init && git add -A && git commit -m "baseline"

After that, watch VSCode's Source Control panel as Claude edits — every tool call shows up as a real diff. The VSCode hook is best-effort: if the code CLI shim isn't on the standard path, the mirror still refreshes silently and the response carries an (auto-mirror: refreshed) hint instead of opening a window.


What's new in this fork

Compatibility fixes (the headline)

  • SP21+/SP22 compatibility. The watcher was rewritten as single-threaded on the primary thread, yielding to the IDE via system.delay(). No background thread, no marshaling. Works on SP19, SP21, and SP22+. Full rationale in docs/migration-sp21-plus.md.
  • Cancel-link hardening. The watcher now catches KeyboardInterrupt (which is not a subclass of Exception in IronPython 2.7) at three layers, so clicking "Click here to CANCEL this operation" in MSP-Con no longer pops the modal traceback dialog or kills the watcher. WATCHER_VERSION 0.4.2.

Upstream tool fixes

  • create_folder — upstream passed name= as a kwarg the API doesn't accept; fixed to use positional foldername= with an SV_POU fallback for SP21+, then walks children to detect success since the API returns void.
  • compile_project / get_compile_messages — upstream choked on Python long values that json.dumps can't serialize on IronPython 2.7. Coerced to int before dumping.
  • connect_to_device — upstream used the wrong LoginMode signature. Fixed, plus the online tools now auto-login if you haven't already, instead of silently returning empty results.
  • ensure_project_open — fixed the cross-project switch path so opening a second project no longer leaves the watcher pinned to the first.
  • set_pou_code — upstream wiped the other half of the POU when only declaration or only implementation was passed. Now an omitted field is left intact.
  • add_library — pre-resolves via library_manager.find_library and prefers the managed-library overload. Refuses to save if the resulting reference is an unresolvable placeholder, which would otherwise brick the next project open.
  • list_project_libraries — switched to the ScriptLibManObjectContainer API (the previous one no longer exists), and now also captures IDE version, devices, and per-Application compiler version.

New tools (not in upstream)

  • SP21 full API coverage (v0.11.0v0.12.0) — 46 tools across 5 phases closing the gap to the SP21 ScriptEngine API: online/runtime ops (reset, force/unforce, bulk read/write, boot application, source up/download, PLC file transfer), project lifecycle (PLCopenXML + native export/import, project archive, compiled library, project info, compiler version), application build actions, device parameters + IO-mapping CSV + task configuration, and project user management. Per-category tables under MCP Tools; plan + status in docs/superpowers/plans/2026-06-12-sp21-api-coverage.md. SVN, Application Composer and Automation Server scripting are deliberately out of scope (license-gated / addon products).
  • mirror_export — walks the project tree and writes one .st file per code-bearing object into <projectDir>/mcp-mirror/, preserving the project tree. Read-only; foundation for source-controlled MSP-Con projects.
  • bump_project_version — bumps one part of the 4-part Project Information.Version (major / minor / revision / build / auto) and maintains a _MCP_PROJECT_VERSION GVL inside the project so the running PLC carries its source version. auto mode classifies via mirror diff vs the latest v* git tag (deletion/rename → major; addition → minor; modification → revision; first-run → seed at 1.0.0.0). Auto-maintains Changelog.md alongside the bump.
  • release_project_version — one-shot release pipeline: mirror_export → classify → bump_project_version → regenerate library.md/pou-dump.md/README.md/Changelog.md → git add controlled paths → git commitgit tag v<new>git push --follow-tags. Tag annotation embeds dual SHAs (project-sha256 + mirror-sha256) so the binary-changed-without-source-diff case still gets a build-bump with provenance.
  • read_running_version_online — reads _MCP_PROJECT_VERSION.sVersion from the running PLC over the MSP-Con online protocol (port 11740 / gateway). Returns the live value plus a sanity check against the X.Y.Z.W shape.

Reliability fixes

  • launcher refuses to spawn a 2nd instance of the same MSP-Con install (would conflict on the project file lock). Different installs (SP21 + SP22) coexist fine. Filters by --msp-con-path, not just by image name, so multi-install setups work.
  • shutdown_codesys kills orphan MSP-Con.exe of the configured install when the launcher has no tracked PID (e.g. after a crashed parent). Other installs are left alone.
  • Template interpolation hardening (v0.12.1)$-sequences in tool-arg values (IEC string literals like '$R$N') are no longer mangled by regex replacement; user-arbitrary values (passwords, comments, PLC paths, device parameter values) are escaped into Python string literals instead of being pasted raw into r"..." templates; find_object_by_path accepts dot-separated paths all the way through its final name check; the build cleans dist/scripts so deleted templates don't ship in the npm tarball.

Verification

The verified state of every tool is recorded in docs/function-test-2026-04-25.md (and the 2026-04-28 re-verification in docs/function-test-2026-04-28.md). Open issues (mostly online-API drift) are tracked in docs/open-bugs-cross-reference.md.


Installation

This is a Node.js MCP server published to npm as msp-con-mcp-sp21-plus. It is this fork — not the upstream luke-harriman/Codesys-MCP and not a Python package. There is no pip install; the .py files under src/scripts/ are MSP-Con IronPython templates bundled inside the npm package itself.

Requirements: Node.js 18+, Windows, MSP-Con 3.5 SP19, SP21 (3.5.21.x), or SP22 (3.5.22.x) installed.

npm install -g msp-con-mcp-sp21-plus

That single command:

  • downloads the published tarball from https://www.npmjs.com/package/msp-con-mcp-sp21-plus
  • installs it globally (-g) so the msp-con-mcp-sp21-plus binary is on your PATH (typically %APPDATA%\npm\ on Windows)
  • pulls in its 4 dependencies (@modelcontextprotocol/sdk, commander, uuid, zod) automatically

Verify the install:

msp-con-mcp-sp21-plus --version
msp-con-mcp-sp21-plus --detect       # lists installed MSP-Con versions

Then wire it into .mcp.json per Quick Start and start Claude Code.

To upgrade later:

npm install -g msp-con-mcp-sp21-plus@latest

Install from source (development / unreleased changes)

If you want to track the fork's main branch directly, contribute fixes, or pin to a specific commit instead of the published version:

git clone https://git.jjgs123.cn/nikon/MSP-Con-MCP-SP21-plus.git
cd MSP-Con-MCP-SP21-plus
npm install
npm run build
npm link

npm link registers dist/bin.js as the global msp-con-mcp-sp21-plus binary, so the same .mcp.json snippet works. Edits to src/ take effect after npm run build; Python script edits hot-reload from dist/scripts/ without a rebuild.

To update later: git pull && npm install && npm run build.

If you'd rather avoid touching the global node_modules, skip npm link and reference the local checkout directly in .mcp.json:

{
  "mcpServers": {
    "codesys": {
      "command": "node",
      "args": [
        "C:\\Users\\<you>\\MSP-Con-MCP-SP21-plus\\dist\\bin.js",
        "--msp-con-path", "C:\\Program Files\\MSP-Con 3.5.22.10\\MSP-Con\\Common\\MSP-Con.exe",
        "--msp-con-profile", "MSP-Con V3.5 SP22 Patch 1",
        "--mode", "persistent"
      ]
    }
  }
}

Run without .mcp.json

You can also invoke the binary directly from a shell (useful for one-off testing or wrapping in another launcher):

msp-con-mcp-sp21-plus \
  --msp-con-path "C:\Program Files\MSP-Con 3.5.22.10\MSP-Con\Common\MSP-Con.exe" \
  --msp-con-profile "MSP-Con V3.5 SP22 Patch 1"

Multiple MSP-Con installations

The MCP server is bound to a single --msp-con-path / --msp-con-profile at startup. launch_codesys takes no parameters — it just starts whichever MSP-Con the server was configured against. If you have several MSP-Con versions installed and want to drive them all from the same Claude Code session, register one MCP server entry per install with a distinct name.

Both blocks below live in the same .mcp.json. Claude can call either by name (codesys-21 / codesys-22) and the two run as independent processes with independent MSP-Con instances:

{
  "mcpServers": {
    "codesys-21": {
      "command": "msp-con-mcp-sp21-plus",
      "args": [
        "--msp-con-path", "C:\\Program Files\\MSP-Con 3.5.21.50\\MSP-Con\\Common\\MSP-Con.exe",
        "--msp-con-profile", "MSP-Con V3.5 SP21 Patch 5",
        "--mode", "persistent"
      ]
    },
    "codesys-22": {
      "command": "msp-con-mcp-sp21-plus",
      "args": [
        "--msp-con-path", "C:\\Program Files\\MSP-Con 3.5.22.10\\MSP-Con\\Common\\MSP-Con.exe",
        "--msp-con-profile", "MSP-Con V3.5 SP22 Patch 1",
        "--mode", "persistent"
      ]
    }
  }
}

Notes:

  • The version numbers (3.5.21.50, 3.5.22.10) match the install directory names under C:\Program Files\ — these are the actual install IDs MSP-Con uses, not the marketing names. The marketing name lives in --msp-con-profile (e.g., MSP-Con V3.5 SP21 Patch 5, MSP-Con V3.5 SP22 Patch 1).
  • Run msp-con-mcp-sp21-plus --detect once to print every MSP-Con install the server can see, with its profile name; copy the values from there into .mcp.json rather than guessing.
  • Each server entry spawns its own MSP-Con process when first invoked. Don't call launch_codesys on both at the same time pointing at projects that overlap — two MSP-Con instances racing on the same .project file pop a "project is currently in use" modal that blocks every subsequent script.
  • Adding or removing an entry requires a Claude Code restart (the MCP client only reads .mcp.json at startup).

If you have a specific .project file in mind and don't want to eyeball which install opens it, point --for-project at the file and --print-config will narrow the snippet to just the matching install (or warn and fall back to same-SP-different-patch if no exact match exists). The match is driven by the project's saved projectinspectiondata.auxiliary profile, so it works without launching MSP-Con:

msp-con-mcp-sp21-plus --print-config --for-project "C:\path\to\MyMachine.project"

Caveat: --for-project reads projectinspectiondata.auxiliary out of the project ZIP. That entry exists in .projectarchive files, but a plain .project is not a ZIP — it is a compressed MSP-Con container (magic 23 89 ED 33) with no readable profile string. On a plain .project, --for-project finds nothing and falls back to the default install. Use the version pin below to protect real projects.

--codesys-additional-folder: where the add-on packages actually live

If launching produces "The command line option 'runscript' has been set. However, there is no script engine implementation available", or a load dialog that appears to contradict itself —

The project file has been created with MSP-Con V3.5 SP19 Patch 2 and contains data that cannot be loaded by MSP-Con V3.5 SP19 Patch 2.

— the install has multiple profiles sharing one name.

The MSP-Con Installer registers add-on packages (Script Engine, device support, …) into a per-installation directory:

<install>\MSP-Con\AdditionalFolders\<InstallationName>\Profiles\<ProfileName>.profile.xml

Every one of those carries the same <ProfileName> as the bare base profile in <install>\MSP-Con\Profiles\, but a different set of registered plugins. So --profile alone is ambiguous: MSP-Con resolves it to the base profile, which on an installer-managed box can have zero plugins. That's both symptoms above — no Script Engine, and "missing packages" phrased in terms of a profile name that matches.

The shortcut the installer drops in the Start Menu passes the disambiguator; so must this server:

"--msp-con-profile", "MSP-Con V3.5 SP19 Patch 2",
"--codesys-additional-folder", "C:\\Program Files\\MSP-Con 3.5.19.20\\MSP-Con\\AdditionalFolders\\MyInstallation",

--detect / --print-config find this for you: they rank every AdditionalFolders\* by how many plugins its profile ranks and emit the fullest one. Installs with no AdditionalFolders (the stock case — everything is in the base profile) get no flag, which is correct.

To check by hand, compare plugin counts across the same-named profiles:

Get-ChildItem "C:\Program Files\MSP-Con 3.5.19.20\MSP-Con" -Recurse -Filter "*.profile.xml" |
  ForEach-Object { "{0,-4} {1}" -f (Select-String $_ -Pattern '<Hint>' -AllMatches).Matches.Count, $_.FullName }

Version pin: never silently convert a project

Opening a project in a MSP-Con newer than the one that authored it converts it on save. The .project on disk is then no longer the software running on the device — and if that save happened inside release_project_version, the wrong binary is already committed, tagged and pushed.

Because the authored version can't be read out of a .project (see the caveat above), it is pinned in the repo instead. Two sources, most specific first:

  1. .codesys-version next to the .project — one line, either 3.5.19.20 or MSP-Con V3.5 SP19. # comments and blank lines are skipped. This is the only option when seeding a project that has no release history yet.
  2. library.md — the MSP-Con Development System row of a previously generated inventory. Every project gets a pin for free after its first release.
echo 3.5.19.20 > "C:\plc\MyVessel\.codesys-version"

The guard is deliberately asymmetric, so it protects the dangerous path without getting in the way:

Tool Pin matches Pin differs No pin
bump_project_version, release_project_version (save the project) proceed refuse refuse
get_project_info, mirror_export, list_project_libraries (read only) proceed warn proceed

Both saving tools take allowVersionUpgrade: true to override when the conversion is deliberate. Read-only tools never refuse — a warning is enough to stop a human before they run the release, and refusing every read would break existing unpinned repos.

Note this is a different mechanism from the open_project pre-flight in src/preflight.ts, which compares the ZIP-derived profile and therefore no-ops on plain .project files.

CLI Reference

Flag Description Default
-p, --msp-con-path <path> Path to MSP-Con executable $MSPCON_PATH or auto-detected
-f, --msp-con-profile <name> MSP-Con profile name $MSPCON_PROFILE or MSP-Con V3.5 SP21
-w, --workspace <dir> Workspace directory for relative paths Current directory
-m, --mode <mode> persistent (UI) or headless (--noUI) persistent
--no-auto-launch Don't launch MSP-Con on startup Auto-launch enabled
--fallback-headless Fall back to headless (--noUI) if persistent launch fails false
--keep-alive Keep MSP-Con running after server stops false
--timeout <ms> Default command timeout 60000
--detect List installed MSP-Con versions and exit
--print-config Print a ready-to-paste .mcp.json snippet for every detected install and exit
--sp <number> With --print-config: emit only the entry for MSP-Con V3.5 SP<n>
--for-project <path> With --print-config: pick only the install(s) matching the .project file at <path> (exact SP+patch, or fall back to same-SP-different-patch). Mutually exclusive with --sp.
--name <name> With --print-config --sp <n>: override the MCP server entry name
--inspect <path> Read a MSP-Con .project offline (no MSP-Con needed) and print its profile name/version + mandatory libraries; uses the unzip CLI from Git for Windows / Linux+Mac
--ssh-version <host> SSH to a MSP-Con Control Linux PLC and print the running project version (extracted from the boot-application binary). Bypasses MSP-Con entirely. Requires SSH key auth + passwordless sudo for strings.
--ssh-user <name> With --ssh-version: SSH user karstein
--ssh-boot-app <path> With --ssh-version: path to the boot application on the PLC /var/opt/codesys/PlcLogic/Application/Application.app
--verbose Enable verbose logging
--debug Enable debug logging
-V, --version Show version number
-h, --help Show help

Environment variables MSPCON_PATH and MSPCON_PROFILE are used as defaults when the corresponding flags are not provided.

--ssh-version — read the running PLC's project version over SSH

For MSP-Con Control Linux PLCs (Raspberry Pi, IPC, etc.) the running project version can be read straight off the boot-application binary, without MSP-Con being installed or the .project file being unlocked:

msp-con-mcp-sp21-plus --ssh-version 192.168.1.83
msp-con-mcp-sp21-plus --ssh-version myplc.lan --ssh-user pi

Requires SSH key auth + passwordless sudo for /usr/bin/strings on the PLC. If your key isn't installed yet, the error message includes a one-line PowerShell recipe; full setup instructions live at ssh-key-windows.md.

MCP Tools

102 tools across the categories below. Tools marked NEW were added in this fork; tools marked FIXED existed upstream but were broken before this fork.

Management Tools

Tool Description
launch_codesys Manually launch MSP-Con (use with --no-auto-launch)
shutdown_codesys Shut down the persistent MSP-Con instance (kills orphans too)
get_codesys_status Get current state, PID, execution mode

Project Tools

Tool Description
open_project Open an existing MSP-Con project file (cross-project switch FIXED; SP-mismatch pre-flight NEW)
create_project Create a new project from the standard template
save_project Save the currently open project
compile_project Build the primary application with structured error output (120s timeout) — JSON long FIXED
get_compile_messages Retrieve last compiler messages without triggering a new build — JSON long FIXED

open_project runs an offline pre-flight (projectinspectiondata.auxiliary ZIP+XML — no MSP-Con) that compares the project's saved profile against this server's --msp-con-profile. Exact match proceeds silently; same-SP-different-patch proceeds with a one-line warning (MSP-Con will pop its patch-difference dialog); SP mismatch refuses without opening so the project isn't dragged through a downgrade/upgrade conversion. The refusal includes a routing hint: pick a different MCP server entry or generate one with msp-con-mcp-sp21-plus --print-config --for-project "<projectFilePath>". If the inspection itself fails (file missing, malformed .project, non-standard profile name), pre-flight falls through silently and the existing MSP-Con open path produces its native error.

POU / Code Authoring Tools

Tool Description
create_pou Create a Program, Function Block, or Function
set_pou_code Set declaration and/or implementation code (omitted-field wipe FIXED)
create_property Create a property within a Function Block
create_method Create a method within a Function Block
create_dut Create a Data Unit Type (Structure, Enumeration, Union, Alias)
create_gvl Create a Global Variable List with optional initial declaration
create_folder Create an organizational folder in the project tree (FIXED)
delete_object Delete any project object (POU, DUT, GVL, folder, etc.)
rename_object Rename any project object
get_all_pou_code Bulk read all declaration and implementation code in the project (120s timeout)

Online / Runtime Tools

Tool Description
connect_to_device Login to the PLC runtime — LoginMode signature + auto-login FIXED; NEW deviceUser/devicePassword args (or CODESYS_DEVICE_USER/CODESYS_DEVICE_PASSWORD env) pre-register credentials via ScriptOnline.set_default_credentials so the modal "Device User Login" dialog is suppressed
disconnect_from_device Logout from the PLC runtime
get_application_state Check if the PLC application is running, stopped, or in exception
read_variable Read a live variable value from the running PLC (e.g., PLC_PRG.bMotorRunning)
write_variable Write/force a variable value on the running PLC
download_to_device Download compiled application to PLC (attempts online change first, 120s timeout); same deviceUser/devicePassword credential-injection support as connect_to_device so the Device User Login dialog can be suppressed on every download too. Runs verify_device_reachable as a pre-flight
start_stop_application Start or stop the PLC application
reset_application NEW — Reset the online application: warm (keep retains), cold (clear retains), origin (erase application from device — destructive)
read_variables NEW — Bulk read: current values of many expressions in one call (read_values)
write_variables NEW — Bulk write: stage many expression/value pairs, commit in one write_prepared_values batch (same-cycle landing)
force_variables NEW — Force expression/value pairs (pinned against task writes until unforced)
unforce_variables NEW — Unforce specific expressions (optionally restoring pre-force values) or ALL forced values
list_forced_variables NEW — List all forced + prepared expressions on the online application
create_boot_application NEW — Create a boot application on the connected device, or write an offline .app file
source_download NEW — Store the project source archive on the PLC (compact or full)
source_upload NEW — Retrieve the source archive stored on the PLC into a local .prj
plc_file_list NEW — List a directory on the PLC filesystem (kind/name/size/mtime)
plc_file_transfer NEW — Copy a single file to (to_plc) or from (from_plc) the PLC filesystem
plc_file_delete NEW — Delete a file or directory on the PLC filesystem (destructive)

Project Lifecycle & Interop (NEW, SP21-coverage phase 2)

Tool Description
close_project Close the open project (optionally saving first)
save_project_as Save under a new path; set/disable encryption password
save_project_archive Save a .projectarchive with default categories
save_as_compiled_library Save the primary project as a .compiled_library
export_plcopen_xml / import_plcopen_xml PLCopenXML interchange (whole project or subtree)
export_native / import_native Lossless MSP-Con native export/import
get_project_info / set_project_info Project Information fields + custom properties
get_compiler_version / set_compiler_version_to_newest Project compiler version
clean_all Clean All (remove compile info)

Application Build & Object Ops (NEW, phase 3)

Tool Description
application_build generate_code / rebuild / clean on the active application
check_online_change Is an online change currently possible?
move_object Move an object to a new parent in the tree
get_signature_crc Signature CRC of a POU (API-compatibility checks)
set_exclude_from_build Set/clear 'Exclude from build' on an object

Device Config & Task Config (NEW, phase 4)

Tool Description
list_device_parameters / get_device_parameter / set_device_parameter Walk and edit device + connector parameters
export_io_mappings_csv / import_io_mappings_csv Bulk IO-mapping editing via CSV
set_device_state enable / disable / simulation_on / simulation_off
get_device_identification Device type/id/version, name, address, state
create_task / configure_task Create tasks and set kind/priority/interval/event

Project Users & Misc Objects (NEW, phase 5)

Tool Description
list_project_users / add_project_user / remove_project_user Project access-protection user management
create_text_list / import_text_list_file Text lists for visu texts/translations
create_image_pool Image pools for visualizations
add_external_file Embed/link an external file into the project
restart_runtime_ssh NEW — SSH into a Linux PLC and restart codesyscontrol via password-fed sudo -S. After issuing systemctl restart, polls ss -tln for the runtime port (default 11740) until it actually comes up — works around systemctl is-active reporting "active" after the binary has died from license-demo expiry. Defaults match the codesys-pi.local Pi

Device Network / Access Management (NEW)

The gateway's cached device address goes stale every time the PLC reboots or gets a new router entry; these tools scan and re-bind without hand-editing the project. The two access-control tools cover the OPC UA prerequisites: the runtime user database (consulted for UserIdentityToken) and the project-side Access Control matrix on the Symbol Configuration object.

Tool Description
scan_network_devices NEW — Drive the gateway's Scan Network on the project's configured device. Returns the live target list (device_name, type_name, vendor_name, address, device_id). useCache=true returns the gateway's last result without rescanning
verify_device_reachable NEW — Pre-flight for download_to_device / connect_to_device: scans and reports whether the project's cached address still matches a live target. download_to_device runs this automatically
rebind_device_to_scan_result NEW — Re-bind the project's configured device to a fresh scan result (same PLC, new address after reboot/DHCP). Match priority: matchName (exact, case-insensitive) → matchDeviceIdmatchAddress (forced, no scan) → single candidate. Refuses on ambiguity and returns the candidate list
add_device_user NEW — Add (or update the password of) a user in the PLC runtime's live User Management. Required for OPC UA authentication on MSP-Con Control SP16+ — without at least one user, UaExpert returns BadIdentityTokenInvalid. The OPC UA server reads its UserIdentityToken policies from this database, NOT from CODESYSControl.cfg
grant_object_access NEW — Set Access Control permissions on a project object for a user group (mirrors Properties → Access Control in the IDE). Required before a downloaded OPC UA server exposes any token policies: if the group has no View/Modify on the Symbol Configuration, there's nothing to expose. Common usage: grant Everyone View+Modify on CodesysRpi/Plc Logic/Application/Symbols

Library Management Tools

Tool Description
list_project_libraries List all libraries referenced in the project with version info, plus IDE version, devices, and per-Application compiler version (FIXED — switched to ScriptLibManObjectContainer)
add_library Add a library reference. Pre-resolves via library_manager.find_library and prefers the managed-library overload; refuses to save if the resulting reference is an unresolvable placeholder (hardened)
remove_library NEW — Remove a library reference from Library Manager. Idempotent: no-op + success if the named library isn't present. Accepts a bare name ('Standard') or the fully-qualified 'Name, Version (Company)' form to target a specific version when duplicates exist. Verifies removal in lm.references before saving

Symbol Configuration Tools (NEW)

Wraps ScriptSymbolConfigObject (MSP-Con 3.5.10.0+). The Symbol Configuration object controls which IEC variables / FBs / methods are exposed to OPC UA, web visualisations, and other external clients. Reference: helpme-codesys.com/en/ScriptingEngine/ScriptSymbolConfigObject.html and the SP22 stub Stubs/scriptengine/ScriptSymbolConfigObject.pyi.

Tool Description
find_symbol_config NEW — Locate the Symbol Configuration object(s) in the project tree (one per Application typically). Read-only
list_all_signatures NEW — Every POU / FunctionBlock / Method / Function the symbol config could potentially export. compile=true forces an application.build() first
list_all_datatypes NEW — Every DUT / struct / enum / alias / union (same compile semantics)
list_configured_symbols NEW — Only those signatures + datatypes actually configured for export, with each variable's configured_access / maximal_access / effective_access
get_symbol_config_settings NEW — Read every knob: content_feature_flags (OPC UA / IncludeComments / IncludeAttributes / IncludeExecutables / etc.), attribute filter, comment filter, direct I/O access (+ obstacles), client-side layout calculator
create_symbol_config NEWapplication.create_symbol_config(...) under a chosen Application. Idempotent: no-ops with success if a symbol config already exists anywhere in the tree
set_symbol_config_settings NEW — Partial-update of any subset of the 6 knobs. Refuses to enable direct I/O if check_effective_direct_io_access() reports obstacles
set_symbol_access NEW — Per-variable configured_access setter (None / ReadOnly / WriteOnly / ReadWrite). Locates the signature by FQN; works on not-yet-configured variables too
set_signature_access_bulk NEW — Set every variable in one signature to the same access in one call
export_symbol_xsd NEW — Write the schema bytes from get_symbol_configuration_xsd() to a file (UTF-8). Useful for downstream XML validation in CI

Version Anchor + Release Pipeline (NEW)

These tools maintain a _MCP_PROJECT_VERSION GVL inside the project so the running PLC carries its source version at a known address, and orchestrate the end-to-end release flow (mirror → classify → bump → regen .md → git commit + tag + push).

Tool Description
bump_project_version NEW — Bump one part of the 4-part Project Information.Version (major / minor / revision / build / auto) and maintain _MCP_PROJECT_VERSION.sVersion. auto mode classifies via mirror diff vs latest v* git tag
release_project_version NEW — One-shot release pipeline: mirror_export → classify → bump_project_version → regenerate .md docs → git addgit commitgit tag v<new>git push --follow-tags. Dual-SHA tag annotation
read_running_version_online NEW — Reads _MCP_PROJECT_VERSION.sVersion from the running PLC over the MSP-Con online protocol (port 11740 / gateway). Caveat: requires some IEC code to reference the variable so the optimizer doesn't strip it from the online symbol table — see the tool's error message for the one-line fix.
read_running_version_ssh NEW — SSH equivalent of read_running_version_online: extracts the X.Y.Z.W literal of _MCP_PROJECT_VERSION.sVersion straight off the boot-application binary on a MSP-Con Control Linux PLC. Bypasses MSP-Con entirely — no IDE, no project lock, no online protocol. Requires SSH key auth + passwordless sudo for strings on the PLC. Same engine as the --ssh-version CLI flag

Source Mirror (NEW)

Tool Description
mirror_export NEW — Walks the project tree and writes one .st file per code-bearing object into <projectDir>/mcp-mirror/, preserving the project tree as nested directories. Read-only. Foundation for the release pipeline classifier

MCP Resources

Resource URI Description
codesys://project/status MSP-Con scripting status and open project info
codesys://project/{path}/structure Project tree structure
codesys://project/{path}/pou/{pou}/code POU declaration and implementation code

Execution Modes

Persistent Mode (default, SP21+ rewrite)

  1. Server launches MSP-Con.exe with --runscript=watcher.py (no --noUI)
  2. MSP-Con UI opens — user can see and interact with the IDE
  3. The watcher runs single-threaded on the primary thread, polling a commands/ directory and yielding to the IDE via system.delay() between polls (this fork — upstream used a background thread + system.execute_on_primary_thread() which was removed in SP21)
  4. When a tool is called, the server writes a .py script + .command.json to commands/
  5. The watcher detects the command, executes it directly on the primary thread, and writes results atomically to results/
  6. Changes made by tools appear in the MSP-Con UI in real-time
  7. The UI remains interactive between commands — only briefly paused during synchronous API calls (compile, open)

Headless Mode

The original approach: each tool call spawns a new MSP-Con process with --noUI, runs the script, and exits. No UI is shown. Used only when:

  • --mode headless is specified, or
  • Persistent mode fails to launch and --fallback-headless is explicitly opted in (off by default)

Persistent mode never silently degrades to headless. With --no-auto-launch, the first tool call lazy-launches the visible IDE; after shutdown_codesys, the next tool call relaunches it. Headless spawns are avoided because their modal dialogs are invisible (calls just abort), they hold .project locks, and they leave orphaned MSP-Con.exe processes behind.

Detect Installed Versions

msp-con-mcp-sp21-plus --detect

Scans Program Files and Program Files (x86) for MSP-Con installations.

Troubleshooting

MSP-Con not found Verify the path with --detect. The executable is typically at: C:\Program Files\MSP-Con 3.5.XX.X\MSP-Con\Common\MSP-Con.exe

Project file locked Another MSP-Con instance may have the project open. Close it first or use persistent mode so there's only one instance. The launcher will refuse to spawn a second MSP-Con.exe.

Watcher timeout (persistent mode) If the watcher doesn't signal ready within 60 seconds, check:

  • MSP-Con path and profile are correct
  • No modal dialogs are blocking MSP-Con startup
  • Try --verbose for detailed logging

UI briefly pauses during commands (persistent mode) The watcher executes commands on the primary thread and yields between polls, so the UI stays responsive between commands. During synchronous MSP-Con API calls (compile, project open), the UI may briefly pause — this is expected and normal. If a command hangs, check the MSP-Con messages window for modal dialogs or errors.

Command timeout Default is 60s (120s for compile and download). Increase with --timeout <ms>. Check MSP-Con messages window for errors.

Online/runtime tools fail The online tools (connect_to_device, read_variable, etc.) require:

  • A device/gateway configured in the MSP-Con project
  • The project to be compiled successfully before connecting
  • A reachable PLC or MSP-Con SoftPLC runtime

Development

# Install dependencies
npm install

# Build (compiles TypeScript + copies Python scripts)
npm run build

# Run all tests
npm test

# Type check only
npm run typecheck

# Run tests in watch mode
npm run test:watch

Project Structure

src/
  bin.ts              CLI entry point
  server.ts           MCP tool/resource registration (102 tools, 3 resources)
  launcher.ts         MSP-Con process management
  ipc.ts              File-based IPC transport
  headless.ts         Headless fallback executor
  script-manager.ts   Python template loading + interpolation
  types.ts            Shared TypeScript types
  logger.ts           Structured stderr logging
  scripts/            Python scripts (watcher + helpers + tool scripts)
tests/
  unit/               Unit tests (IPC, script manager, launcher)
  integration/        Integration tests (script pipeline, manual MSP-Con tests)
  mock_watcher.py     Standalone watcher for testing without MSP-Con

Credits

License

MIT