Skip to main content
Glama

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.

<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

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:

docs/HANDOFF_DEEPSEEK.md

Related MCP server: docpack MCP Server

Quick start

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

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:

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:

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.

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:

{
  "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

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:

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.

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:

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

Measured (451 samples, ~10 ms):

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

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

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:

$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:

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

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

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.

Related MCP Connectors

Related MCP Servers