@renanlido/tia-openness-mcp 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/index.js +95 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -16,6 +16,27 @@ publicado no **npm público** via **semantic-release** — rode com um one-liner
|
|
|
16
16
|
npx -y @renanlido/tia-openness-mcp
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## Operating guide embedded (tia_guide)
|
|
20
|
+
|
|
21
|
+
The harness operating guide ships **inside the MCP itself**, in three layers:
|
|
22
|
+
|
|
23
|
+
1. **Server `instructions`** — the always-on operating brief (`content/brief.md`) travels in the
|
|
24
|
+
MCP handshake (`instructions` field). Any client that honors `instructions` (Claude Code,
|
|
25
|
+
Claude Desktop, ...) gets the gates/serial-only/pipeline rules automatically, with zero setup.
|
|
26
|
+
2. **`tia_guide(topic)`** — a tool that returns the bundled guide chapters on demand:
|
|
27
|
+
`toc` (default) | `full` | `brief` | `gates-guards` | `gui-gates` | `gotchas` | `pipeline` |
|
|
28
|
+
`recipes` | `spec-format`. Call it with `{"topic":"pipeline"}` before the first build on a
|
|
29
|
+
project, and `{"topic":"gui-gates"}` when a tool returns `requires_human_action` or a compile
|
|
30
|
+
error mentions a GUI-only setting.
|
|
31
|
+
3. **Teaching errors on gate signals** — gate responses (`requires_human_action`, the
|
|
32
|
+
`guiGateHints` on compile results, etc.) carry the relevant next steps inline, so the guide
|
|
33
|
+
reinforces itself at the moment a gate is actually hit, not only up front.
|
|
34
|
+
|
|
35
|
+
`tia_guide` is **pure and offline**: it never calls the `serve` API and never returns `isError` —
|
|
36
|
+
unlike every other tool in this package, it works **even with the `serve` down**. The chapters are
|
|
37
|
+
generated from `mcp/content/*.md` (the canonical source) into `src/content.gen.ts` via
|
|
38
|
+
`npm run sync` — see [scripts/sync-guide.mjs](scripts/sync-guide.mjs).
|
|
39
|
+
|
|
19
40
|
## Tools
|
|
20
41
|
|
|
21
42
|
### Diagnóstico + M1 read-only (classe R, auto-aprováveis)
|
|
@@ -23,6 +44,7 @@ npx -y @renanlido/tia-openness-mcp
|
|
|
23
44
|
|---|---|---|
|
|
24
45
|
| `tia_ping` | `GET /health` + `GET /readiness` | Diagnostica conectividade e ATIVAÇÃO do `serve`. `{ base, reachable, activated, latencyMs, hint }`. Ideal p/ Mac→VM. |
|
|
25
46
|
| `tia_readiness` | `GET /readiness` | Prontidão do ambiente **no host do serve** (x64, grupo Windows, PublicAPI, registro, instâncias). |
|
|
47
|
+
| `tia_guide` | — (bundled content, no HTTP call) | Returns the operating guide (see "Operating guide embedded" above). Topics: `toc` (default) \| `full` \| `brief` \| `gates-guards` \| `gui-gates` \| `gotchas` \| `pipeline` \| `recipes` \| `spec-format`. Pure/offline — works with the `serve` down. |
|
|
26
48
|
|
|
27
49
|
**M1 (read-only, 1:1 com os GET da API):** `tia_instances`, `tia_status`, `tia_projects`, `tia_project_info`,
|
|
28
50
|
`tia_project_languages`, `tia_plcs`, `tia_devices`, `tia_device_tree`, `tia_device_connections`,
|
|
@@ -81,7 +103,7 @@ Aceita o **project-spec** estruturado (`docs/TIA-BEST-PRACTICES.md` §13.2; JSON
|
|
|
81
103
|
- **`tia_spec_plan`** (sem serve): transforma um spec válido na sequência ordenada de operações (subnets→devices→módulos→rede→tags→blocos→compile).
|
|
82
104
|
- **`tia_spec_apply`**: orquestra o build OFFLINE num projeto aberto (valida → G5 → executa serial → compila), **para no 1º erro** e **nunca baixa** (download é o GATE G3, sempre humano). `dryRun:true` devolve o plano.
|
|
83
105
|
|
|
84
|
-
> **MCP M0–M5 COMPLETO** (
|
|
106
|
+
> **MCP M0–M5 COMPLETO** (76 tools), **validado em runtime** contra um TIA Portal V21 real (incluindo **deploy em hardware** num S7-1200) e **publicado no npm**. Roadmap e marcos em `docs/ROADMAP.md` §3.
|
|
85
107
|
|
|
86
108
|
## Configuração (env)
|
|
87
109
|
|
package/dist/index.js
CHANGED
|
@@ -31336,6 +31336,7 @@ function trustHandoff(projectPath, err) {
|
|
|
31336
31336
|
state: "WAITING_FOR_TRUST",
|
|
31337
31337
|
canDo: `connect ${projectPath}`,
|
|
31338
31338
|
youMustDo: 'A human needs to confirm the TIA Openness TRUST/SECURITY dialog ON THE SERVE HOST (click "Sim, para tudo" / "Yes to all"). It is required on the 1st connection of each `serve` process and CANNOT be automated (for security reasons).',
|
|
31339
|
+
caveat: 'This handoff can FALSE-POSITIVE on a client timeout with NO real dialog on screen. Check tia_status first: if attached:true the connect actually succeeded \u2014 just resume. Details: tia_guide {"topic":"gui-gates"}.',
|
|
31339
31340
|
nextSteps: [
|
|
31340
31341
|
"1. If TIA is headless, call tia_show_ui to relaunch it VISIBLE and render the dialog on screen.",
|
|
31341
31342
|
"2. Ask a human to confirm the trust dialog on the serve host.",
|
|
@@ -31539,13 +31540,17 @@ async function deviceCreateFlow(args) {
|
|
|
31539
31540
|
summary: `create device "${args.deviceName ?? args.deviceItemName ?? "?"}" (${args.typeIdentifier})`
|
|
31540
31541
|
});
|
|
31541
31542
|
}
|
|
31543
|
+
var FW_CHANGE_RESET_WARNING = `DeviceItem.ChangeType RESETS GUI-gated comm settings on the CPU (runtime-validated): OPC UA server activation goes false and the OPC UA runtime-license selection is cleared (the server interface and PUT/GET survive). Archive FIRST (tia_project_archive). Afterwards: re-set OpcUaServer:true via tia_obj_set_attributes on the 'OPC UA' device-item (scriptable), re-select the license in the GUI (GUI-only), and recompile \u2014 the change marks blocks isConsistent:false. Same-version ChangeType is rejected by TIA. Details: tia_guide {"topic":"gui-gates"}.`;
|
|
31542
31544
|
async function deviceChangeVersionFlow(args) {
|
|
31543
31545
|
const bad = gd5Violation(args.typeIdentifier);
|
|
31544
31546
|
if (bad) return guardError("Gd5", bad, { typeIdentifier: args.typeIdentifier });
|
|
31545
|
-
|
|
31547
|
+
const r = await mutate("POST", `/plcs/${seg2(args.plc)}/device/changeversion`, jsonBody2({ typeIdentifier: args.typeIdentifier, dryRun: args.dryRun }), {
|
|
31546
31548
|
dryRun: args.dryRun,
|
|
31547
31549
|
summary: `change firmware of CPU "${args.plc}" to ${args.typeIdentifier}`
|
|
31548
31550
|
});
|
|
31551
|
+
if (r.isError) return r;
|
|
31552
|
+
const data = JSON.parse(r.content[0].text);
|
|
31553
|
+
return textResult({ ...data, fwChangeReset: FW_CHANGE_RESET_WARNING });
|
|
31549
31554
|
}
|
|
31550
31555
|
var dryRunField = {
|
|
31551
31556
|
dryRun: external_exports.boolean().optional().describe("Gd11: plan-without-applying \u2014 validates the guards and describes the action WITHOUT mutating the project")
|
|
@@ -32014,6 +32019,32 @@ function gateHandoff(gate, ctx) {
|
|
|
32014
32019
|
false
|
|
32015
32020
|
);
|
|
32016
32021
|
}
|
|
32022
|
+
var COMPILE_GUI_GATE_SIGNATURES = [
|
|
32023
|
+
{
|
|
32024
|
+
contains: "OPC UA license is not sufficient",
|
|
32025
|
+
gate: "opcua_license",
|
|
32026
|
+
guiSteps: 'EXPECTED after a fresh project or a firmware change \u2014 a GUI-only setting, not a code bug. In TIA Portal: CPU \u2192 Properties \u2192 General \u2192 Runtime licenses \u2192 OPC UA \u2192 "Type of required license" \u2192 select the license (e.g. "SIMATIC OPC UA S7-1200 Basic" on a 1214C), then recompile. Details: tia_guide {"topic":"gui-gates"}.'
|
|
32027
|
+
}
|
|
32028
|
+
];
|
|
32029
|
+
function detectGuiGateHints(messages) {
|
|
32030
|
+
const hits = /* @__PURE__ */ new Map();
|
|
32031
|
+
const walk = (node) => {
|
|
32032
|
+
if (Array.isArray(node)) {
|
|
32033
|
+
for (const n of node) walk(n);
|
|
32034
|
+
return;
|
|
32035
|
+
}
|
|
32036
|
+
if (!node || typeof node !== "object") return;
|
|
32037
|
+
const desc = node.description;
|
|
32038
|
+
if (typeof desc === "string") {
|
|
32039
|
+
for (const sig of COMPILE_GUI_GATE_SIGNATURES) {
|
|
32040
|
+
if (desc.includes(sig.contains)) hits.set(sig.gate, { gate: sig.gate, guiSteps: sig.guiSteps });
|
|
32041
|
+
}
|
|
32042
|
+
}
|
|
32043
|
+
walk(node.children);
|
|
32044
|
+
};
|
|
32045
|
+
walk(messages);
|
|
32046
|
+
return [...hits.values()];
|
|
32047
|
+
}
|
|
32017
32048
|
async function compileFlow(plc) {
|
|
32018
32049
|
try {
|
|
32019
32050
|
const data = await tiaFetch(`/plcs/${seg3(plc)}/compile`, { method: "POST", headers: JSON_HEADERS, body: "{}" }, { timeoutMs: 12e4 });
|
|
@@ -32021,7 +32052,13 @@ async function compileFlow(plc) {
|
|
|
32021
32052
|
const errors = typeof d.errors === "number" ? d.errors : null;
|
|
32022
32053
|
const clean = errors !== null ? errors === 0 : d.state === "Success";
|
|
32023
32054
|
const nextHints = clean ? ["Compile CLEAN (Gd8: warnings don't block). Ready for gated download (tia_download) or S7 connection."] : [`Compile with ${errors ?? "?"} ERROR(s) \u2014 fix BEFORE download/S7 (Gd9 forbids a non-clean deploy). See 'messages' (tree).`];
|
|
32024
|
-
|
|
32055
|
+
const guiGateHints = detectGuiGateHints(d.messages);
|
|
32056
|
+
return textResult({
|
|
32057
|
+
...d,
|
|
32058
|
+
verification: { compiled: clean ? "clean" : "error", errors, warnings: d.warnings ?? null },
|
|
32059
|
+
nextHints,
|
|
32060
|
+
...guiGateHints.length > 0 ? { guiGateHints } : {}
|
|
32061
|
+
});
|
|
32025
32062
|
} catch (e) {
|
|
32026
32063
|
return errorResult3(asTiaError(e));
|
|
32027
32064
|
}
|
|
@@ -32461,8 +32498,62 @@ function registerSpecTools(server2) {
|
|
|
32461
32498
|
);
|
|
32462
32499
|
}
|
|
32463
32500
|
|
|
32501
|
+
// src/content.gen.ts
|
|
32502
|
+
var BRIEF = '# TIA Openness MCP \u2014 operating brief\n\nYou are driving Siemens TIA Portal V21 through the `tia_*` tools (an MCP over the TIA\nOpenness HTTP `serve`). This brief is ALWAYS in effect. For depth, call the `tia_guide`\ntool (topics: `gates-guards`, `gui-gates`, `gotchas`, `pipeline`, `recipes`,\n`spec-format`) \u2014 read the relevant topic BEFORE authoring blocks, changing hardware,\nor preparing a deploy. You are an assistant up to the gate, never an autonomous\ncommissioner.\n\n## Vocabulary (used across all tool descriptions)\n\n- **Class R** \u2014 read-only; safe anytime.\n- **Class M-off** \u2014 offline mutation (edits the project; never touches the physical PLC).\n- **Class GATE** \u2014 touches the physical world or a human-only decision; needs a human.\n- **G1..G7** \u2014 the GATES: G1 trust dialog, G2 S7-connection precondition (tool-enforced:\n both CPUs must compile clean), G3 download / CPU-STOP, G4 go-online, G5 safety / F-logic\n (NEVER autonomous), G6 master-secret / UMAC (credentials come from a human \u2014 never\n invented), G7 network scan. All except G2 are human-only \u2014 never crossed autonomously.\n- **Gd\\*** \u2014 tool-enforced GUARDS (preconditions the tools check), e.g. Gd5\n `OrderNumber:<MLFB>/V<fw>` format (slash before V, never `:V`), Gd8 only ERRORs block\n a compile (warnings don\'t), Gd9 clean compile before deploy, Gd11 dryRun-then-approve.\n\n## Non-negotiable rules\n\n1. **Serial only.** The serve owns ONE TIA Portal on ONE serial worker. Never fire\n concurrent `tia_*` calls; one hung Openness call can stall the whole serve.\n2. **Never cross a GATE autonomously.** A `requires_human_action` response is a HANDOFF,\n not an error: relay its `youMustDo`/`nextSteps` to the human verbatim, wait for their\n explicit yes, then resume exactly as instructed. Never self-approve a download; never\n invent a secret (G6); never author, modify or deploy safety/F logic (G5).\n3. **Compile before deploy.** `tia_compile` aggregates hardware + software and is the\n offline verifier: `errors:0` is the Gd9 precondition for download (G3) and for S7\n connections.\n4. **Follow the build pipeline order** (details: `tia_guide {"topic":"pipeline"}`):\n validate the spec (`tia_spec_validate`) \u2192 plan (`tia_spec_plan`) \u2192 scaffold\n (`tia_spec_apply` or the M-off tools) \u2192 verify (`tia_compile`) \u2192 human-gated deploy\n (`tia_download` with dryRun \u2192 human approval \u2192 `approved:true` + the same `planHash`).\n5. **Some CPU settings are GUI-only** (PUT/GET permission, OPC UA runtime license,\n OPC UA server interface). Hitting one is EXPECTED, not a bug: relay the exact GUI\n steps from `tia_guide {"topic":"gui-gates"}` and resume after the human confirms.\n6. **Prerequisites:** the serve must be running (default `http://localhost:5000` \u2014\n check with `tia_ping`); the FIRST connect per serve process hits the G1 trust dialog\n on the serve host.\n\nSession start: `tia_ping` \u2192 `tia_status` \u2192 (if needed) `tia_start` / `tia_connect`.\nBefore the first build on a project: read `tia_guide {"topic":"pipeline"}` and\n`tia_guide {"topic":"gotchas"}`.\n';
|
|
32503
|
+
var GATES_GUARDS = '# Gates vs guards\n\n## GATES vs GUARDS\n\n- **GATES (human-only \u2014 never auto-bypass):** trust dialog (**G1**), download / CPU-STOP\n (**G3**), go-online (**G4**), network scan / accessible-devices (**G7**), master-secret/UMAC\n (**G6**), **safety / F-logic (G5) \u2014 never autonomous**.\n- **GUARDS (tool-enforced preconditions):** compile-clean before deploy, consistency,\n dry-run\u2192approve, idempotency by name, `OrderNumber:<MLFB>/V<fw>` format, serial-only.\n- **GUI-ONLY settings (NOT exposed via Openness \u2014 surface as a human gate, do not script):**\n "Permit access with PUT/GET communication", OPC UA **runtime license** / server-interface\n creation. (OPC UA server **activation** is the exception \u2014 settable via Openness with\n `tia_obj_set_attributes {OpcUaServer:true}` on the "OPC UA" device-item.)\n\n**Deploy-time approval gates** (download / online / network-scan / secret / safety = **G3/G4/G7/G6/G5**)\nare a *different* class \u2014 see the **`tia.handoff`** skill. Those are "a human **approves an action**",\nnot "a human **clicks a GUI setting**". Both are human interaction points; surface them as\n`requires_human_action` with concrete guidance, never as a tool failure.\n';
|
|
32504
|
+
var GUI_GATES = '# GUI gates \u2014 expected human interaction points\n\n## Human interaction points (GUI gates) \u2014 EXPECTED, not an error\n\nSome TIA settings are **not reachable via Openness** on the S7-1200/1500. The harness will hit a\nsignal that *looks* like an error but is actually a point where a **human must act in the TIA GUI**.\nWhen you see one of these, **do not treat it as a failure and do not retry blindly** \u2014 recognize it,\ntell the human the **exact GUI steps**, and resume after they confirm. A GUI gate is a *handoff*, not a *bug*.\n\n| # | Signal you\'ll see | Why it\'s expected | Tell the human (exact GUI steps) |\n|---|---|---|---|\n| **G1 trust** | `tia_connect` \u2192 `requires_human_action` (`trust_dialog`), or the **1st** connect per `serve` process hangs ~30 s | TIA shows a one-time security prompt the first time an Openness app attaches, **per process** | On the **serve host**, in TIA Portal, click **"Yes to all"** in the access dialog. *Caveat:* it can false-positive on a client timeout with **no** real dialog \u2014 check `tia_status` (`attached:true`) and just resume. |\n| **OPC UA license** | `tia_compile` **ERROR**: *"The selected OPC UA license is not sufficient\u2026"* | License selection is GUI-only (no Openness service); a fresh project **or a firmware change** leaves it unset | CPU \u2192 **Properties \u2192 General \u2192 Runtime licenses \u2192 OPC UA** \u2192 *Type of required license* \u2192 select **"SIMATIC OPC UA S7-1200 Basic"** (the 1214C option). Recompile. |\n| **OPC UA interface** | OPC UA client connects but the DB nodes aren\'t there (`ns=4` empty / no `ServerInterfaces`) | The **S7-1200 does NOT auto-expose DBs** (unlike the S7-1500) \u2014 a server interface must be created by hand | CPU \u2192 **OPC UA \u2192 Server interfaces \u2192 Add new server interface** \u2192 add/drag the DB (e.g. `ModbusData`) into it. Recompile; data appears under `ns=4`. |\n| **PUT/GET** | S7 client (`nodes7`/`snap7`) connects on port 102 but reads return access-denied (`ROSCTR=2` / "BAD") | *"Permit access with PUT/GET"* is a GUI-only protection setting | CPU \u2192 **Properties \u2192 Protection & Security \u2192 Connection mechanisms** \u2192 check **"Permit access with PUT/GET communication from remote partner"**. Compile + download (`hardware:true`). |\n| **FW-change reset** | After `tia_device_change_version`: OPC UA server off, the license error on compile, blocks `isConsistent:false` | `DeviceItem.ChangeType` **resets GUI-gated comm settings** (server activation + license); the interface & PUT/GET survive | `tia_project_archive` **before** the FW change. After: re-set `OpcUaServer:true` via `tia_obj_set_attributes` (scriptable), re-select the license (row above), recompile. |\n\n**Not a gate (now scriptable):** *activating* the OPC UA server \u2014 `tia_obj_set_attributes {OpcUaServer:true}`\non the "OPC UA" device-item (it auto-generates the server certificate). Only the **license** and the\n**server interface** stay GUI-only.\n';
|
|
32505
|
+
var GOTCHAS = '# Hard-won gotchas (runtime-validated)\n\n## Hard-won gotchas\n\n1. **Always compile before deploy.** `tia_compile` aggregates **hardware + software**; a\n **clean** compile (`errors:0`) is the precondition for download and for S7 connections.\n2. **The serve is a SINGLE serial worker** \u2014 one Openness op at a time. Do **not** fire\n concurrent `tia_*` calls; a hung Openness op can stall the serve (may need a restart).\n3. **Changing an FB\'s interface (e.g. regenerating it via `tia_source_put`) leaves its\n instance DB inconsistent.** Recompile FIRST: a software-scope `tia_compile` regenerates\n the instance DB in most cases. Only if the DB still reports inconsistent after a compile,\n delete it (`tia_block_delete`), recreate it (`tia_instancedb_create`) and recompile.\n4. **Firmware rule: project FW \u2264 device FW.** Bump the project FW **in-place** with\n `tia_device_change_version` (`DeviceItem.ChangeType`), which preserves the **program** \u2014 never\n set the project higher than the physical CPU. **\u26A0\uFE0F It RESETS GUI-gated comm settings** (the\n **OPC UA server activation** + **runtime-license**; the server interface and PUT/GET survive):\n **archive first** (`tia_project_archive`) on a CPU with hand-configured OPC UA, recompile after\n (it marks blocks inconsistent), and re-verify. Same-version `ChangeType` is rejected.\n5. **Use a NON-optimized DB** for Modbus (`MB_HOLD_REG`) and S7 (absolute addressing); OPC UA\n works with optimized or not.\n6. **Order numbers use `/V<fw>`, not `:V<fw>`** (guard Gd5).\n\n## Prerequisites (environment)\n\n- The **`tia` serve must be running** (default `http://localhost:5000`, e.g.\n `TIAOpenness.exe serve 5000`). The MCP is a pure HTTP client; point it at the serve host via\n `TIA_API_BASE` if it runs elsewhere.\n- The Windows user must belong to the **"Siemens TIA Openness"** local group.\n- The **FIRST connection per serve process shows a trust dialog** that a **human must confirm**\n ("Yes to all") on the serve host \u2014 this **cannot be automated** (gate G1).\n';
|
|
32506
|
+
var PIPELINE = '# The build pipeline (spec \u2192 verified project)\n\nStages in order \u2014 the class tells you what each stage may do:\n\n| # | Stage | Tool(s) | Class | What it does |\n|---|---|---|---|---|\n| 0 | Validate the spec | `tia_spec_validate` | R (pure) | JSON-Schema + cross-field engineering rules (unique names, same-subnet IPs, Gd5 order-number format, per-CPU module limits, language-by-family, the G5 safety rule). Needs no serve. |\n| 1 | Plan | `tia_spec_plan` | R (pure) | Turns a VALID spec into the ordered list of M-off operations. Nothing mutates. |\n| 2 | Scaffold | `tia_spec_apply` (or the M-off tools manually) | M-off | Creates the project content in dependency order (see below). Stops at every gate. |\n| 3 | Verify | `tia_compile` | M-off | THE offline verifier: aggregates hardware + software. Gd8: only ERRORs block. `errors:0` is the Gd9 precondition for any deploy. |\n| 4 | Handoff / deploy | `tia_download` | GATE G3 | `dryRun:true` \u2192 plan + `planHash` \u2192 a HUMAN approves \u2192 re-call with `approved:true` + the SAME `planHash`. Never self-approve. |\n\nScaffold order (dependency-correct): subnets \u2192 devices (CPUs first) \u2192 modules \u2192\nnetwork/IP/IO-system \u2192 protection recipe (`tia_guide {"topic":"recipes"}`) \u2192 tag\ntables \u2192 tags \u2192 constants \u2192 UDTs BEFORE dependent blocks \u2192 FB shells + instance DBs \u2192\nlogic (SCL/STL via `tia_source_put`; LAD/FBD/GRAPH via `tia_import_xml` \u2014 Gd6 format\nrouting) \u2192 `tia_compile`.\n\n## The approval envelope (what a gate handoff looks like)\n\nGated tools return `status:"requires_human_action"` with:\n\n- `gate` \u2014 which gate (e.g. `download_approval`, `go_online`, `trust_dialog`);\n- `youMustDo` \u2014 what the human must confirm, in plain words;\n- `plan` + `planHash` \u2014 exactly what would be executed (the hash binds the approval to\n this exact plan; a changed plan needs a new approval);\n- `nextSteps` \u2014 how to resume after the human\'s yes.\n\nRelay the envelope to the human, wait for an explicit yes, resume exactly as\ninstructed. The AI never approves, never invents a secret, never deploys F-logic.\n\n## After scaffolding\n\n- Re-run `tia_compile` after ANY authoring change.\n- `tia_project_save` regularly; `tia_project_archive` BEFORE risky operations\n (firmware change \u2014 see `tia_guide {"topic":"gotchas"}`).\n- Auditing an existing project is class R: read blocks/types/tags via `tia_plc_*` and\n check the engineering baseline (optimized access, 100% symbolic, UDT-typed\n interfaces, FB + instance-DB, uniform naming).\n';
|
|
32507
|
+
var RECIPES = '# Recipes (runtime-verified) & scaffold order\n\n## Pipeline order (dependency-correct)\n\n`tia_spec_apply` already encodes this; use it manually only when the spec maps imperfectly.\n\n1. **Subnets** (`tia_subnet_create`) \u2014 before connecting devices to them.\n2. **Devices** (`tia_device_create`, `OrderNumber:<MLFB>/V<fw>`) \u2014 CPUs first.\n3. **Modules** (`tia_device_plug`) \u2014 into existing devices, by slot.\n4. **Network** (`tia_network_connect`, IP, `createIoSystem`) \u2014 CPU + its IO-devices on the\n **same** subnet; exactly one `createIoSystem` per IO-system.\n5. **Protection recipe** (below) \u2014 so the CPU can compile clean.\n6. **Tag tables** (`tia_tagtable_create`) \u2192 **tags** (`tia_tag_create`) \u2192 **constants**.\n7. **UDTs first** (`tia_types_import`), **then** blocks that use them.\n8. **Blocks** \u2014 FB shells (`tia_fb_create`) + instance DBs (`tia_instancedb_create`);\n logic via `tia_source_put` (SCL) or `tia_import_xml` (LAD/FBD/GRAPH). Gd6 format routing.\n9. **Compile** (`tia_compile`) \u2014 the verifier; only `Error` blocks. It compiles the **software**\n scope (not just the CPU device item), so it makes freshly-generated blocks consistent and, in\n most cases, **regenerates** any instance DB left inconsistent by an FB interface change.\n **Recompile first**; only if the DB still reports inconsistent after a compile, delete it\n (`tia_block_delete`), recreate it (`tia_instancedb_create`) and recompile.\n\n## Recipe \u2014 unlock a freshly-created CPU so it compiles clean (VERIFIED, V21 / S7-1500 V2.9)\n\nA bare CPU added via Openness fails `compile` with **protection** errors (verified\n2026 against a real S7-1500 1516-3 V2.9 \u2014 3 errors): the *confidential-PLC-configuration-\ndata* password is unset, the *communication certificate* can\'t be generated without it,\nand the *access level* defaults to a protected level needing a password. Fix, in order:\n\n**(1) Confidential-data password \u2192 also auto-generates the certificate** (clears 2 of 3):\n\n```\n# find the CPU device-item handle:\ntia_obj_roots -> the device handle (role:"device")\ntia_obj_items { handle:<device>, composition:"DeviceItems" } -> the CPU item handle\n# acquire the service ON THE CPU MODULE (not the rack) and invoke Protect:\ntia_obj_service { handle:<cpu>, type:"Siemens.Engineering.HW.Features.PlcMasterSecretConfigurator" } -> <svc>\ntia_obj_invoke { handle:<svc>, name:"Protect", args:["<password>"] }\n```\n\n**(2) Access level \u2192 FullAccess** (clears the 3rd; the legacy model on FW < 3.1):\n\n```\ntia_obj_set_attributes { handle:<cpu>, attributes:{ "PlcProtectionAccessLevel":"FullAccess" } }\n```\n\nThen `tia_compile` \u2192 `state: Warning, errors: 0` (the 2 warnings \u2014 CPU-display password \u2014\nare benign and do **not** block; Gd8).\n\n> **CORRECTION for V21:** the older recipe `PlcAccessControlConfiguration="Disabled"` returns\n> **HTTP 500** on V21 (that attribute is not on the CPU item). Use `PlcProtectionAccessLevel`.\n>\n> **Requires the `tia_obj_service` tool** (acquire `GetService<T>` \u2192 handle). Without it the\n> MCP cannot run step 1.\n\n### Open policy \u2014 who supplies the confidential password\n\n`Protect()` sets an **offline project** password (it is not the online master secret / G6).\nThe repo docs treat it as offline authoring ("no human needed"), but it protects confidential\nconfig data \u2014 so this is a **harness policy decision**: either (a) the human/spec supplies it\n(`spec.security.confidentialPassword`), or (b) the harness uses a documented project default.\nDefault to asking the human if the spec doesn\'t carry it; never invent a *secret the user\nmust later know* silently. The online master secret (`tia_mastersecret_set`, G6) is **always**\nhuman-supplied.\n\n## S7 connection precondition (G2)\n\n`tia_connection_create` needs **\u2265 2 CONSISTENT CPUs** (both compile clean). Two *bare* CPUs\nnever form a valid S7 connection \u2014 apply the protection recipe + `tia_compile` to make each\nconsistent first, then create the connection. Use `tia_connections_diagnose` on failure.\n\n## Recipe \u2014 bump the project firmware **in-place** (keeps the program) (VERIFIED, V21 / S7-1200 1214C)\n\nBumped a project **V4.0 \u2192 V4.4** on a real S7-1200 1214C **without losing blocks or tags**\n(verified 2026-06-07). Use this for **auto-FW-match**: align the project to the firmware\nactually read from the CPU. `DeviceItem.ChangeType(typeIdentifier)` changes the item type\nin place and **preserves the software** \u2014 it does **not** require delete/recreate.\n\n```\n# find the CPU device-item handle:\ntia_obj_roots -> the device handle (role:"device")\ntia_obj_items { handle:<device>, composition:"DeviceItems" } -> the CPU item handle (not the rack)\n# change the CPU\'s type to the SAME MLFB with the target /V<fw> suffix:\ntia_obj_invoke { handle:<cpu>, name:"ChangeType",\n args:["OrderNumber:6ES7 214-1HG40-0XB0/V4.4"] }\n# then recompile \u2014 a regenerated FB may need a fresh compile before download:\ntia_compile { ... }\n```\n\n- **Rule: `project FW \u2264 device FW`** \u2014 never set the project firmware **higher** than the physical\n device\'s (download would be rejected). Bumping the project *up to* the device FW is the safe move.\n- Keep the **same MLFB**, swap only the `/V<fw>` suffix. The program (blocks/tags) survives.\n- After `ChangeType` the CPU/blocks can be inconsistent; **always recompile** before any handoff.\n- **\u26A0\uFE0F `ChangeType` RESETS GUI-gated comm settings** (runtime-validated on the 1214C): it **disabled\n the OPC UA server** (`OpcUaServer`\u2192`false`) and **cleared the OPC UA runtime-license** selection\n (the custom server interface and PUT/GET *survived*). So **`tia_project_archive` first** on a CPU\n with hand-configured OPC UA/PUT-GET, and **re-verify after**. Recovery: `tia_obj_set_attributes\n {OpcUaServer:true}` on the "OPC UA" device-item (it **is** writable; re-select the license in the\n GUI). `ChangeType` to the **same** version is rejected ("Cannot change the type of the device item").\n\n## Recipe \u2014 enable a **Modbus TCP server** (`MB_SERVER`) on the S7-1200 (VERIFIED, FW V4.0)\n\nLets an external client (e.g. a Node backend) read/write the CPU over **Modbus TCP, port 502**\n(verified 2026-06-07; full backend/app detail in `docs/COMM-SCENARIOS.md`).\n\n1. **Non-optimized DB with an `Array of Word`** for the holding-register block (`MB_HOLD_REG`).\n Modbus needs a byte-addressable (non-optimized) DB \u2014 optimized access won\'t map.\n2. **On FW V4.0 use `MB_SERVER` V1.x**, which takes **`CONNECT_ID` + `IP_PORT`** parameters\n (**not** `CONNECT`/`TCON`, which the newer instruction versions use). Pin the instruction\n version when generating the FB.\n3. Instantiate `MB_SERVER` as a **multi-instance inside an FB** (its instance lives in the FB\'s\n instance DB), call it cyclically.\n4. **Map a holding register \u2192 a command tag** (e.g. `MB_HOLD_REG[0]` \u2192 `LedCmd`), so writing the\n register drives the output.\n\n> **Instance-DB gotcha (the big one):** when you regenerate the FB via `tia_source_put`, the\n> existing **instance DB** can go inconsistent (its interface no longer matches the FB).\n> **Recompile first** \u2014 a software-scope `tia_compile` regenerates the instance DB in most cases.\n> Only if it still reports inconsistent after a compile (typical when the interface was edited via\n> source), delete + recreate it, then recompile:\n>\n> ```\n> tia_compile { ... } # try first\n> tia_block_delete { ... the stale instance DB ... } # only if still inconsistent\n> tia_instancedb_create { ... fresh instance DB for the FB ... }\n> tia_compile { ... }\n> ```\n>\n> (Matches the \xA7Pipeline note: recompile first, delete+recreate only as the fallback.)\n\n## Recipe \u2014 enable **S7 / PUT-GET** on the S7-1200 (port 102)\n\nLets an external S7 client (e.g. `nodes7`) read/write the CPU over **S7comm, port 102**.\n\n1. **GATE (human, GUI only):** enable PUT/GET \u2014 *Properties \u2192 Protection & Security \u2192\n Connection mechanisms \u2192 "Permit access with PUT/GET communication"*. **Not exposed by\n Openness** on this CPU/FW (we tried `PlcAccessLevelProvider` \u2014 no toggle). Mark it as a\n **human gate** and surface it in `tia.handoff`; do **not** attempt to script it.\n2. **Non-optimized DB** for the data the client touches (PUT/GET addresses absolute offsets, so\n the DB must be byte-addressable / non-optimized).\n3. Compile + download as usual; the client then reads/writes the DB by area+offset.\n\n## Recipe \u2014 enable **OPC UA** on the S7-1200 (port 4840)\n\nLets an external OPC UA client read/write the CPU over **OPC UA, port 4840**. This path is the\nmost gated \u2014 most of it is **human GUI steps**, not Openness.\n\n**Prerequisite \u2014 firmware \u2265 V4.4.** OPC UA server isn\'t available on V4.0. If the project is on\nan older FW, bump it in-place first (see *bump the project firmware in-place* above \u2014\n`ChangeType` to `.../V4.4`).\n\nThen the setup sequence. **Step 1 is scriptable; steps 2\u20133 are human/GUI** on this CPU/FW (for those two\nwe tried `CommunicationManagement` and `OpcUaUserManagement\u2192404`):\n\n1. **Activate the OPC UA server** \u2014 *Properties \u2192 OPC UA \u2192 Server* (enable). *(This step **is**\n scriptable via Openness: `tia_obj_set_attributes` `{OpcUaServer:true}` on the "OPC UA"\n device-item \u2014 it auto-generates the server certificate. Steps 2\u20133 stay GUI-only.)*\n2. **Select the runtime license** \u2014 *"SIMATIC OPC UA S7-1200 Basic"* (Runtime licenses).\n3. **Create a server interface** that **exposes the DB** \u2014 the **S7-1200 does NOT auto-expose**\n tags/DBs to OPC UA (unlike the S7-1500), so you must add a server interface and place the DB\n nodes in it manually.\n\n**Finding the node IDs (client side):** browse `ns=3;s=ServerInterfaces` with any OPC UA client;\nthe actual data nodes show up under **`ns=4`**. Use those node IDs from the backend.\n\n> What Openness *can* still do here: create the DB/blocks, compile, download \u2014 **and activate the OPC UA\n> server** (step 1: `tia_obj_set_attributes {OpcUaServer:true}`; do NOT gate it on a human). Only **three**\n> items are truly human/GUI: the **runtime license**, the **server interface** \u2014 and **PUT/GET** in the S7\n> recipe. See `docs/TIA-Openness-Casos-de-Uso.md` \xA78c for the full "GUI-only gates" table.\n\n## Where to stop\n\nEverything above is **offline (M-off)**. `tia_download` / `tia_online` (G3/G4) are **not**\npart of scaffold \u2014 they belong to `tia.handoff` and require explicit human approval.\n';
|
|
32508
|
+
var SPEC_FORMAT = '# The project-spec (input model)\n\nA project-spec is ONE JSON document describing the whole project. Always run it\nthrough the pipeline front door: `tia_spec_validate` (rung 0, pure) \u2192 `tia_spec_plan`\n(dry) \u2192 `tia_spec_apply` (M-off, stops at gates).\n\nRules the validator enforces (beyond the JSON Schema): unique device/tag/block names;\nevery network node IP inside the subnet CIDR (same-subnet rule); `OrderNumber:<MLFB>/V<fw>`\nformat with `/V` \u2014 never `:V` (Gd5); per-CPU module limits; language by family (e.g.\nS7-1200 has no GRAPH); and the G5 rule \u2014 a spec with safety content marked autonomous\nis REJECTED (F-logic is never autonomous).\n\nMinimal example:\n\n```json\n{\n "project": { "name": "Line2", "directory": "C:\\\\TIA\\\\Line2" },\n "conventions": {\n "namingScheme": "plcopen",\n "optimizedAccess": true,\n "symbolicOnly": true,\n "defaultLanguages": { "interlocks": "LAD", "algorithms": "SCL", "sequencers": "GRAPH" }\n },\n "devices": [\n {\n "name": "Line2_CPU",\n "typeIdentifier": "OrderNumber:6ES7 511-1AK02-0AB0/V2.9",\n "role": "cpu",\n "family": "S7-1500"\n }\n ],\n "network": {\n "subnet": { "type": "System:Subnet.Ethernet", "name": "PN_Line2", "cidr": "192.168.0.0/24" },\n "nodes": [{ "device": "Line2_CPU", "ip": "192.168.0.1", "createIoSystem": true }]\n },\n "dataTypes": [\n {\n "name": "UDT_Motor",\n "members": [\n { "name": "xStart", "dataType": "Bool", "comment": "Start command" },\n { "name": "rSpeed", "dataType": "Real", "comment": "Speed [%]" }\n ]\n }\n ],\n "tagTables": [{ "name": "IO", "purpose": "IO" }],\n "tags": [{ "table": "IO", "name": "Conv01_Run", "dataType": "Bool", "address": "%Q0.0" }]\n}\n```\n\nResolve every concrete MLFB + firmware via `tia_catalog` before building \u2014 never guess\nan order number. Project firmware must be \u2264 the physical device\'s firmware.\n';
|
|
32509
|
+
var FULL_GUIDE = [BRIEF, GATES_GUARDS, GUI_GATES, GOTCHAS, PIPELINE, RECIPES, SPEC_FORMAT].join("\n---\n\n");
|
|
32510
|
+
|
|
32511
|
+
// src/tools/guide.ts
|
|
32512
|
+
var TOPICS = {
|
|
32513
|
+
brief: { summary: "the always-on operating brief (same text as the server instructions)", text: BRIEF },
|
|
32514
|
+
"gates-guards": { summary: "human-only GATES (G1..G7) vs tool-enforced GUARDS (Gd*)", text: GATES_GUARDS },
|
|
32515
|
+
"gui-gates": { summary: "expected human GUI interaction points + the exact steps to relay", text: GUI_GATES },
|
|
32516
|
+
gotchas: { summary: "runtime-validated gotchas (serial-only, compile, instance DB, firmware)", text: GOTCHAS },
|
|
32517
|
+
pipeline: { summary: "the build pipeline validate\u2192plan\u2192scaffold\u2192verify\u2192handoff + the approval envelope", text: PIPELINE },
|
|
32518
|
+
recipes: { summary: "verified recipes: bare-CPU protection unlock, in-place firmware bump, S7 preconditions", text: RECIPES },
|
|
32519
|
+
"spec-format": { summary: "the project-spec JSON model, validator rules and a minimal example", text: SPEC_FORMAT },
|
|
32520
|
+
full: { summary: "the whole guide (every chapter, concatenated)", text: FULL_GUIDE }
|
|
32521
|
+
};
|
|
32522
|
+
var GUIDE_TOPIC_IDS = Object.keys(TOPICS);
|
|
32523
|
+
function tocText() {
|
|
32524
|
+
const rows = GUIDE_TOPIC_IDS.map((id) => `- \`${id}\` \u2014 ${TOPICS[id].summary}`).join("\n");
|
|
32525
|
+
return '# tia_guide \u2014 table of contents\n\nCall again with {"topic":"<id>"} to read a chapter. Read `pipeline` and `gotchas`\nBEFORE the first build on a project; read `gui-gates` when a tool returns\n`requires_human_action` or a compile error mentions a GUI-only setting.\n\n' + rows + "\n";
|
|
32526
|
+
}
|
|
32527
|
+
function guideFlow(topic) {
|
|
32528
|
+
let text;
|
|
32529
|
+
if (topic === void 0 || topic === "toc") text = tocText();
|
|
32530
|
+
else if (TOPICS[topic]) text = TOPICS[topic].text;
|
|
32531
|
+
else text = `> Unknown topic "${topic}" \u2014 showing the table of contents.
|
|
32532
|
+
|
|
32533
|
+
` + tocText();
|
|
32534
|
+
return { content: [{ type: "text", text }], isError: false };
|
|
32535
|
+
}
|
|
32536
|
+
function registerGuideTool(server2) {
|
|
32537
|
+
server2.registerTool(
|
|
32538
|
+
"tia_guide",
|
|
32539
|
+
{
|
|
32540
|
+
title: "TIA \u2014 the operating guide (READ FIRST)",
|
|
32541
|
+
description: "THE OPERATING GUIDE for driving the tia_* tools \u2014 read the relevant topic BEFORE authoring, changing hardware or preparing a deploy. Returns bundled markdown: gates vs guards (G1..G7 are human-only), the build pipeline (validate\u2192plan\u2192scaffold\u2192verify\u2192handoff), runtime-validated gotchas, GUI-gate handoffs, the bare-CPU protection recipe, and the project-spec format. Default topic 'toc' lists the chapters; 'full' returns everything. Pure/offline (class R): does NOT call the serve \u2014 works with the serve down.",
|
|
32542
|
+
inputSchema: {
|
|
32543
|
+
topic: external_exports.string().optional().describe('one of: toc | full | brief | gates-guards | gui-gates | gotchas | pipeline | recipes | spec-format (default "toc")')
|
|
32544
|
+
}
|
|
32545
|
+
},
|
|
32546
|
+
async ({ topic }) => guideFlow(topic)
|
|
32547
|
+
);
|
|
32548
|
+
}
|
|
32549
|
+
|
|
32464
32550
|
// src/index.ts
|
|
32465
|
-
var server = new McpServer(
|
|
32551
|
+
var server = new McpServer(
|
|
32552
|
+
{ name: "tia-openness", version: "1.3.0" },
|
|
32553
|
+
// Camada 1 (ambiente): o brief operacional viaja no handshake MCP — qualquer cliente
|
|
32554
|
+
// que honre `instructions` recebe gates/serial-only/pipeline sem instalar nada.
|
|
32555
|
+
{ instructions: BRIEF }
|
|
32556
|
+
);
|
|
32466
32557
|
server.registerTool(
|
|
32467
32558
|
"tia_ping",
|
|
32468
32559
|
{
|
|
@@ -32520,6 +32611,7 @@ server.registerTool(
|
|
|
32520
32611
|
}
|
|
32521
32612
|
}
|
|
32522
32613
|
);
|
|
32614
|
+
registerGuideTool(server);
|
|
32523
32615
|
registerReadOnlyTools(server);
|
|
32524
32616
|
registerLifecycleTools(server);
|
|
32525
32617
|
registerAuthoringTools(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renanlido/tia-openness-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "MCP server (stdio) — thin HTTP client over the TIA Openness `serve` API. Zero Openness/net48 references.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"bundle": "node scripts/build.mjs",
|
|
27
|
+
"sync": "node scripts/sync-guide.mjs",
|
|
27
28
|
"build": "npm run typecheck && npm run bundle",
|
|
28
29
|
"start": "node dist/index.js",
|
|
29
30
|
"dev": "node --experimental-strip-types src/index.ts",
|