# Mcptoolkit For Codesys

> MCP server that lets Claude (or any Model Context Protocol client) drive the CODESYS V3.5 SP22 PLC IDE via its Python ScriptEngine - projects, POUs, build, devices, libraries, and live online control.

- **Type:** MCP server
- **Install:** `agentstack add mcp-zbest1000-mcptoolkit-for-codesys`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [zbest1000](https://agentstack.voostack.com/s/zbest1000)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [zbest1000](https://github.com/zbest1000)
- **Source:** https://github.com/zbest1000/mcptoolkit-for-codesys

## Install

```sh
agentstack add mcp-zbest1000-mcptoolkit-for-codesys
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# mcptoolkit-for-codesys

**Drive the CODESYS V3.5 SP22 PLC IDE from Claude — a Model Context Protocol server.**

[](https://github.com/zbest1000/mcptoolkit-for-codesys/actions/workflows/test.yml)
[](https://github.com/zbest1000/mcptoolkit-for-codesys/releases)
[](LICENSE)

A Model Context Protocol server that drives the **CODESYS V3.5 SP22** IDE through
its Python ScriptEngine. It lets Claude (or any MCP client) open projects, create
POUs/DUTs/GVLs with methods and properties, build, add and version devices, manage
libraries, and log into a live PLC to read, write, and force variables.

> **New to this?** Start with the **[Setup Guide](SETUP.md)** — a plain-English,
> step-by-step install that assumes no programming experience.
>
> **Want to understand how it works?** The **[`docs/`](docs/)** folder explains
> each part in plain language: [concepts](docs/concepts.md), the
> [watch page](docs/dashboard.md), [reliability](docs/reliability.md),
> [configuration](docs/configuration.md), and [security](docs/security.md).

**Status — v0.2.2, verified end-to-end on a real CODESYS V3.5 SP22 Patch 1
instance.** Everything from creating a project through the full online cycle
(login → run → read live values → write → stop → logout) works against a running
soft PLC, driven through the server's **82 tools**. It is covered by **199 host
unit tests plus 6 live integration tests** — you can run the live ones yourself
with `MCPTOOLKIT_LIVE=1 pytest tests/integration`. It should also work on SP19 and
up wherever the scripting API matches, though only SP22 has been exercised. The
full history is in [`CHANGES.md`](CHANGES.md).

## Contents

- [Feature highlights](#feature-highlights)
- [Why another CODESYS MCP](#why-another-codesys-mcp)
- [How it works](#how-it-works)
- [Install](#install)
- [Configure Claude Desktop](#configure-claude-desktop)
- [Example: a first session](#example-a-first-session)
- [Remote access over SSH](#remote-access-over-ssh)
- [Environment variables](#environment-variables)
- [Reliability & modal-dialog defense](#reliability--modal-dialog-defense)
- [Observability](#observability)
- [Tools](#tools)
- [Version control](#version-control)
- [What's verified](#whats-verified)
- [Known limitations](#known-limitations)
- [Roadmap](#roadmap)
- [Security model](#security-model)
- [Architecture notes & gotchas](#architecture-notes--gotchas)
- [Acknowledgements](#acknowledgements)
- [License](#license)

## Feature highlights

- **Full IEC authoring** — projects, folders, POUs (Program/FB/Function in any
  IEC language), DUTs (struct/enum/union/alias), GVLs, FB methods + properties,
  declaration/implementation round-trip, variable-level editing, tree
  introspection, PLCopenXML import/export, and a git-friendly mirror export.
- **Build pipeline** with structured messages (severity/source/position) and a
  `build.validate` lint report that names root causes.
- **Task configuration** — list/set interval, priority, watchdog; create tasks.
- **Device tools** — search the 3000+ device repository, add devices
  (PLC → fieldbus chains, e.g. EtherNet/IP), and `device.update` to fix
  device-descriptor library pins.
- **Library management** — add/remove references, diagnose missing libraries,
  search disk, and an `install_missing` orchestrator that auto-fixes version
  pins; install packages via APInstaller.
- **Online/runtime** — login (user-supplied or env-var credentials), download,
  start/stop/reset, read/write/force live variables, timestamped monitoring
  snapshots. Physical-impact ops are `confirm`-gated.
- **Reliability** — heartbeat + liveness detection, hung-watcher auto-recovery,
  process adoption (no duplicate IDEs), and a background modal-dialog guard.
- **Observability** — structured JSON logs + an optional read-only web
  dashboard, plus a hang-proof `codesys.diagnose` tool.

## Why another CODESYS MCP

Earlier community CODESYS MCP projects inspired this one (see
[Acknowledgements](#acknowledgements)), but it is a from-scratch implementation
built around a few deliberate engineering choices that keep it stable on current
service packs:

- **A single-threaded loop on the IDE's primary thread** — no `clr.AddReference`
  for threading and no `execute_on_primary_thread` (removed in SP21.5). It is the
  only model that stays reliable on recent service packs.
- **Atomic file-based IPC** rather than a socket — requests and results are
  exchanged as files (written `*.tmp` then renamed), so it survives a frozen IDE
  and never reads a half-written message.
- **Pydantic-validated wire contracts** on the host side, so a malformed call
  fails cleanly before it ever reaches CODESYS.
- **Self-registering handler modules** — each tool group is one small Python file
  that registers itself, which keeps the 82-tool surface easy to extend.

Breadth (82 tools across 10 areas), dependable unattended operation (see
[Reliability](#reliability--modal-dialog-defense)), and a documented safety model
(see [Security model](#security-model)) round it out — each covered in its own
section below rather than repeated here.

## How it works

```
 Claude ─── stdio ───▶ mcptoolkit_for_codesys.server (CPython 3.11+)
                              │
                              │ writes commands/.json
                              ▼
                       /
                              ▲
                              │ writes results/.json
                              │
                       CODESYS.exe --runscript=watcher.py  (IronPython 2.7)
                              │
                              ▼
                       CODESYS Scripting API
                       (projects, online, system, ...)
```

**Two programs, two Pythons.** CODESYS can only be scripted from *inside* its own
embedded Python (IronPython 2.7), which is too old to speak modern MCP. So a
modern-Python **server** handles Claude, and a small **watcher** running inside
CODESYS does the actual work. The server supervises one long-lived `CODESYS.exe`
running `watcher.py` on the IDE's primary UI thread (it yields via
`system.delay()` and never touches `System.Threading` — the only model that stays
stable on SP21.5+).

**The `` is the channel, not a scratch folder.** The two halves can't
call each other directly, so they exchange messages as files in one shared
folder: the server drops a request in `commands/`, the watcher writes the answer
to `results/`. The same folder also holds `watcher.ready` (a PID marker), a
`watcher.heartbeat`, a `STOP` sentinel, the staged `watcher.py`, and the log.
Three things follow:

- **Both halves must point at the same workdir**, or they can't talk to each
  other. (This is why a remote SSH setup pins `--workdir` on both sides.)
- **It is the trust boundary.** File IPC has no authentication — whoever can write
  into `commands/` can drive CODESYS. Keep it a private, per-user folder; never a
  shared or network path (see [Security model](#security-model)).
- **One watcher per workdir.** Two IDEs sharing one folder would grab each other's
  messages and corrupt results, so a second server *adopts* a running watcher
  rather than spawning a rival (see [Reliability](#reliability--modal-dialog-defense)).

Why files instead of a socket? IronPython 2.7's networking is fragile and the IDE
can freeze on a dialog; files survive both, and each write is atomic (`*.tmp` +
rename) so a half-written message is never read.

**Startup is lazy.** CODESYS takes 30–90 s to open, so the server spawns it on the
*first tool call*, not at launch — otherwise Claude's ~60 s connect timeout would
trip every time. The first tool call is slow; the rest are fast.

## Install

Requires:

- Windows + CODESYS V3.5 SP22 installed via APInstaller (any SP19+ should also
  work; only SP22 is the stated test target).
- Python 3.11+ on the host.

```
cd "C:\path\to\mcptoolkit-for-codesys"
py -3.11 -m venv .venv
.venv\Scripts\activate
pip install -e .
```

For development (run the test suite):

```
pip install -e .[dev]
pytest
```

The host test suite (**199 tests**) covers: Pydantic schemas, install discovery
from APInstaller JSON, the full tool registry shape, end-to-end IPC against a
fake watcher, input validation, watcher liveness/adoption, error envelopes,
the dashboard status builder, and the online safety controls (confirm-gate +
env-var credentials). The watcher itself is IronPython 2.7 inside CODESYS and
isn't unit-tested — it's exercised by the **live integration suite** under
`tests/integration/` (gated by `MCPTOOLKIT_LIVE=1`):

```
MCPTOOLKIT_LIVE=1 pytest tests/integration -v
```

That suite spawns/adopts a watcher, builds a full project, asserts
`device.update` takes the build 17 → 0 errors, and round-trips the online
surface. It's off by default so the normal `pytest` run stays CODESYS-free.

## Configure Claude Desktop

Add to `%APPDATA%\Claude\claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "codesys": {
      "command": "C:\\path\\to\\mcptoolkit-for-codesys\\.venv\\Scripts\\mcptoolkit-for-codesys.exe",
      "args": ["--sp", "22"],
      "env": {
        "MCPTOOLKIT_WORKDIR": "C:\\Users\\you\\AppData\\Local\\mcptoolkit-for-codesys"
      }
    }
  }
}
```

**Command-line arguments** (pass these in the `args` list):

| Argument | Default | Purpose |
|---|---|---|
| `--workdir ` | `%TEMP%\mcptoolkit-for-codesys` | The IPC mailbox folder (`commands/` + `results/`). |
| `--sp ` | highest installed | Pin a CODESYS service pack, e.g. `--sp 22`. |
| `--headless` | off (IDE visible) | Launch CODESYS with `--noUI`. |
| `--log-level ` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |

**Args vs. environment variables — which wins.** Every flag above also has an
`MCPTOOLKIT_*` env-var equivalent, and you can set it in the config's `args` list
*or* its `env` block. They aren't redundant — they're layered:

```
command-line flag   >   environment variable   >   built-in default
```

Use an **argument** for an explicit, per-launch setting; use an **environment
variable** for an ambient one you set once. A few settings are **env-only on
purpose**: `CODESYS_EXE` / `CODESYS_PROFILE` (this machine's install paths — set
once, not per launch), the `MCPTOOLKIT_DEV` debug gate, and credential
*references* (`username_env` / `password_env`) that keep secrets out of `args`,
which are recorded in the conversation transcript. Full reference:
[Environment variables](#environment-variables) and
[docs/configuration.md](docs/configuration.md).

The first call from Claude spawns the IDE (visible by default; pass
`--headless` to launch with `--noUI`).

**Running Claude and CODESYS on different machines?** You can drive a CODESYS IDE
on another PC over SSH — and still watch the IDE on that PC's screen while Claude
operates it. See [Remote access over SSH](#remote-access-over-ssh) below.

## Example: a first session

You drive everything through Claude in plain language — you never call tools
directly. For example:

> **You:** Create a standard CODESYS project at `C:\work\demo.project`, add a
> function block `Motor` with a `Start` method, build it, and tell me whether it
> compiles.

Claude calls, in order:

1. `codesys.project.create_standard` — copies the shipped Standard template
   (Device + Application + MainTask + PLC_PRG) to that path and opens it.
2. `codesys.pou.create` — adds the `Motor` function block.
3. `codesys.pou.create_method` — adds `Start` on `Motor`.
4. `codesys.build.build` — compiles and returns structured errors/warnings.

…then summarizes the result for you. The **first** call opens CODESYS
(~60–90 s); the rest are quick, and you can watch each step happen in the IDE
window. More things you can just ask for:

- *"Open `C:\plant\line2.project` and list any missing libraries."* →
  `project.open` (which auto-attaches a diagnosis) + `library.diagnose`.
- *"Log into the PLC, start it, and read `PLC_PRG.iCounter`."* → `online.login`
  → `online.start` (Claude must pass `confirm: true` — it actuates equipment) →
  `online.read`.
- *"Export the project as a git-friendly source tree under `.\src`."* →
  `project.mirror_export`.

The full list of 82 tools is in [Tools](#tools) below.

## Remote access over SSH

You can run **Claude on one PC and CODESYS on another** on the same network.
MCP's stdio transport means the client launches the server as a subprocess — so
set that command to `ssh`, and it runs the server on the remote machine and pipes
the JSON-RPC stream back:

```json
{
  "mcpServers": {
    "codesys": {
      "command": "ssh",
      "args": ["@", "C:\\mcp\\codesys-mcp-stdio.cmd"]
    }
  }
}
```

The server, launcher, and config are otherwise unchanged — SSH just carries the
same stdio stream, and adds the authentication + encryption the local file IPC
deliberately lacks. Three things to know:

- **Seeing the IDE.** A process launched by SSH runs in a non-interactive Windows
  session, so a CODESYS it *spawns* is invisible on the remote screen. To watch
  the IDE there, start it yourself first with the `start-codesys-visible` helper;
  the server then **adopts** that running instance instead of spawning a hidden
  one. (Don't need to see it? Add `--headless`.)
- **Authentication.** Use an SSH **key** — the headless subprocess can't answer a
  password prompt. And no `-t`/`-tt` in the `args`: a forced TTY corrupts the
  JSON-RPC stream.
- **Security.** This exposes live PLC control to a remote machine, so keep it on a
  private network or VPN, behind key auth.

Editable launcher templates are in
[`examples/remote-ssh/`](examples/remote-ssh/), and the full walkthrough —
enabling OpenSSH, key setup, the day-to-day flow, and troubleshooting — is in
**[REMOTE.md](REMOTE.md)**.

## Environment variables

Set these in the config's `env` block or your shell. A command-line flag
overrides its env-var equivalent (see the precedence note above).

| Variable | Purpose |
|---|---|
| `MCPTOOLKIT_WORKDIR` | Where `commands/` + `results/` live. Default: `%TEMP%/mcptoolkit-for-codesys`. |
| `CODESYS_EXE` | Override discovery and use this `CODESYS.exe`. |
| `CODESYS_PROFILE` | Required when `CODESYS_EXE` is set; path to a `.profile.xml`. |
| `MCPTOOLKIT_SP` | Prefer this CODESYS SP (e.g. `22`). |
| `MCPTOOLKIT_HEADLESS` | `1` to launch with `--noUI`. |
| `MCPTOOLKIT_LOG` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |
| `MCPTOOLKIT_DEV` | Set to `1` to register the `_introspect` and `_eval` diagnostic ops in the watcher. These are NOT exposed as MCP tools — they're callable via direct file IPC into `/commands/` — but `_eval` evaluates arbitrary IronPython in the watcher's namespace, so it's off by default. Useful for development/debugging API drift. Alternative: drop a sentinel file at `/dev.flag` (same effect, no env-var needed). |

## Reliability & modal-dialog defense

The watcher runs single-threaded on the CODESYS UI thread, so a modal
dialog freezes it. Three layers keep the server drivable unattended:

1. **Prevention** — the watcher sets `system.prompt_handling =
   ForwardSimplePrompts | LogSimplePrompts | LogMessageKeys` at startup, so
   simple prompts are auto-answered rather than shown as modals.
2. **Heartbeat + liveness** — the watcher writes `/watcher.heartbeat`
   each loop tick (idle) and at each op boundary (busy + op + deadline). The
   host classifies it healthy / **hung** (idle-stale >30s, or busy past the
   op's deadline + grace) / **dead** (PID gone). A hung watcher is killed and
   respawned on the next call; a restarted host **adopts** an already-running
   watcher instead of spawning a duplicate.
3. **Dialog guard** — a host-side background task auto-confirms the watcher's
   own safe dialogs (storage-format upgrade, save prompts) by clicking
   Yes/OK/Continue. It never clicks No/Cancel; an unrecognized dialog is left
   alone and surfaced.

`codesys.diagnose` is the **hang-proof** health check: it reads only host-side
state (PID, heartbeat, the IDE's visible windows) and returns instantly even
when every other tool

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [zbest1000](https://github.com/zbest1000)
- **Source:** [zbest1000/mcptoolkit-for-codesys](https://github.com/zbest1000/mcptoolkit-for-codesys)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-zbest1000-mcptoolkit-for-codesys
- Seller: https://agentstack.voostack.com/s/zbest1000
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
