twincat-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| TCMCP_DOCS | No | Infosys documentation lookup on/off. Default 1. | 1 |
| TCMCP_MODE | No | Start mode: `readonly` / `guarded` / `full`. | guarded |
| TCMCP_CONFIG | No | Path to the TOML config file (otherwise `./twincat_mcp.toml` or `~/.twincat_mcp/config.toml`). | |
| TCMCP_PYTHON | No | Python executable used by the launcher (discovery order: TCMCP_PYTHON → `py -3` → `python` → %LOCALAPPDATA%\Programs\Python\Python3*). | python |
| TCMCP_CONFIRM | No | `0` → destructive ops in full mode need no confirmation. | 1 |
| TCMCP_PROGIDS | No | `;`-separated DTE ProgIDs to try, in order. Default: 17.0, 15.0, VS 18/17/16. | |
| TCMCP_AMS_PORT | No | Target ADS port. Default 851. | 851 |
| TCMCP_MAX_MODE | No | Ceiling for `tc_set_mode`. Default = mode. | |
| TCMCP_SOLUTION | No | `.sln` to attach to; also sets project root. | |
| TCMCP_AUDIT_LOG | No | JSONL audit file path. | ~/.twincat_mcp/audit.jsonl |
| TCMCP_AMS_NET_ID | No | Target AmsNetId. Default: local. | |
| TCMCP_DOCS_BOOKS | No | `;`-separated default Infosys books to index. Default: 8 core PLC libraries. | |
| TCMCP_DOCS_CACHE | No | Infosys cache directory. | ~/.twincat_mcp/infosys |
| TCMCP_HTTP_TOKEN | No | Bearer token gate for HTTP mode (`--http` / `--token`). | |
| TCMCP_WRITE_DENY | No | `;`-separated fnmatch patterns for denied writes (deny always wins). Default empty. | |
| TCMCP_AUDIT_READS | No | Also log reads. Default 0. | 0 |
| TCMCP_WRITE_ALLOW | No | `;`-separated fnmatch patterns for allowed writes (deny always wins). Default empty. | |
| TCMCP_ALLOW_SAFETY | No | Set to `1` to allow XAE edits to `TISC` / `^Safety` paths; otherwise refused. | |
| TCMCP_DOCS_EXCLUDE | No | `;`-separated exclude regexes for Infosys books. | |
| TCMCP_PROJECT_ROOT | No | Folder for `proj_*` tools. Default: solution folder. | |
| TCMCP_TCUNIT_RESULT | No | TcUnit result path on the target. | C:\TwinCAT\3.1\Boot\TcUnit_xUnit_testresults.xml |
| TCMCP_XAE_ATTACH_ONLY | No | `0` → `xae_attach` may launch a shell. Default 1. | 1 |
| TCMCP_DOCS_WEB_FALLBACK | No | Enable sitemap-verified web fallback for Infosys search. Default 1. | 1 |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| tc_doctorA | Environment check: platform, pyads/router, local AMS NetId, XAE ProgIDs in the
registry, running XAE shells, project root, safety mode, whether a confirmation prompt can reach the user
in this session ( |
| tc_safety_statusA | Current safety mode (and where it came from), ceiling, allow/deny lists, pending confirmation tokens. |
| tc_set_modeA | Switch safety mode: readonly | guarded | full. Lowering is free. Escalating (guarded→full, readonly→guarded) is capped at max_mode AND needs a confirmation (prompt or confirm_token) — an agent must not be able to widen its own permissions silently. |
| tc_allow_writeA | Add (or remove) a write allow-list pattern for THIS session only — the in-session alternative to editing twincat_mcp.toml and reconnecting. fnmatch, case-insensitive: 'GVL_Test.*', 'MAIN.fbDispense.rSetpoint'. Control-class: needs a confirmation (prompt or confirm_token from tc_request_confirmation(action='tc_allow_write', target=''), or target='-' to remove). The deny list still wins; the change is audited and lost at the next server start. |
| tc_reload_configA | Re-read twincat_mcp.toml (and TCMCP_* env) without a restart. Applies: safety allow/deny lists, confirm_destructive, confirm_ttl_s, docs settings, naming prefixes, dialog rules, tcunit result path. Reports — but does not apply — changes that need a restart: ads target/port, solution path, project root, mode/max_mode (use tc_set_mode). |
| kb_statusA | Knowledge base (layout and rules: docs/KNOWLEDGE_BASE.md): where it is (or the question to ask the user if
no folder was chosen yet), the project folders indexed for kb_search, the ESI index for hw_lookup — and under
|
| kb_rebuildA | Rebuild one index of the knowledge folder from its source on this PC — ONLY with the user's approval (docs/KNOWLEDGE_BASE.md rule 6: the user is asked, or pass the confirm_token of tc_request_confirmation(action='kb_rebuild', args={'index': …})). index: 'infosys' (the Help Viewer package, the plugin's extractor, in its own process — minutes; the current index stays in use until the new one is complete and then replaces it), 'manuals' (the PDFs in manuals, in its own process), 'esi' (the ESI files in manuals\ and TwinCAT's ESI folders → index\esi.db, in its own process — minutes; the current index stays in use until the new one is complete). Without force an index that is complete and current is left as it is. Nothing is deleted. kb_status / tc_doctor show the progress. |
| kb_set_locationA | Choose the folder for the knowledge database — ONLY with a path the user gave you (or use_fallback=true once the user agreed to D:\TwinCAT-MCP\knowledge; not both). The folder is checked: a folder (not a file, not a drive root) on a local fixed drive (no network share / mapped drive / USB), not inside OneDrive/Dropbox/Google Drive/iCloud/Box, not inside the plugin folder, writable, >= 1 GB free — a failing condition is named and nothing is changed. An existing database is never moved or deleted. Without arguments it returns the current location (and changes nothing). A change is refused in readonly mode and audited, as every EDIT tool (N127). |
| kb_add_projectA | Index a project folder for kb_search (cross-project pattern search) — ONLY a folder the user named. Reads
.TcPOU/.TcDUT/.TcGVL/.TcIO beneath it into the knowledge database; nothing is indexed by default. Refused: the
plugin's folders, the knowledge folder, a folder inside or around one already indexed (its units would be
there twice). The answer names the files that could not be read ( |
| kb_remove_projectA | Remove a project folder from the knowledge base (index entries only; the files are untouched). Refused in readonly mode; audited (N127). |
| kb_reindexA | Re-index one project folder (or every changed one) after edits. Refused in readonly mode; audited (N127). |
| kb_searchA | Search the code of the project folders the user added ("alarm handler", "FB_Valve", "CONCAT"): unit, kind,
file and the matching declaration/code excerpts, best first. Names and identifiers count as their words
(FB_AlarmList: alarm, list). Units with EVERY word first, then every word as a prefix ('CounterMod' finds
FB_CounterModule); a unit whose code holds the words before one that has them only in comments
(match_in='comments'); only when none has them all, units with some words
(match='some words', with the words each unit holds; words on no unit or on most units are not used and
are listed). A unit that declares the name (variable, constant, member, enum value, its own name) comes first
( |
| hw_lookupA | EtherCAT device data from the ESI index (index\esi.db: the ESI files in the knowledge folder's manuals |
| hw_searchA | Full-text search of the ESI index: devices (names, PDOs and their entries, PDO assignments), CoE objects
(names, descriptions, sub-index names), diagnosis messages (text, description, causes, remedies) and modules.
kinds: any of 'device', 'object', 'diag', 'module' (default all; a module is found by its own name and PDOs
and by the devices whose slots take it — type, name, group). FTS5 syntax (words ANDed, "phrase", prefix*);
other text is searched as one phrase. Hits with the same title (revisions of one type, one text in several
catalogues) are one item with |
| hw_objectA | One CoE object of a device from its ESI (newest revision unless |
| hw_diagA | A diagnosis message by its text id as the diagnosis history shows it (object 0x10F3; '#x4101', '0x4101'):
message text, description, causes and remedies, class, reaction, reset — from the device's own catalogue when
|
| hw_moduleB | A module of a modular device (AX8000, EP/EL with slots, TSC …) by its ModuleIdent as hw_lookup's slots give
it: PDOs, CoE objects, diagnosis messages. With |
| tc_request_confirmationA | Issue a single-use confirm_token for a control/destructive tool: action = the tool's exact name. Easiest: pass the tool's own arguments as args (e.g. action='xae_plc', args={'action': 'login', 'plc_project_path': 'TIPC^Plc^Plc Project'}) — the server computes the target the tool will check, the same way the tool does. A target given by hand must equal it (a partial one — 'login', 'PLC.Login' — is completed); the error names the right one. Targets: xae_deploy / xae_restart_twincat / xae_activate_configuration / tcunit_run: the XAE target NetId; xae_plc: ':@'; xae_command: '@'; xae_boot_project: the plc_project_path (with '@' when activate_after_generate); xae_tree_delete: '<parent_path>^'; xae_library_add: ':'; xae_library_remove: ':' + ' ()' and ', ' when given; xae_placeholder_add: ':'; xae_placeholder_resolve: ':=' + ', ' when given; xae_placeholder_freeze: ':' ('*' for all); xae_link: ' -> ' (paths as normalised: '^', TIID) + ' [offsets /, bit]' for a part; xae_unlink: ' -> ', or ' -> (all )'; xae_links_import: ':<sha256 of the XML text, 16 hex> over <sha256 of the table now, 16 hex> (+ -)' — the dry run's confirm_target; xae_target: the new NetId; xae_dialog_resolve: ':'; ads_plc_state / ads_system_mode: the action (run, stop, reset / run, config); ads_connect: the NetId, or 'local'; ads_add_route_to_plc: the PLC's IP address; ipc_write_file / ipc_delete_file / ipc_start_process: the remote path; ipc_eventlog: the log name; ipc_reboot: the literal 'target'; rec_delete: the recording id; tc_set_mode: the mode; tc_allow_write: the pattern ('-pattern' to remove); exp_run: the experiment name; kb_rebuild: the index: 'infosys', 'manuals' or 'esi'. |
| tc_audit_tailB | Last n audit records (mutating calls, confirmations, mode changes). |
| ads_connectA | (Re)connect to a target. ams_net_id like '5.12.34.56.1.1', 'local' for this PC, None = keep current; port 851 = PLC runtime 1. Re-targeting to a NetId other than the configured one is control-class (it changes which machine every later write hits). |
| ads_statusA | Composite status: connection, PLC runtime state, TwinCAT system mode (RUN/CONFIG), device name/version. With port=N returns only the ADS state of that port (852 PLC2, 500 NC, 10000 system service, 65535 EtherCAT master 1). |
| ads_list_symbolsB | Browse the runtime symbol table. pattern is fnmatch ('MAIN.', 'valve'; bare text is wrapped in ''). type_filter substring-matches the type ('ST_', 'BOOL', 'FB_'). flag filters by symbol flag: PERSISTENT, READONLY, STATIC, ATTRIBUTES … compact=true returns 'name : type' strings only (one fifth of the tokens). |
| ads_symbol_infoB | Type, size, index group/offset, flags (READONLY, PERSISTENT …) and comment of one symbol. |
| ads_readA | Read one symbol with type-aware decoding. Structs/FB instances/arrays of structs decode into a nested tree (datatype table); TIME/DATE/DT/TOD get readable text; enums get names. Trees larger than max_chars are summarised (member names + sizes) — use ads_leaves or ads_dump for the full content. |
| ads_read_manyB | Read several symbols in one ADS sum-command. Returns {name: value | {error}}. |
| ads_writeB | Write a symbol on the running PLC. Accepts JSON scalars, lists for arrays, and literals like 'T#1s500ms', 'D#2026-10-15', '16#FF'. Returns before/after. Gated: readonly=never; guarded=write_allowlist; full=denylist only. |
| ads_write_manyA | Write several symbols ({name: value}) together. Every symbol is gated (allow/deny list) and pre-checked (type, range, enum name, array/string length, CONSTANT, pointer/interface) BEFORE anything is written — one refusal → nothing is written and every problem is listed. Then ONE ADS sum-write: the runtime applies all values between the same two task cycles (method says if it had to fall back to one-by-one). Per-item before/written/read_back; applied/failed/not_attempted if the PLC rejects one. |
| ads_io_readA | Raw process image of the PLC: area 'I' (%IB inputs) or 'Q' (%QB outputs) at a byte offset — what the task actually sees/wrote, independent of symbol names (link checks). |
| ads_read_rawC | Raw ADS read by index group/offset on any port (hex + little-endian uint). |
| ads_write_rawA | Raw ADS write by index group/offset — PLC runtime ports (851-859) only. Every symbol whose memory overlaps the written range is checked against the write allow/deny lists; the raw target itself is 'raw://'. |
| ads_watchA | Trace symbols for duration_ms. Uses ADS notifications (PLC-timestamped, every change the runtime sees at its task cycle — not 50 ms polling from this PC): change_only=true records changes, false one sample per interval_ms. Falls back to polling when notifications are unavailable (method field says which). Caps: 60 s, 5000 rows. The wait happens off the ADS workers, so writes issued concurrently land during the watch — a client that sends calls one after another still sees them after it; for stimulus→response use test_response, or rec_start + the write + rec_stop. |
| ads_wait_untilA | Block until symbol value (==, !=, >, >=, <, <=) or timeout. Uses an ADS change notification, so it returns as soon as the runtime sees the condition (met_at_plc_ms = PLC-relative timestamp of that sample); falls back to polling every interval_ms when notifications are unavailable. Returns met/value/elapsed. |
| ads_plc_stateA | PLC runtime control via ADS: run | stop | reset. 'reset' is a cold reset (all non-persistent data lost). Control-class: confirmation in guarded mode. |
| ads_system_modeA | Switch TwinCAT on the target between 'run' and 'config' mode (restarts the runtime, I/O goes down). Destructive-class. Waits (≤20 s) until the target reports the requested mode and returns before/after; reached=false when it did not get there (license / configuration error). |
| ads_ethercatA | EtherCAT master state (INIT/PREOP/SAFEOP/OP) and slave count via the master's ADS port (default 65535 for the first master; see Device → ADS tab in XAE). |
| ads_routesB | Local static ADS routes (parsed from StaticRoutes.xml on this PC). |
| ads_add_route_to_plcC | Create the PLC-side route back to this PC (UDP discovery protocol, needs the target's Windows/TC-BSD credentials). Control-class. |
| rec_startA | Start a continuous real-time recording of online PLC variables (runs in the background until rec_stop, duration_s, max_events or stop_when). Select by explicit symbols (an FB or struct instance expands to all its leaves), prefix ('MAIN.fbFill', 'GVL_Sim.aCh[1]' → every leaf under it) or pattern — the same leaves rec_scan lists for it: ''/'?' wildcards, '[ ]' literal ('GVL_Sim.aCh[1].'), a leaf is recorded when it or one of its parents matches ('*bOpen' → every bOpen). mode: 'cycle' = one PLC-timestamped sample per symbol per cycle_ms (ADS notification, cycle-accurate); 'change' = PLC-timestamped sample only when the value changes (checked every cycle_ms); 'poll' = sum-read every cycle_ms from this PC. Triggers: start_when='MAIN.bStart == TRUE' with pre_trigger_ms keeps the events before the trigger (scope-style); stop_when='MAIN.fbFill.bDone == TRUE'. Events stream to a JSONL file. duration_s is PLC time from the first sample (also while waiting for start_when); post_trigger_s ends the recording that long after the start_when trigger fired (scope-style post-trigger window). Timestamps (cycle/change) are the TwinCAT runtime's own, stored exactly with the raw bytes it sent; neighbouring variables share ONE notification (a block), which is what keeps 1 ms recordings lossless — select whole FBs, structs or arrays rather than hundreds of scattered globals. Any notification that cannot be registered fails the start (allow_partial=true records the rest and lists unsubscribed_symbols). max_delay_ms > 0 lets the runtime batch samples (fewer ADS frames; each sample keeps its own timestamp). cycle mode also records a per-cycle counter as ground truth (cycle_counter='auto' = the matching task's _TaskInfo[n].CycleCount, or your own e.g. 'MAIN.nCycleCount', or null for none) and the header carries a manifest of what was recorded from (AppTimestamp, OnlineChangeCnt, tasks, TwinCAT build, symbol version). A recording is of one program: a download / online change while it runs ends it at the last sample verified against the symbol version (program_changed); nothing sampled from the new program is written. Conditions are levels: a start_when that already holds fires at once — edge_triggers=true fires only when it BECOMES true. A condition may name one element of an array recorded as a whole (GVL_Test.aCounter[1] when GVL_Test.aCounter is recorded). engine (cycle mode): 'native' = the C++ capture service (Beckhoff AdsLib on TcAdsDll: ADS callback → ring → file, no Python on the data path), 'python' = the in-process recorder, None = recorder.engine from the config (default 'auto': native when installed). The response says which engine ran and why. After stopping: rec_verify. also_scope: also run the TwinCAT Scope configuration open in XAE (scope = its name when several are open) — StartRecord now; at rec_stop StopRecord and SaveSVD into /Scope Recordings/.svdx, the CSV .scope.csv exported from that file with Beckhoff's export tool (XAE's ExportCSV only without it, flagged), XAE's ExportCSV of what Scope holds in memory beside it (.scope.xae.csv) with both row counts compared (cross_check — a saved .svdx shorter than memory, or the reverse, is a warning), a signed link file, and the Scope View log's new ERROR / FATAL lines (scope_view_log). A .svdx still being written 15 s after SaveSVD is not exported. What Scope records (channels, sample time) is set in that configuration. With recorder.also_scope = 'ask' (default) and a Scope configuration open, leaving also_scope unset asks the user (prompt, or an error that tells you to ask and call again with true/false). After a recording Scope holds its data until it is disconnected: a recording with Scope then asks to disconnect it first — scope_disconnect=true / false is the user's answer (false: this recording without Scope). |
| rec_scanA | Enumerate the variables that could be recorded and what each would cost. source='runtime' walks the live symbol/datatype tables (every scalar leaf under prefix, or all programs/GVLs); source='project' walks the source declarations (works offline). pattern selects leaves exactly as rec_start does: '*'/'?' wildcards, '[ ]' literal, a leaf matches when it or a parent matches. Returns counts by type and by group (program.instance) with bytes/s estimates at cycle_ms, a suggested mode per leaf, and — when checklist='' — writes ~/.twincat_mcp/profiles/.checklist.csv with a 'select' column to mark (preselect = patterns already marked). Then rec_profile_save(name, from_checklist=name). |
| rec_profile_saveA | Save a named capture profile. Sources combine: explicit symbols (mode='auto' picks cycle for REAL/TIME, change for BOOL/INT/enum, poll for strings — or give cycle|change|poll), a marked checklist (path, or the name given to rec_scan), and/or {attribute 'tcmcp.record' := 'cycle'} pragmas in the source (from_attributes=true; scope limits to one program). Triggers and the default cycle are stored with the profile. rec_profile_start(name) replays it. |
| rec_profile_listC | Saved capture profiles (name, symbol count, modes, source). |
| rec_profile_getB | One profile in full: every symbol with its mode and cycle, triggers, per-group cost estimate. |
| rec_profile_deleteC | Delete a profile and its checklist file. |
| rec_profile_startB | Start recording a saved profile: one recording per (mode, cycle_ms) group, all named '/@'. Triggers given here override the profile's. Returns the recording ids; rec_profile_stop(name) stops them together, rec_stats/rec_export work per id. align (default, several groups, no start_when): every group starts on the same PLC cycle — a shared start trigger on the task's cycle counter a moment ahead — and duration_s then counts from that cycle, so all files cover the same window (D29: groups started ≈ 160 ms apart). |
| rec_profile_stopA | Stop every live recording started from this profile; returns per-recording status + stats. |
| rec_listC | Recordings of this session (compact status) and recording files on disk, newest first (paged: limit/offset; on_disk_total says how many exist); a recording with Scope: its scope state. |
| rec_statusA | Live status of one recording: samples, events, rate, missing_samples (holes in the PLC timeline, cycle mode), completeness, dropped (lost on this PC), backlog, stats_coverage, per-block report, warnings, armed/triggered, last value per symbol; with Scope: scope {state recording | finishing | finished | stop_unknown} (N182; recording with stop_refused: XAE refused StopRecord, rec_stop asks again). |
| rec_snapshotC | Most recent events of a running (or finished) recording — the live tail. |
| rec_statsA | Per-symbol statistics over the whole recording so far: count, first/last, min/max/mean, change count, BOOL rising/falling edges with timestamps, and value runs (state timeline with dwell times). Live (incremental) while complete; recomputed from the file when the ingest had to skip statistics under load, for recordings of earlier sessions, or with exact=true. |
| rec_captureA | One call for "record X for N s and give me the stats": starts a recording, waits (≤ 60 s), stops, and
returns per-symbol statistics, completeness and the rec_verify verdict (integrity + PLC timeline, plus the
optional per-cycle |
| rec_verifyA | Prove a finished recording. integrity: the file is byte-for-byte what the recorder wrote (sha256 of every
line, signed with this install's key) — 'MODIFIED' if anything was edited. timeline (cycle mode): per
notification block, holes between the PLC's own sample timestamps. counter (optional): a PLC variable that
increments every cycle and was recorded with the rest (your nCycleCount, or
TwinCAT_SystemInfoVarList._TaskInfo[1].CycleCount) must step by exactly |
| rec_readB | Read a time window of a recording from its file. wide=true: one row per PLC timestamp with one column per selected symbol; a cell is null when that symbol has no sample at that time — nothing is invented (R4). fill=true repeats the last known value into those cells and lists them per row under "_filled" (handy for 'what else was true when X happened', but a filled cell is not a sample). Works on finished recordings from earlier sessions too. |
| rec_stopB | Stop a recording, release the ADS notifications, finalize the file; returns status + stats. With Scope: its part is finished, and Scope (it now holds this recording) is closed, starts a new recording, or keeps the data (pause) — the user is asked (scope_next). |
| scope_statusB | TwinCAT Scope on this PC: where TC3ScopeExportTool.exe is (TE130x Scope View / TF3300 Scope Server) and the default export folder. |
| scope_exportA | Export a saved TwinCAT Scope recording (.svdx / .svd) to CSV with Beckhoff's TC3ScopeExportTool.exe
(command line |
| scope_listB | TwinCAT Scope configurations in the solution open in XAE (TwinCAT Measurement projects). Their channels, sample time and record time are what scope_record / rec_start(also_scope=true) record. |
| scope_recordB | Drive a Scope configuration open in XAE (Automation Interface IMeasurementScope): action = start | stop | disconnect (Scope discards what it holds and is back in Config — needed before it records again; asks for the user's confirmation unless what it holds is a recording the plugin saved) | new (disconnect, then start) | save (SaveSVD → /Scope Recordings/-.svdx, or path=.svdx; the .csv exported from that file with Beckhoff's export tool — XAE's ExportCSV only without it, flagged, and never over an existing CSV; XAE's ExportCSV of the in-memory data beside it, .xae.csv, row counts compared in cross_check; the Scope View log's new ERROR / FATAL lines). scope = its name when several are open. Affects only the Scope recording, not the PLC. One Scope, one owner (N189): while a recording's Scope part has Scope, its rec_stop / rec_delete stop and save it — start / stop / save / disconnect / new here are refused; a Scope recording started here owns Scope until action='stop'. |
| rec_exportA | Export a recording to CSV. csv_wide: one row per PLC sample time, one column per variable and per array element (name[i]); an empty cell means no sample of that variable at that time (fill=true forward-fills — handy for charts, but a cell then no longer means "sampled now"). csv_long: t_ms,symbol,value. Both include plc_filetime (the PLC's own sample timestamp) when available. Default path next to the JSONL. An out_path must be in a folder this server writes to ([server] output_dirs adds more). An existing file — also an earlier export at the default path with other symbols/fill, or of a recording that has grown since — is only replaced with overwrite=true; an identical re-export (same source bytes and parameters) is not refused. |
| rec_deleteA | Stop (if running) and delete a recording and its exports from disk. Destructive-class (field report R11): a recording is evidence — guarded mode needs tc_request_confirmation(action='rec_delete', target=); the audit keeps the deleted file's size and SHA-256 so its content stays identifiable. |
| test_expectA | Assert conditions on live values. checks: [{symbol, op, value, tolerance?}], op ∈ == != > >= < <= ~= (relative/abs tolerance) in between. within_ms>0 waits until all hold. Returns pass/fail per check with the time each one was met. |
| test_stableB | Assert a value does not move more than tolerance for duration_ms (settle check for a weight, pressure, PID output, state). |
| test_responseA | Write a stimulus ({symbol: value}) and measure the PLC-timestamped latency until
|
| test_scenarioA | Scripted FB test against the live PLC with a recording in parallel. spec = {name, record:{symbols|prefix|pattern, mode, cycle_ms}, steps:[{at_ms?, write:{sym:val}} | {expect:[checks], within_ms} | {wait_ms} | {wait_until:{symbol,op,value,timeout_ms}} | {stable:{symbol,duration_ms,tolerance}}], final_expect:[checks], timeout_ms, restore, stop_on_fail}. Every written symbol is gated before the run starts; written values are restored at the end. Returns per-step results, overall pass, and the recording id. |
| ads_app_infoB | PLC application info (TwinCAT_SystemInfoVarList._AppInfo): project/app name, online change count, boot data loaded, task count, logged-in flag, app timestamp. |
| ads_task_statsB | PLC task timing (TwinCAT_SystemInfoVarList._TaskInfo[n]): cycle time, last/min/max/mean
execution time over |
| ads_pingC | ADS round-trip time statistics to the target (router/route/network health). |
| ads_call_methodA | Invoke an FB method on the running PLC via ADS RPC. The method must carry {attribute 'TcRpcEnable'}. inputs: [{type:'LREAL', value: 12.5}, …] in VAR_INPUT order (use base types: INT not E_Enum); outputs: ['BOOL', …] in VAR_OUTPUT order. Write-class. |
| snap_saveB | Save a named snapshot of live values (expanded like rec_start) for regression comparison — parameters, recipes, whole FB instances before/after a change. |
| snap_compareB | Diff snapshot |
| snap_listC | Saved snapshots. |
| rec_pulsesB | Pulse / dwell analysis of one recorded symbol: for BOOL → pulse count, high/low widths, period, frequency, duty (fine-dosing valve pulses); for other types → state timeline with dwell times. |
| rec_latencyA | Edge-to-edge latency in a recording: from each time |
| proj_librariesA | Library versions (K3): every library reference of each PLC project — the version it asks for, the version it effectively resolves to on this PC ('*' = newest installed) and what is installed in TwinCAT's library repository. Flags a pinned version that is not installed. Reads the .plcproj and folder names only. |
| proj_interfaceB | Public interface of an FB/program: inputs/outputs/in-out with types and comments, methods (access, signature), properties (get/set), actions, extends/implements. |
| proj_lintA | Static checks over one object or the whole project: unused locals, naming prefixes
(override map via |
| proj_call_graphA | Call graph from a program/FB: FB instances it invokes, functions and methods it calls,
recursively to |
| tcunit_scaffoldA | Generate _Tests.TcPOU (TcUnit.FB_TestSuite) with Test_InitialState and one test method per public method, arranged from the FB interface, and register it in the .plcproj. Assertions are left as TODO stubs. |
| ads_dumpA | Read EVERY variable on the PLC (all top-level symbols, fully expanded through the datatype table — structs, FB instances, arrays of structs) in one pass and write a flat CSV (path,type,value,note) and a nested JSON tree built from the same values, so the two files never disagree. pattern limits the roots ('MAIN.', 'GVL_'); system symbols excluded unless include_system. With a project configured its own programs and GVLs come first; without one the order is alphabetical (origin unknown). POINTER TO / REFERENCE TO / INTERFACE values are addresses (NULL = 0x0000…0000) — the target is never expanded. An array beyond 4096 elements or a value beyond 64 KiB is not expanded: both files show the same {not_expanded, bytes, elements, head_hex}. Big symbols are read in 256 KiB pieces. max_leaves bounds CSV and JSON together; max_total_mb (MiB) bounds the CSV + JSON on disk (written_mib; the bytes read from the PLC are not counted): a row is written only when it, the root's JSON with it and the ending still to be written (the footer naming what is left out) fit — nothing is estimated. complete=false names every root that is missing or cut (skipped_roots) and every root that could not be read (failed_roots, also in the CSV note column and the JSON footer). not_on_target names the programs/GVLs of the source that have no symbol on the target, with the cause (not linked, or another program on the target). Returns counts, largest_roots, file paths and a 50-leaf preview. |
| ads_leavesB | Enumerate every scalar leaf under a symbol with byte offsets (datatype table): 'MAIN' → all program variables, 'MAIN.arrSteps' → arrSteps[1].nId … With values=true each leaf is decoded from the symbol's bytes, read in bounded pieces (a value that is not expanded: its head only); value_note says why a value is missing or partial. |
| ads_typesB | Runtime datatype table: describe one type (members with offsets/sizes, array bounds, enum literals, attributes) or list types matching a pattern. |
| proj_variablesA | Every variable the project source declares, flattened to runtime instance paths by walking programs and GVLs through FB/STRUCT definitions (MAIN.fbFill.fbValve.bOpen …). scope = one program/GVL/FB to walk instead of all. Library-typed variables stay as one item with their type. A name declared in several PLC projects is walked in the PLC project on the connected ADS port (the .tsproj's AmsPort); pin another with '@'. |
| proj_vs_runtimeA | Cross-check the source against the running PLC: variables declared in the project but not present online (not compiled / not activated / optimised away) and online variables under the same programs/GVLs that the source does not declare (stale boot project, library internals); roots_not_on_target names a program/GVL of which the target has no symbol at all, with the cause (a GVL nothing references is not linked). Use after a deploy to prove the target runs this source. |
| wf_debug_symbolB | One-call debugging dossier for a variable: live value, declaring file:line chain, the statements that write it, code excerpts around them, and a short change watch. |
| wf_fb_profileB | Profile an FB instance for duration_s: records every leaf (change mode), samples task timing in parallel, then reports most-active symbols, boolean pulse rates, state dwell timelines, silent symbols and the FB's cost on the task. Read-only on the PLC. |
| wf_release_checkA | Go/no-go before a deploy: git changes, build result, lint, source-vs-runtime, TcUnit result on the target, task health. Writes nothing to the PLC. |
| wf_project_overviewA | Orientation in one call: index summary, programs with call trees, FBs with interfaces, GVLs/DUTs, lint summary, git state. |
| rec_compareA | Before/after comparison of two recordings: per-symbol stat deltas (count, min, max, mean, changes, edges) and pulse width/period deltas for BOOLs — timing regression after a code change. |
| xae_dialogsA | Modal dialogs currently open in the attached XAE shell (title, class, text, buttons), the watchdog's auto-answer rules and its recent log. A pending dialog blocks every COM call. Any window that holds the shell modal (its main window disabled) is listed, whatever its class (WPF About / Options, WinForms): its buttons read through UI Automation, or buttons null + buttons_note when they cannot be read; unidentified when the shell is modal but no window could be named. |
| xae_dialog_resolveA | Click a button on an open XAE dialog ('OK', 'Yes', 'No', 'Cancel'…). remember=true adds an auto-answer rule for that title (never for machine-affecting prompts). Control-class; machine-affecting prompts (activate/run/download/login/delete/safety) are destructive-class. A window that is not a standard dialog is pressed through UI Automation; one whose buttons cannot be read is refused (close it in XAE) — never closed by the tool. |
| xae_io_disableA | Set/clear the Disabled flag on I/O devices under TIID (all, or the named ones) so a configuration activates without the hardware present — TcUnit on a PC, dry runs. |
| xae_linksA | Variable links of the PLC instance: every AT %I*/%Q* variable (from the instance's XML) with its link targets (the project's mapping table, ProduceMappingInfo); lists the unlinked ones — array elements too, the usual reason an output never moves — and links whose variable is gone. plc_project_path: plc_path, iec_project_path or instance_path. What could not be read is said (unlinked_count null), never counted as 0. not_restored: links the saved project keeps but XAE could not restore (RestoreInfo in the .tsproj / .xti, e.g. ANotFound after a variable moved) — ProduceMappingInfo does not list them. |
| xae_linkA | Link a variable to its I/O counterpart (ITcSysManager::LinkVariables), both as full tree paths: variable='TIPC^^ Instance^PlcTask Inputs^GVL_IO.bIn', target='TIID^Device 1 (EtherCAT)^Term 1 (EK1100)^Term 2 (EL1004)^Channel 1^Input' (xae_links gives the instance's variables, xae_tree_get the terminals'). offs_* / size_bits for a part (bits; 0 = the smaller of the two). XAE checks that both exist, are variables and fit. A whole link is refused when the two are linked already (a part may be linked besides others). Runs once; the result has what changed in the mapping table and what else the target is linked to. In XAE only — not saved, not activated. TwinSAFE ends are refused. guarded: tc_request_confirmation(action='xae_link', target=' -> ' + ' [offsets /, bit]' for a part). |
| xae_unlinkA | Remove a link (ITcSysManager::UnlinkVariables): the one between variable and target, or — target left out — every link of the variable. Full tree paths as the mapping table names them (xae_links). Refused when there is no such link, or when a link to be removed has a TwinSAFE end. Runs once; the result has what changed. In XAE only — not saved, not activated. Destructive-class: tc_request_confirmation(action='xae_unlink', target=' -> ' or ' -> (all )', n = the links it has now). |
| xae_links_importA | Import a mapping table (, as ProduceMappingInfo writes it) through ConsumeMappingInfo, which replaces ALL links of the project. mode='add': its links are added to the table as it is (nothing removed); 'replace': it is the whole new table. dry_run (default): what would be added / removed / changed — nothing done; its confirm_target is the token target the apply needs. dry_run=false: the table as it is goes to a new backup file first (~/.twincat_mcp/backups/links/), then ConsumeMappingInfo once, then the table is read again and compared; a failure says what the table holds then and how to restore it. xml text or file (on this PC, UTF-8 or UTF-16). Refused while the table has a link with a TwinSAFE end (ConsumeMappingInfo rewrites every link). Destructive-class: tc_request_confirmation(action='xae_links_import', args={xml|file, mode}) — the target is the dry run's confirm_target, ': over (+ -)' (a table changed since: refused). |
| xae_library_listA | The library references of a PLC project as XAE holds them now (TE1000 ITcPlcLibraryManager): libraries (name, version, distributor) and placeholders (default and effective resolution — '*' and what it resolves to). Flags a library referenced twice and a version that is not installed (ScanLibraries; check_installed=false skips it), and the references and requested versions that differ from the saved .plcproj (unsaved changes; an unsaved resolve / freeze cannot be seen). plc_project_path: plc_path, iec_project_path or instance_path — needed when the solution has more than one PLC project. |
| xae_library_installedB | The libraries installed in the repositories XAE uses (ScanLibraries): name, version, distributor — every version of a library. name filters (part of the name, any case). |
| xae_library_repositoriesA | The library repositories XAE uses, in priority order (name, folder). Where this XAE's Automation Interface does not give them, it says so — nothing is guessed. |
| xae_library_addA | Add a library reference (AddLibrary): library by name, version ('' = '*', the newest installed) and distributor, or by display name 'Tc2_Math, * (Beckhoff Automation GmbH)'. XAE refuses a bare name: a missing distributor / version is completed from the installed libraries (ScanLibraries) — refused when the library is not installed, is installed from several distributors and none is given, or a pinned version is not installed. Refused when it is already referenced. Runs once; the result has the references before / after, XAE's messages, and build_required — it never builds. guarded: confirmation tc_request_confirmation(action='xae_library_add', target=':'). |
| xae_library_removeA | Remove a library or placeholder reference (RemoveReference). A name referenced more than once (a library and a placeholder of the same name, two versions) is refused until kind ('library' / 'placeholder') or version says which ('#X' = the placeholder X). RemoveReference itself takes a name (and a library's version): which of a placeholder and a library of one name XAE removes is checked afterwards and said. Code that uses it does not build any more — said, nothing in the code is changed. Destructive-class: tc_request_confirmation(action='xae_library_remove', target=':' + ' ()' and ', ' when given). |
| xae_placeholder_addB | Add a placeholder (AddPlaceholder) with its default library (library '' = the device's placeholder of that name; a library's missing distributor / version is completed as in xae_library_add). target=':'. |
| xae_placeholder_resolveC | Set what a placeholder resolves to (SetEffectiveResolution): library, version ('' or '*' = newest), distributor (completed from the installed libraries when not given). The result checks the effective resolution after it. target=':=' + ', ' when given. |
| xae_placeholder_freezeB | Freeze a placeholder (all when placeholder is left out) at the version it resolves to now (FreezePlaceholder): '' no longer follows newer installed versions. target=':' ('' for all). |
| ads_discoverA | Broadcast search for TwinCAT targets on the local network (UDP 48899): NetId, host name, TwinCAT version, IP — the same thing XAE's 'Broadcast Search' does. |
| hmi_scanA | Index HMI symbol bindings: TE2000 .view/.content/.usercontrol/.json (%s%PLC1.X%/s%) and TF1800 .TcVIS (VisualElemMember paths -> kind 'tcvis'; ST input-action assignments -> kind 'tcvis_write'). root defaults to project.hmi_root, else the project root (which is where .TcVIS files live). |
| hmi_bindingsB | Which HMI files/controls bind a PLC symbol (fnmatch on the symbol path). |
| hmi_vs_runtimeB | Broken HMI bindings: symbols bound in the HMI that do not exist on the running PLC (and, with check_source, whether the source declares them), plus declared variables with no HMI. |
| exp_templateB | A complete example experiment spec to edit (YAML text + the same as JSON): factors × levels, runs per level, preconditions, start/done conditions, measures (last/latency/pulses/settle/ dwell/edges/expr…), metrics (mean/std/cpk/p95), criteria, writes_allowed. kind: dosing_fill. |
| exp_validateA | Validate a spec (YAML/JSON text, a dict, or a saved experiment by name): structure, measures, metrics, criteria, writes_allowed vs what the spec writes, the server guard's view of each write, symbol resolution on the connected PLC, planned run count and a duration estimate. |
| exp_saveB | Save an experiment spec (YAML or JSON) under <experiments_dir>//spec.yaml. Runs already recorded for that name are kept; resume picks up where they stopped. |
| exp_listC | Experiments on disk with run counts, state and whether a report exists. |
| exp_getB | Spec, status and run summary of one experiment. |
| exp_runsD | Run records: level, status, measures, recording ids, duration, error. |
| exp_runA | Run an experiment in the background: per level × repetition → preconditions → factor values written → snapshot → capture profile started → stimulus → start acknowledged (done_when must go false) → done_when → settle → stop → measures → runs.jsonl. Factor values are read first and restored at the end or on abort (read back and checked). Every write is re-checked against the server guard, pinned to the machine the experiment started on, and audited with symbol and value; a refusal, a target change or a timed-out write stops the experiment. dry_run resolves every symbol, coerces condition values (enum names → ints) and prints the plan without writing. Starting is control-class: in guarded mode it asks once (elicitation / confirm_token); in full mode it does not. Only one experiment may run at a time. |
| exp_statusA | Live state of a running experiment: planned/done/failed, current level, ETA, last error, live recording ids. For an experiment from an earlier session, the persisted status. |
| exp_abortA | Stop a running experiment after the current step; recordings are closed, factor values restored. |
| exp_evaluateB | Metrics per level with 95 % CIs and the criteria verdicts (pass / fail / inconclusive with the run count that would settle it). Deterministic from runs.jsonl; rerunnable by anyone. |
| exp_reportA | Write <experiments_dir>//report.md and report.html: identity (machine, TwinCAT version, project, git state, safety mode), criteria table, metrics per level, charts (primary measure per run; per-level mean/CI), run table, the spec, the audit log of writes during the run, and the interpretation — stored separately and labelled as not computed. interpretation= saves a new one (author=agent|operator|). firmware_check=true runs proj_vs_runtime first. |
| exp_compareB | Two experiments with the same levels side by side: metric deltas and whether their 95 % CIs separate (the before/after question with error bars). |
| infosys_booksA | List the Infosys books (documentation sets) of the Help Viewer package installed with TwinCAT, with page counts — offline, one index of every book. pattern is a glob on the slug ('tcplclib_*', 'json'); category: plc-library | function-tf | engineering-te | twincat-system | hardware | other. TwinCAT 2 books are excluded. |
| infosys_indexD | The local Help Viewer index holds every book and is built as a whole (the first time by itself, later only with kb_rebuild(index='infosys') once the user approves) — this tool changes nothing here and says so; kb_status / tc_doctor show the index state. |
| infosys_search_symbolsA | Find library symbols (function blocks, functions, structs, enums, aliases, interfaces, GVLs) by name fragment in the local Help Viewer index; exact > prefix > contains. kind: FunctionBlock | Function | Program | Method | Property | Action | Enum | Struct | Union | Alias | Interface | GlobalVariable | Library. library: 'Tc2_System' or the book slug. Offline; while the index is still being built the answer says so. |
| infosys_declarationA | Declaration of a library symbol as data: kind, inputs/outputs/in_out (name, type, default, description from the page's table), struct members, enum values, return type, EXTENDS / IMPLEMENTS, methods (name, definition location, description) and properties (type, access, initial value) of an FB, plus the ST declaration text and the page URL. The thing to call before writing code against an FB you have not used before. A name documented by different pages (two libraries, two methods of one name) is refused as ambiguous with the candidates — pass library=; one page in several books is one answer (same_page_in); also_in names the other libraries. A member as '.' (FB_WG_ComboFilter.Call): the page below that FB in the Help Viewer contents (Beckhoff titles a method page with its bare name); parent names the FB of a member. The twincat-docs skill's docs_declaration resolves names by the same rule. |
| infosys_searchA | Full-text search over the local Help Viewer index (FTS5, bm25-ranked, excerpt with [hits]); "quoted phrases" stay together, limit 1..50. Offline — no web search (web_fallback is ignored and said so); results carry the page's doc version and the package date. |
| infosys_search_codeA | Search the code examples of the indexed pages for a fragment — a call pattern, an attribute, a constant (case-insensitive substring of the code as written; non-breaking spaces count as spaces). language: one that occurs in the index (an unknown one is refused with the list), '' / 'none' = samples without a label. limit 1..50; total_matches says how many there are. ignore_spacing=true also matches when only the spacing around punctuation differs. Returns the matching lines with context and the page URL. |
| infosys_get_pageA | A documentation page as Markdown (headings, tables, fenced code), read from the local Help Viewer package — any page in it, indexed or not; doc version + breadcrumb included. ref: full Infosys URL, 'book/pageid', a bare page id, or a symbol name ('ADSREAD'). Offline. Use max_chars to keep it short. |
| ads_exportB | Generate code from the runtime: fmt=csharp (explicit-offset structs + enums for Beckhoff.TwinCAT.Ads ReadAny/WriteAny), typescript (interfaces), json (type descriptions), or csharp_symbols (const symbol paths under prefix). types = struct/enum names; prefix = take the types of every symbol under it. |
| snap_applyA | Write a saved snapshot back to the PLC (recipe / parameter restore). dry_run lists the differences only. Every differing symbol is gated like ads_write. |
| proj_validate_xmlA | Structural validation of .TcPOU/.TcDUT/.TcGVL files: XML well-formedness, Name/Id, GUID format + project-wide uniqueness, Declaration/Implementation presence, property accessor VAR blocks, LineIds vs implementation line counts, EOF newline. fix=true applies safe fixes (.bak written); regenerate_lineids rebuilds LineIds blocks. |
| ipc_eventlogA | Windows event log of the target (Windows 7/10/11 IPCs): runs wevtutil on the IPC via the System Service, reads the result back and deletes the temp file. Also lists the event providers registered on the target whose name contains 'TwinCAT' or 'Beckhoff', so a provider that does not exist there (the default name differs between TwinCAT builds) is reported as such instead of an empty result. Not available on Windows CE / TC-BSD targets. Destructive-class because it starts a process on the IPC: confirm with tc_request_confirmation(action='ipc_eventlog', target=). |
| tc_session_reportB | Markdown handover of this session: mode changes, writes, deploys and other mutating calls from the audit log, recordings, snapshots and dumps produced. Written to ~/.twincat_mcp/reports/.md (or out_path). |
| ipc_read_fileA | Read a file from the target IPC's file system over ADS (no share needed). Binary → base64. Prefer the OS-independent prefixes, which the target resolves itself: 'boot:' (the live TwinCAT boot folder — C:\ProgramData\Beckhoff\TwinCAT\3.1\Boot on 3.1.4026, \Hard Disk\TwinCAT\3.1\Boot on CE7), 'bootprj:', 'bootdata:', 'target:', 'config:', 'install:', 'user1:'..'user9:'. On 4026 the old absolute C:\TwinCAT\3.1\Boot still exists but is a stale legacy copy (e.g. a CurrentConfig.xml from years ago) — do not draw conclusions from it. |
| ipc_list_dirA | List files on the target matching a Win32 pattern. Prefer 'boot:*' (the live boot folder on any target OS; the result names the absolute folder in resolved_pattern) over a hard-coded 'C:\TwinCAT\3.1\Boot*', which is the stale legacy folder on 3.1.4026. Other prefixes: see ipc_read_file. |
| ipc_write_fileC | Write a file on the target IPC (text or base64). Control-class. |
| ipc_delete_fileC | Delete a file on the target IPC. Destructive-class. |
| ipc_start_processC | Start a process on the target IPC (NT_StartProcess equivalent). Destructive-class. |
| ipc_rebootC | Reboot (or shut down) the target IPC via the System Service. Destructive-class. |
| xae_attachA | Attach to the running XAE shell / Visual Studio (Running Object Table) that has the solution: solution_path when given (from then on every xae_* tool works on it — flagged solution_mismatch when it is not xae.solution_path), else xae.solution_path. A solution no shell has open is never replaced by another one (refused, or launch=true starts a shell with it); several shells with different solutions and none named or configured: refused. If the solution is closed later, every xae_* tool refuses until it is open again (in any shell — they find it by themselves). |
| xae_statusA | Shell version, open solution, projects, TwinCAT project name, target NetId, started flag. Attaches to the shell first if not attached yet (as every xae_* tool does). solution_bound: the solution the xae_* tools work on; solution_mismatch / refused: it is not open (they refuse); reattached: it was found in a shell again. |
| xae_open_solutionB | Open a .sln in the attached shell. |
| xae_buildA | Build the solution. save_first (default) runs File.SaveAll, saves whatever the shell still reports unsaved, and checks that every xae_pou_set / xae_pou_create edit of this session is in the source files — it refuses to build otherwise (an external reload would discard the edits). Returns success, error/warning items with file:line (VS error list, or parsed from the Build output pane) and the output tail. check_all_objects=true runs PLC 'Check all objects' instead (compiles unused POUs). After a build of 0 errors, link_check names the GVLs nothing in the source references (TwinCAT does not link them: no symbol on the target) unless they carry {attribute 'linkalways'}. |
| xae_diagnosticsB | Current XAE Error List (errors/warnings/messages with file:line — TwinCAT's own entries too, read from the
list view when the automation interface reports none; this focuses the Error List window) merged with the
Build output pane, plus the pane's tail, without rebuilding. |
| xae_list_commandsB | List DTE command names containing a substring (to discover what this shell exposes). |
| xae_commandB | Execute an arbitrary DTE command (e.g. 'PLC.Login'). Control-class; commands whose name contains download/reset/activate/restart/delete/remove/shutdown are destructive-class. |
| xae_plcA | PLC online actions: login | logout | start | stop | reset_cold | reset_origin | download. First the Automation Interface (ConsumeXml online commands on ' Project' — no active project needed; plc_project_path when there are several PLC projects), then the shell's DTE commands (names vary by build). download/reset_cold/reset_origin are destructive-class, the rest control-class. |
| xae_targetB | Get (net_id=None) or set the target AMS NetId of the TwinCAT project. Setting is control-class. |
| xae_activate_configurationA | ActivateConfiguration on the current target (overwrites the boot project on the IPC). Destructive-class. Follow with xae_restart_twincat. |
| xae_restart_twincatC | StartRestartTwinCAT on the current target (runtime restarts in RUN mode). Destructive-class. |
| xae_deployA | Save → build → (boot project autostart on) → activate → restart, in one call. Stops at the first build error. Destructive-class (one confirmation covers all steps). Refuses when the XAE target and the ADS target of this server differ. Done = the target runs the new configuration: in RUN and a restart proven (steps.verify.transition_by): 'poll' (another state seen after the commands), 'mode_change' (it was not in RUN before the activation), 'witness' (task 1's cycle counter went back, or the PLC application's compile timestamp changed). In RUN without such proof is reported as that, not as 'not in RUN'. |
| xae_tree_getA | System Manager tree item: properties, children to |
| xae_tree_consume_xmlB | ConsumeXml on a tree item — the generic write path for any configuration (add I/O boxes, link variables, set task cycle time, add project routes on TIRR …). |
| xae_tree_set_valuesB | Set element texts in a tree item's XML by relative XPath, e.g. {'TaskDef/CycleTime': '10000'} (unit 100 ns). |
| xae_tree_create_childB | Generic CreateChild (tree item types/subtypes per Infosys). For PLC objects prefer xae_pou_create. |
| xae_tree_deleteB | Delete a tree item (POU, box, task …). Destructive-class; the confirmation names the item: tc_request_confirmation(action='xae_tree_delete', target='<parent_path>^'). |
| xae_plc_projectsA | PLC projects in the TwinCAT project with their tree paths (needed by PLCopen / boot tools). |
| xae_pou_getB | Declaration/implementation text of a POU/DUT/GVL/interface tree item and its methods/properties/actions. |
| xae_pou_setA | Replace declaration and/or implementation text of a POU item inside the live shell (no file round-trip, XAE keeps LineIds consistent). Build afterwards. |
| xae_pou_createA | Create a PLC object under a folder/POU. kind = program | fb | function | method | property | action | transition | struct | enum | union | alias | gvl | interface | itf_method | itf_property | folder | parameter_list, and accessors on their own: property_get | property_set (under a property) | itf_property_get | itf_property_set (under an interface property). A property / itf_property is created with its accessors (accessors='get,set' default, 'get' or 'set'): without one it does not compile (C0373). return_type: function, method, property; access: PUBLIC | PRIVATE | PROTECTED | INTERNAL. Optional code is set after creation. |
| xae_plcopen_importA | Import a PLCopen XML file into a PLC project (path from xae_plc_projects). option: none | rename | replace | skip (conflict handling). replace: the existing object keeps its place (folder) and identity and gets the imported text. TwinCAT's own replace deletes it and re-creates the import at the project root, so the file is imported as renamed copies (PRG_X_1, header renamed too) and the tool finishes the replace: the copy's header is named back, the texts are checked against the XML, written into the existing object, read back, and the declared name must equal the object's name. Only then is the copy — created by this import, it was not in the tree before — deleted (nothing that existed before the import is ever deleted). Anything not proven → nothing written, the copy kept and named in the error. An object that is not at its place after an import is named (removed / moved). Result: replaced, header_renamed, copies_removed, build_required + objects_to_build (build next). |
| xae_plcopen_exportB | Export PLC objects ('POUs.FB_X', 'DUTs.ST_Y' — folder.object) to a PLCopen XML file. |
| xae_boot_projectB | Boot project settings: set Autostart and/or GenerateBootProject (local, no activate unless activate_after_generate=true, which is then destructive-class). |
| proj_scanB | Index the project tree (.sln/.tsproj/.plcproj/.TcPOU/.TcDUT/.TcGVL/.TcIO). incremental=true re-indexes only files whose mtime changed (fast after XAE saves). |
| proj_changedA | Units whose source differs from a git revision (default: uncommitted work) plus untracked files — 'what did I change?'; pair with proj_vs_runtime for 'is it deployed?'. |
| proj_listC | List indexed objects. kind: PROGRAM | FUNCTION_BLOCK | FUNCTION | STRUCT | ENUM | UNION | ALIAS | GVL | INTERFACE | METHOD | PROPERTY | ACTION (case-insensitive). |
| proj_get_codeB | Source of an object ('FB_Valve', 'FB_Valve.Open', 'MAIN'): declaration/implementation with file line numbers, parsed VAR declarations, sub-items. |
| proj_resolve_symbolA | Map an ADS symbol path (MAIN.fbFill.fbValve.bOpen) to the file:line where each hop is declared, following variable types through FB/STRUCT definitions. Hops inside a library type (Tc2_Standard.CTU.CV) are completed from the runtime's datatype table when the target is reachable. A first name declared in several PLC projects (MAIN in Plc1 and Plc2) is the one of the PLC project on the connected ADS port (AmsPort in the .tsproj). |
| proj_searchC | Regex search over all declarations/implementations → unit, file, line, text. |
| proj_referencesC | Where a type/object is instantiated (variables of that type) and mentioned. |
| proj_read_fileC | Raw text of a project file (relative to project root or absolute). |
| proj_set_codeA | Write declaration/implementation back into the .TcPOU/.TcDUT/.TcGVL file (keeps the rest of the XML byte-identical, writes a .bak, drops the object's stale ). Use when XAE is closed or for batch edits; otherwise prefer xae_pou_set. Written also while the attached XAE has the project open — the result then says what that means (xae_open). |
| tcunit_resultsB | Parse a TcUnit xUnit result into a pass/fail summary. Source: the file on the target over ADS (default: configured remote path), a local file, or raw XML text. |
| tcunit_runA | Full TcUnit cycle: save (checked) → build → boot project autostart+generate → activate → restart → delete old result → wait for the xUnit file on the target → parse. Destructive-class (it activates a configuration on the target): tc_request_confirmation(action='tcunit_run', target=). plc_project_path from xae_plc_projects. TcUnit writes the result file only with its library parameter GVL_Param_TcUnit.xUnitEnablePublish = TRUE (default FALSE): the run is refused before anything is activated when the project source says FALSE, and stops waiting when the PLC reports FALSE (D40). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| debug_symbol | Investigate why a PLC variable has the value it has |
| fb_validation | Validate a function block against its specification on the live machine |
| deploy_safely | Build, check and deploy a change to the target with evidence |
| performance_audit | Find what costs cycle time and where timing is marginal |
| onboard_project | Get oriented in an unfamiliar TwinCAT project |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| res_config | |
| res_audit | |
| res_safety |
TDQS
Scored across 171 tools
The domain prefixes help, but there are many near-duplicate entry points for capturing or inspecting PLC behavior: rec_start, rec_capture, ads_watch, test_scenario, wf_fb_profile, and exp_run all overlap in purpose. Similarly, several read/write, status, search, and deploy tools have boundaries that are hard to keep straight at 171 tools.
All names are snake_case and use stable family prefixes such as xae_, ads_, proj_, and rec_, but verb/object ordering is mixed: xae_pou_get versus xae_pou_create, ads_list_symbols versus ads_read, and proj_get_code versus proj_scan. It is readable, but not one predictable convention.
171 tools is far beyond a well-scoped MCP surface, even for a broad TwinCAT automation domain. Many workflow helpers duplicate compositions of lower-level tools, and the sheer count overwhelms selection.
Coverage is unusually broad across shell automation, build/diagnostics, PLC runtime ADS, project files, recordings, tests, experiments, documentation, ESI hardware lookup, HMI, IPC, Scope, and safety. Some lifecycle operations are only reachable through generic escape hatches such as xae_command or tree XML, but no major domain gap is apparent.