Skip to main content
Glama
README.md
> **A note from the author**
>
> I'm an automation and control engineer with 13 years of experience, mostly on
> Beckhoff TwinCAT systems. Every line of code in this repository was written by
> AI, and the project is still an early draft — yet it already writes PLC code
> that makes me feel outclassed, and it drives TwinCAT 3.1.4026.27 entirely on
> its own.
>
> My goal is a fully automated programming system that never requires TwinCAT 3
> to be opened by hand — the same way I haven't opened Visual Studio in years.
> I'll let you know when I get there. Until then: enjoy what AI makes possible,
> before it takes your job.

---

# TC-MCP - TwinCAT 3 knowledge and engineering agent

Public repository: <https://github.com/bonnypro/TwinCAT3-MCP>

`main` is the DSH-first line of this repository: DSH workflow, CLI-driven
local-simulation acceptance, waveform and build diagnostics. The original
OpenCode MCP integration lives on the `opencode` branch, which is the port
target: features finished and verified here are ported there.

TC-MCP is an agent workspace for **the whole TwinCAT 3 ecosystem**: XAE/XAR,
PLC/ST, MC3, NC PTP, NC I, CNC, EtherCAT, drives, ADS, Automation Interface,
Scope, HMI, OPC UA/IoT, licensing, TcPkg, and project automation.

The public repository contains code, tests, configuration, documentation, and
source-only TwinCAT templates. It does **not** contain the copyrighted PDF
store, extracted PDF text, the SQLite knowledge database, runtime artifacts, or
official Beckhoff compiled-library binaries.

```text
<workspace>\
  TC-MCP\          # product: MCP servers, automation code, docs, templates
  TEST\            # optional local experiments/evidence, not part of the repo
```

`TC-MCP` is intentionally product-only. Put probes, test clones, generated
runs, screenshots, and other non-MCP work in a sibling `TEST` folder. The
ignored `TC-MCP/runtime` directory is disposable execution output and should be
cleaned after acceptance runs.

## Current status

```text
F1 knowledge layer:        working (official PDFs + SQLite FTS5)
F2 automation layer:       working (local UM simulation)
Tests:                     263 tests, 16 skipped by default
TwinCAT:                   3.1.4026.27
TF5500 MC3:                4.0.19
Python:                    3.15+
Node:                      26+
ADS target:                127.0.0.1.1.1:851
```

Recently completed:

- `MC_ExternalSetpointGenerator` reproduces the LoadControl waveform
  (0 → 1000 → hold 1 s → 0) on an MC3 axis.
- `templates/mc3_minimal` is a working minimal MC3 project with JobTask,
  MC-instance data areas, simulation drive, and full cyclic mapping.
- `tc_write_pou` supports real POU methods (`methods` array) instead of
  stuffing `METHOD` blocks into the declaration text.
- License preflight: `python scripts\check_licenses.py`.
- TwinCAT-specific dialogs are suppressed with `TcAutomationSettings.SilentMode`
  in addition to `dte.SuppressUI`.
- `tests/test_real_mc3.py` automates the `templates/mc3_minimal` acceptance:
  build, activate, `MC_Power`/`IsInControl`, trigger the external setpoint generator.
- Build failures now carry the PLC compiler errors (`file(line) : error: C0046:`)
  from the XAE Build pane, not only MSBuild's generic message.
- `tests/test_real_mc3_waveform.py` captures the generator, `FB_LoadControl`, and
  the axis in one 10 ms PLC buffer and scores the waveform (generator vs
  LoadControl max 10, mean 0.88; axis vs generator max 4, mean 1.33).
- `tests/test_real_mc3_move.py` drives `MC_MoveAbsolute` on the simulated MC3
  axis: target 100, velocity 50, `Done`, `ErrorId=0`, no overshoot, final error 0.
- `tests/test_real_mc3_relative.py` drives `MC_MoveRelative` +30 then -30: both
  `Done`, `ErrorId=0`, final error 0, ends at the start position.
- `tests/test_real_mc3_halt.py` halts an in-flight move with `MC_Halt`: stops at
  ~27.8 (trigger 25), `ErrorId=0`, then returns the axis to 0.
- `tests/test_real_mc3_stop.py` aborts a move with `MC_Stop`, blocks motion until
  `Execute` is released, then homes the axis.
- `tests/test_real_mc3_setposition.py` re-zeroes the axis with `MC_SetPosition`
  and proves the next absolute move uses the new zero.
- `tests/test_real_mc3_status.py` reads the axis state machine with
  `MC_ReadStatus`: standstill → discrete motion → standstill.
- `tests/test_real_ram_upset.py` re-implements `TEST/PMAC/72RamUpset.pmc` with
  `MC_MoveAbsolute` + `TON` (force modelled from penetration) and captures one
  cycle's position/force curves into CSV/JSON/SVG.
- `tests/test_real_ram_insert.py` ports `71RamInsert.pmc`: absolute move to
  `INSERT_POS`, wait inside the in-position window, position curve.
- `tests/test_real_clamp_ram.py` ports the stack branch of `70ClampRam.pmc`:
  clamp approach, simulated LVDT seat detection with `MC_Halt`, stack read.
- `tests/test_real_ram_stroke.py` ports `74RamStroke.pmc`: stroke move and
  arrival window (the closest existing program after missing 69).
- `tests/test_real_mc3_error.py` proves the PLC error path: `Jerk := 0` surfaces
  as `0x81AB / 33195`, then `MC_Reset` clears it and a valid move works again.
- DSH is the primary agent environment; finished features are ported to the
  `opencode` branch.
- `scripts\tc.ps1` is a thin wrapper for doctor / license / test / accept / kb /
  status, so the common tasks are one command each.

Detailed handoff for the next agent:

```text
docs/HANDOFF_DEEPSEEK.md
```

## Quick start

Requirements: Windows, TwinCAT 3 XAE/XAR with standard PLC libraries, Python
3.15+, Node.js 26+, Git, and DeepSeek Harness (DSH).

```powershell
git clone https://github.com/bonnypro/TwinCAT3-MCP.git TC-MCP
cd TC-MCP
python -m pip install --user -r requirements.txt
python -m unittest discover -s tests -q
```

Or use the thin wrapper for the common tasks:

```powershell
pwsh scripts\tc.ps1 runtime
pwsh scripts\tc.ps1 test
pwsh scripts\tc.ps1 accept
pwsh scripts\tc.ps1 summary
pwsh scripts\tc.ps1 kb "MC_LoadControl"
pwsh scripts\tc.ps1 license
```

DSH is the primary environment: open the workspace and drive the project with
Python directly (`python -m app.cli ...`, `scripts\...`, `python -m unittest`).
The `tc-auto` / `tc-mcp` MCP servers remain available for MCP clients but are
not required for DSH work.

`tc-mcp` starts without a database; run `python -m app.cli update` once to
download the official PDFs and create `data/knowledge.sqlite`. That is a large
download and is intentionally not part of the repository.

The OpenCode integration (`.opencode/`, `opencode.jsonc`, `/tc-*` commands)
lives on the `opencode` branch; it is not part of this tree. Features are
ported there after they are finished and verified on `main`.

Before any MC3 run, check the local trial/valid license:

```powershell
python scripts\check_licenses.py
```

Required for MC3: `TC3 PLC`, `TC3 ADS`, `TC3 MC3 Base`, `TC3 MC3 Axis`.

## MCP servers

Both servers are product capabilities for MCP clients. DSH drives the project
through Python directly and does not require them.

### `tc-mcp` - read-only knowledge

- `search` - search the local Beckhoff PDF knowledge base with citations
- `get_document` - read an indexed document
- `status` - counts and last ingest time

The knowledge policy is official Beckhoff PDFs only. No InfoSys HTML crawling,
no local reference corpus.

### `tc-auto` - local simulation automation

`automation_mcp_server.py` is a stdio MCP server with a persistent XAE/ADS
session. It is local-simulation only.

```text
tc_status
tc_revision
tc_list_templates
tc_create
tc_write_pou
tc_write_dut
tc_write_gvl
tc_plc_add_library
tc_plc_list_libraries
tc_project_info
tc_project_tree
tc_archive_export
tc_snapshot
tc_restore
tc_build
tc_activate
tc_verify_symbol
tc_ads_state
tc_ads_read
tc_ads_write
tc_ads_read_many
tc_ads_write_many
tc_ads_wait
tc_list_pous
tc_read_pou
tc_list_symbols
tc_ads_discover
tc_validate_project
tc_test_run
tc_run_loadcontrol
tc_close
```

`tc_write_pou` accepts an optional `methods` array:

```json
{
  "name": "FB_Derived",
  "declaration": "{attribute 'c++_compatible'}\nFUNCTION_BLOCK FB_Derived EXTENDS MC_ExternalSetpointGenerator IMPLEMENTS ITcMcExternalSetpointGenerator",
  "methods": [
    {
      "name": "OnCyclicSetpointGeneration",
      "declaration": "METHOD OnCyclicSetpointGeneration : HRESULT\nVAR_INPUT\nEND_VAR",
      "implementation": "OnCyclicSetpointGeneration := S_OK;"
    }
  ]
}
```

Methods are written as real `<Method>` children through
`ITcXmlDocument.DocumentXml`.

## Templates

| Template | Purpose |
| --- | --- |
| `templates/minimal/Minimal.sln` | Standard minimal TwinCAT PLC project |
| `templates/mc3_minimal/Minimal.sln` | Minimal MC3 axis + simulation drive + external setpoint generator |
| `templates/scope/loadcontrol/` | Scope YT chart bindings for LoadControl |

### Minimal MC3 axis template

```text
tc_create { name: "mc3-run", template: "templates/mc3_minimal/Minimal.sln" }
tc_build
tc_activate
tc_ads_read { symbol: "MAIN.fbPowerMC3Slave.Status", type: "BOOL" }
tc_ads_read { symbol: "MAIN.Mc3SlaveAxis.McToPlc.Std.IsInControl", type: "BOOL" }
tc_ads_write { symbol: "MAIN.bStartTest", value: "true", type: "BOOL" }
tc_ads_read { symbol: "MAIN.fbActivateMc3ExtSg.Active", type: "BOOL" }
tc_ads_read { symbol: "MAIN.fbGen.rPosition", type: "LREAL" }
```

The template contains:

- `PlcTask` + `MJT` JobTask + `MET` + `MPT`
- MC-instance `DataAreas` (`PlcToMc` / `McToPlc`)
- MC3 `Slave` axis + simulation drive
- four cyclic links (`McToPlc^Std/Set/Act`, `PlcToMc^Std`)
- `FB_LoadExtSetpoint` derived from `MC_ExternalSetpointGenerator`
- `MAIN` with `MC_Power` and `MC_ActivateExternalSetpointGeneration`

Observed on this laptop:

```text
build:    ok=True, errors=0, warnings=0
activate: ok=True
PLC:      RUN (5, 0)
MC_Power: Status=True, ErrorId=0
Axis:     IsInControl=True
Generator: bStartTest=True -> Active=True
Position: 492.0(about 0.5 s into the 1000/s ramp)
```

A TcCOM TypeSystem warning about `OTCID_PositionEncoder` may appear while
loading the MC3 axis description; it does not block build or activation.

## Examples

### LoadControl simulation

`tc_run_loadcontrol` creates a fresh project, writes `FB_LoadControl` + `MAIN`,
validates ST/PLC XML, builds, verifies TMC symbols, activates local simulation,
commands a 1000 lb setpoint, holds 1 s, releases, and returns an ADS report.

```text
max_actual_lbs: 1000.0
hold_elapsed_s: 1.01
final_actual_lbs: 0.0
complete: true
release_seen: true
```

This is a **simulated force loop**. The laptop has no TF5560 hydraulic package,
no real force sensor, no real actuator, and no safety-rated hardware.
`rActualLbs` is a deterministic PLC simulation and must not be treated as a
physical pressure test.

### MC3 external setpoint

`FB_LoadExtSetpoint` extends `MC_ExternalSetpointGenerator` and produces the
same waveform as `FB_LoadControl`:

```text
0 → 1000(1000/s, 1 s)
1000 hold 1 s
1000 → 0(2000/s, 0.5 s)
```

Measured (451 samples, ~10 ms):

```text
external setpoint vs LoadControl max error: 10.0 (one 10 ms step)
external setpoint vs LoadControl mean error: 0.0347
MC3 axis vs external setpoint max error: 4.0
MC3 axis vs external setpoint mean error: 1.325
```

Full report and evidence:

- `docs/mc3_external_setpoint.md`
- `..\TEST\evolution\mc3_external_setpoint\evidence\`

## Architecture

```text
automation/core       project/task models and DAG
automation/ports      XaePort, AdsPort, PlcValidatorPort
automation/adapters   dry-run, XAE DTE/COM, pyads, PLC XML validator
automation/services   Runner, AutomationSession, licensing
automation/security   workspace, simulation, lock, audit, safety-project policy
automation/validation ST review and XML validation
automation/examples   LoadControl and MC3 external setpoint sources
automation/evolution  self-iteration, trusted evaluation, channels, GC
automation/evaluation hidden task bank, evidence, ADS probe
app/                  PDF knowledge CLI
templates/            source-only TwinCAT templates
```

F1 is the knowledge/MCP layer. F2 adds the automation layer; it is local
simulation only and does not touch real hardware, remote targets, or safety
projects.

## Safety boundaries

- `tc-auto` is local simulation only by default.
- Target platform is `TwinCAT OS (x64)` because `XarMode` is UM.
- ADS uses `127.0.0.1.1.1:851`; no remote route or real hardware.
- Hardware activation is rejected by `ProjectSpec` and `SafetyPolicy`.
- ADS reads are read-only; `ensure_running()` is the only state write.
- One XAE, one XAR, one ADS session at a time.
- Never write to a safety project.
- Writes only under `runtime/` or `..\TEST`.
- Back up `.tsproj`, `.xti`, `.plcproj`, and source files before manual edits.

## Testing

```powershell
python -m unittest discover -s tests -q
```

Expected: `Ran 263 tests ... OK (skipped=16)`.

Real-XAE integration tests are opt-in, local-simulation only, and need DSH full
access because the file sandbox blocks ADS local IPC:

```powershell
$env:TC_MCP_REAL_XAE='1'
python -m unittest tests.test_real_mc3 -v
python -m unittest tests.test_real_mc3_waveform -v
python -m unittest tests.test_real_mc3_move -v
python -m unittest tests.test_real_mc3_relative -v
python -m unittest tests.test_real_mc3_halt -v
python -m unittest tests.test_real_mc3_stop -v
python -m unittest tests.test_real_mc3_setposition -v
python -m unittest tests.test_real_mc3_status -v
python -m unittest tests.test_real_ram_upset -v
python -m unittest tests.test_real_ram_insert -v
python -m unittest tests.test_real_clamp_ram -v
python -m unittest tests.test_real_ram_stroke -v
python -m unittest tests.test_real_build_diagnostics -v
python -m unittest tests.test_real_xae tests.test_real_session -v
python -m unittest tests.test_real_mc3_error -v
```

Or run all of them, one process per module (recommended; avoids a stale
TcXaeShell COM object leaking into the next test). The script retries a failed
module once and writes `TEST/evolution/real_acceptance/latest.json`:

```powershell
python scripts\run_real_acceptance.py
```

`tests.test_real_mc3` builds, activates, and exercises `templates/mc3_minimal`
on PLC 851 (`MC_Power`, `IsInControl`, external setpoint generator).
`tests.test_real_mc3_waveform` composes `FB_LoadControl` and a 300-sample
waveform buffer into the same project, then compares the external setpoint
generator, the reference FB, and the MC3 axis.
`tests.test_real_build_diagnostics` compiles a deliberately broken POU and
asserts the PLC compiler error (file, line, `C0046`) reaches `BuildResult`.
Run `python scripts\check_licenses.py` first; the first two need MC3 licenses.

## Repository layout

```text
TC-MCP\
  app\                    PDF knowledge CLI
  automation\             F2 automation core, adapters, services, evolution
  config\                 sources.json, automation.toml, eval/, evolution.toml
  docs\                   architecture, LoadControl, MC3 report, handoff
  scripts\                checks, runners, Scope, evolution, MCP launcher
  templates\              minimal, mc3_minimal, scope
  tests\                  unit tests; real-XAE tests opt-in
  automation_mcp_server.py   tc-auto
  mcp_server.py              tc-mcp
  AGENTS.md               project rules
  README.md               this file
```

## Documentation

- `docs/HANDOFF_DEEPSEEK.md` - detailed handoff and backlog
- `docs/mc3_external_setpoint.md` - MC3 external setpoint report
- `docs/loadcontrol.md` - LoadControl simulation
- `docs/architecture.md` - architecture
- `docs/ACCEPTANCE.md` - what every real acceptance test proves
- `docs/PORTING.md` - how finished features are ported to the `opencode` branch
- `docs/MC3_ERRORS.md` - MC3/TwinCAT error codes seen here and their fixes
- `docs/PITFALLS.md` - blocker log, triage order and the regression guards added for each lesson
- `docs/RELEASE_NOTES.md` - what each `v0.x` tag contains
- `docs/STATUS_2026-11-05.md` - work done today, current runtime blockage and recovery steps
- `docs/PUBLISHING.md` - publish checklist
- `docs/evolution/` - evolution control-plane notes (historical snapshots)
- `AGENTS.md` - project rules and command list

## Publishing

```powershell
git push origin main:main
```

`main` is the DSH release line. The OpenCode tree lives on `origin/opencode`
and receives ports once a feature is finished here; do not force-push over it.

## License

MIT; see `LICENSE`. Third-party notices are in `THIRD_PARTY_NOTICES.md`.
`PyMuPDF` is optional and not distributed; the default parser is `pypdf`.