@renanlido/tia-openness-mcp 1.4.1 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +67 -14
  2. package/dist/index.js +1460 -376
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,26 +47,34 @@ generated from `mcp/content/*.md` (the canonical source) into `src/content.gen.t
47
47
  | `tia_ping` | `GET /health` + `GET /readiness` | Diagnoses connectivity and ACTIVATION of the `serve`. `{ base, reachable, activated, latencyMs, hint }`. Ideal for Mac→VM. |
48
48
  | `tia_readiness` | `GET /readiness` | Environment readiness **on the serve host** (x64, Windows group, PublicAPI, registry, instances). |
49
49
  | `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. |
50
+ | `tia_job_status` | — (does not call `serve`) | Waits (up to `waitSeconds`, default 20, max 25) for a long tool's background job and returns its FINAL result (see "Long operations run as jobs" below). |
50
51
 
51
52
  **M1 (read-only, 1:1 with the API's GET endpoints):** `tia_instances`, `tia_status`, `tia_projects`, `tia_project_info`,
52
53
  `tia_project_languages`, `tia_plcs`, `tia_devices`, `tia_device_tree`, `tia_device_connections`,
53
54
  `tia_plc_blocks/types/tags/watchtables`, `tia_catalog`, `tia_connections_diagnose`, `tia_subnets`,
54
- `tia_obj_roots/get/items/info/creationinfos/services`. None of them mutate state.
55
+ `tia_obj_roots/get/items/info/creationinfos/services`, `tia_pg_interfaces` (connection modes/PG-PC
56
+ interfaces the CPU sees, plus each adapter's OS status — **no network scan**, unlike
57
+ `tia_accessible_devices`; use it to pick `tia_download`'s connection params). None of them mutate state.
55
58
 
56
59
  ### M2 — lifecycle (class M-off) + trust GATE (G1)
57
60
  | Tool | Endpoint | Description |
58
61
  |---|---|---|
59
62
  | `tia_start` | `POST /tia/start` | Starts/attaches TIA **without a project** (optional `headless`). |
60
63
  | `tia_show_ui` | `POST /tia/show-ui` | Relaunches a headless TIA as **visible** to render the trust dialog (enables G1). |
61
- | `tia_connect` | `POST /connect` | Opens/attaches a project. **May block on GATE G1** → returns a `requires_human_action` envelope (no retry storm) with a `resumeToken`. UMAC (G6) only if a human provides it. |
62
- | `tia_disconnect` | `POST /disconnect` | Disconnects (Gd3: use `tia_project_close` to release the lock). |
64
+ | `tia_connect` | `POST /connect` | Opens/attaches a project. Idempotent for the already-open project (`alreadyOpen:true`) and REUSES a running TIA instance for a different one (`reusedPortal:true`, closing foreign projects — `closedForeign`); `withUserInterface` only applies when a NEW instance is started. **May block on GATE G1** → returns a `requires_human_action` envelope (no retry storm) with a `resumeToken`. UMAC (G6) only if a human provides it. |
65
+ | `tia_disconnect` | `POST /disconnect` | The explicit teardown for the TIA instance the serve created (an attached one is only detached) — but not the only path that can end an owned instance (`tia_show_ui`, `tia_project_create` and a `tia_connect` needing a fresh connect also end/replace it). Gd3: use `tia_project_close` to just release the project lock. |
63
66
  | `tia_project_create` | `POST /project/create` | Creates a project (Gd4: empty directory → otherwise 409). |
64
- | `tia_project_save` / `tia_project_saveas` / `tia_project_close` / `tia_project_archive` | `POST /project/*` | Project lifecycle. |
67
+ | `tia_project_save` / `tia_project_saveas` / `tia_project_close` / `tia_project_archive` | `POST /project/*` | Project lifecycle. `tia_project_close` KEEPS the TIA process alive (`portalKept`/`pid`) — the next `tia_connect` reuses it; nothing is saved. |
65
68
 
66
69
  **Trust GATE (G1):** on the 1st connection per `serve` process, TIA requires manual confirmation of a security
67
70
  dialog ON THE HOST. The proactive flow is **`tia_start` → `tia_show_ui` → (human confirms) → `tia_connect`**.
68
71
  If `tia_connect` hits the dialog, it does NOT retry-storm: it returns `{ status:"requires_human_action",
69
- gate:"trust_dialog", youMustDo, resumeToken }`. After the human confirms, call `tia_connect` again with the `resumeToken`.
72
+ gate:"trust_dialog", youMustDo, resumeToken }` after ~180 s without an answer (`TIA_CONNECT_TIMEOUT_MS`) with
73
+ `/health` still reachable. Check `tia_status` first — `hasProject:true` means the connect actually succeeded
74
+ (the dialog was a false positive on a slow open); with the job mechanism, a `status:"running"` answer from
75
+ `tia_connect`/`tia_start`/`tia_show_ui` can ALSO mean the dialog is waiting (`tia_status` itself answers `busy`
76
+ until the job ends) — keep polling `tia_job_status`. After the human confirms, call `tia_connect` again with
77
+ the `resumeToken`.
70
78
 
71
79
  ### M3 — offline creation/authoring (class M-off) + guards
72
80
  A **compliant-by-construction** creation surface (~28 tools), all with `dryRun` (Gd11) and schema validation (Gd13):
@@ -74,6 +82,7 @@ A **compliant-by-construction** creation surface (~28 tools), all with `dryRun`
74
82
  - **Tags/constants:** `tia_tagtable_create`, `tia_tag_create`/`delete`, `tia_constant_create`.
75
83
  - **Blocks/types:** `tia_fb_create`, `tia_instancedb_create`, `tia_block_delete`/`set_attribute`, `tia_block_group_create`, `tia_type_group_create`/`type_delete`.
76
84
  - **Code:** `tia_source_put` (textual SCL — Gd6), `tia_import_xml`/`tia_types_import` (graphical SimaticML — Gd6/Gd7), `tia_export_xml`.
85
+ - **Project-as-code proof (#26/#28):** `tia_export_block` (exports ONE block inline as SimaticML — `sha256`/`normalizedSha256`/`bytes`, no file on the serve host) and `tia_block_compare` (compares a block against the SimaticML you pass, e.g. the file in git — normalized, ignores BOM/formatting/`<Created>`/UIds — `verdict:"equal"|"different"` is the actual roundtrip proof; a raw hash is not). Both read-mostly: exporting an inconsistent block first compiles it (export requires consistency), which can regenerate its instance DB.
77
86
  - **Watch tables:** `tia_watchtable_create`/`delete`.
78
87
  - **Generic write layer:** `tia_obj_set_attributes`/`invoke`/`create`.
79
88
 
@@ -86,18 +95,33 @@ by name (check existence with the M1 read-only tools before creating; Openness h
86
95
  ### M4 — verification + GATED deploy (hardware)
87
96
  | Tool | Endpoint | Gate/class |
88
97
  |---|---|---|
89
- | `tia_compile` | `POST /plcs/{plc}/compile` | M-off — **the verifier**. Flattens the message tree + `verification:{compiled,errors,warnings}` + `nextHints` (Gd8: only ERRORs block). |
98
+ | `tia_compile` | `POST /plcs/{plc}/compile` | M-off — **the verifier**. Flattens the message tree + `verification:{compiled,errors,warnings}` + `nextHints` (Gd8: only ERRORs block). `blockChanges` (#28) lists blocks whose fingerprint changed since the compile's own before/after snapshot — a regenerated instance DB shows up here. |
90
99
  | `tia_connection_create` | `POST /connections` | M-off — S7 connection (G2: requires ≥2 consistent CPUs; failure attaches diagnostics). |
91
- | `tia_download` | `POST /plcs/{plc}/download` | **GATE G3** — never downloads without human approval bound to the `planHash`. |
100
+ | `tia_download` | `POST /plcs/{plc}/download/plan` + `POST /plcs/{plc}/download` | **GATE G3** — never downloads without human approval bound to the `planHash`. |
101
+ | `tia_upload` | `POST /stations/upload/plan` · `POST /stations/upload` | **GATE G4** (+G6 read password) — single-use planHash bound to the open project; runs as a job. |
92
102
  | `tia_online` / `tia_offline` / `tia_online_state` | `POST /plcs/{plc}/online`·`/offline`·`GET …/state` | **G4** (online) / R (state). |
93
103
  | `tia_accessible_devices` | `POST /plcs/{plc}/online/accessibledevices` | **GATE G7** (net scan). |
94
104
  | `tia_mastersecret_set` / `tia_mastersecret_reset` | `…/online/mastersecret` | **GATE G6** (the secret comes from a human, never from the AI). |
95
105
 
96
- **Download GATE (G3):** `tia_download` NEVER downloads without approval. Flow: `tia_compile` (Gd9 precondition: `errors=0`) →
97
- `tia_download dryRun:true` (returns `plan` + `planHash`, without downloading) → a human approves → `tia_download approved:true`
98
- with the **same `planHash`**. Wrong hash or missing `approved` → `requires_human_action / download_approval` envelope.
99
- **Honest limitation:** the `serve` still **auto-confirms** StopAll/Overwrite (C# backlog **P0.5**); until that is
100
- re-externalized, the real "Load Preview" is not exposed and G3 is the safeguard of the **MCP layer** (not bypassable by LLM text alone).
106
+ **Download GATE (G3, ADR 0008):** `tia_download` NEVER downloads without approval, and the approval is bound to
107
+ the approved connection/options and the covered software content — **not the hardware configuration**. Flow: `tia_compile` (Gd9 precondition: `errors=0`)
108
+ → `tia_download dryRun:true`, which asks **serve** for a VERIFIED plan (`POST /plcs/{plc}/download/plan`: fresh compile,
109
+ resolved connection, and a **software fingerprint** — content hash of program blocks/UDTs/tag tables; NOT hardware,
110
+ system block groups or technology objects — so a hardware change after approval passes the token check undetected:
111
+ any hardware change needs fresh human review, not the old approval) → only a `ready` plan returns a single-use `planHash` that expires
112
+ (`TIA_PLAN_TTL_MS`, default 15 min, MCP-process-only) → a human approves the plan **and its fingerprint** → `tia_download
113
+ approved:true` with the **same `planHash`**. `serve` re-checks the fingerprint **inside** the download call (no window
114
+ between check and load) and refuses **`409 plan_stale`** if the software changed since the plan. `allowUpgradeTargetDevice:
115
+ true` (#31) is part of the approved plan — without it, a CPU that requires TIA's "upgrade the target device" confirmation
116
+ refuses the load with **`409 upgrade_target_device_required`** (Openness aborts; check the CPU state before retrying —
117
+ earlier confirmations in the same pass, like `StopAll`, cannot be undone). A successful dry-run is NOT a handoff —
118
+ it returns `status:"plan_ok"` with the `planHash`. Calling the real download without `approved:true` (or with a
119
+ wrong/expired/consumed hash or a parameter mismatch) returns a `requires_human_action / download_approval` envelope
120
+ with a `reasonCode` (`not_approved` \| `unknown` \| `expired` \| `consumed` \| `plan_mismatch` \| `outcome_unknown`).
121
+ `serve_too_old` is NOT a `reasonCode`: a serve without the plan endpoint fails the dry-run with an ordinary
122
+ `isError:true` result with `kind:"serve_too_old"` (update the app, never fall back to the unverified path).
123
+ The auto-confirmed selections (StopAll/Overwrite/Start/…) remain visible in the download result's `configurations`
124
+ (transparency record, P0.5) — pick the connection with `tia_pg_interfaces` (no scan).
101
125
 
102
126
  ### M5 — project-spec ingestion (orchestrates M1–M4)
103
127
  Accepts the structured **project-spec** (`docs/TIA-BEST-PRACTICES.md` §13.2; JSON Schema at `.claude-plugin/schemas/project-spec.schema.json`):
@@ -105,15 +129,44 @@ Accepts the structured **project-spec** (`docs/TIA-BEST-PRACTICES.md` §13.2; JS
105
129
  - **`tia_spec_plan`** (no serve needed): turns a valid spec into the ordered operation sequence (subnets→devices→modules→network→tags→blocks→compile).
106
130
  - **`tia_spec_apply`**: orchestrates the OFFLINE build on an open project (validate → G5 → execute serially → compile), **stops at the 1st error** and **never downloads** (download is GATE G3, always human). `dryRun:true` returns the plan.
107
131
 
108
- > **MCP M0–M5 COMPLETE** (76 tools), **runtime-validated** against a real TIA Portal V21 (including a
132
+ > **MCP M0–M5 COMPLETE** (81 tools in this branch — the published npm `@renanlido/tia-openness-mcp` 1.4.1 still
133
+ > ships 76 until the next release), **runtime-validated** against a real TIA Portal V21 (including a
109
134
  > **hardware deploy** to an S7-1200) and **published to npm**. Roadmap and milestones in `docs/ROADMAP.md` §3.
110
135
 
136
+ ### Long operations run as jobs (ADR 0009)
137
+
138
+ Opening a project takes 40–80 s in the field, and a compile/download of a large project can take just
139
+ as long — well past the ~30 s ceiling most MCP clients enforce per call. **14 tools** run through a
140
+ soft-deadline job runner instead of blocking the call: `tia_connect`, `tia_start`, `tia_show_ui`,
141
+ `tia_project_create`, `tia_project_save`, `tia_project_close`, `tia_project_archive`, `tia_compile`,
142
+ `tia_download` (plan and real), `tia_upload`, `tia_export_xml`, `tia_spec_apply`, `tia_export_block`, `tia_block_compare`.
143
+
144
+ - If the operation finishes before `TIA_SOFT_DEADLINE_MS` (default 20 s, capped at 25 s), the call
145
+ returns the normal result — nothing changes for the fast case.
146
+ - If it doesn't, the call returns `{status:"running", jobId}` **without an error** and keeps running in
147
+ the background inside the MCP process. Call **`tia_job_status {jobId, waitSeconds?}`** (waits up to
148
+ `waitSeconds`, default 20, max 25) until it returns the final result — including a gate handoff, if the
149
+ operation ended in one. Never repeat the original call.
150
+ - **Single-flight:** while a job is running, every OTHER ordinary serve-bound tool answers `kind:"busy"` (with
151
+ `details.jobId`) immediately instead of queuing behind the worker — go back to `tia_job_status`.
152
+ `tia_guide`, `tia_spec_validate`, `tia_spec_plan` and `tia_job_status` itself never answer busy (they
153
+ don't call `serve`). `tia_ping` is the diagnostic exception: it probes `/health` (which bypasses the
154
+ busy guard) and returns a reachability report instead of `busy` — with no `details.jobId`, so it cannot
155
+ be used to obtain the job ID. The same long tool called again with the SAME key (e.g. `tia_connect` for the
156
+ same project) returns the same `jobId` instead of starting a second run.
157
+ - If a `tia_connect`/`tia_start`/`tia_show_ui` job stays `running` for minutes, a **trust dialog (G1)**
158
+ may be waiting on the serve host — ask a human to look.
159
+
111
160
  ## Configuration (env)
112
161
 
113
162
  | Var | Default | Purpose |
114
163
  |---|---|---|
115
164
  | `TIA_API_BASE` | `http://localhost:5000` | Base URL of the `serve` API. |
116
165
  | `TIA_API_TIMEOUT_MS` | `15000` | Per-request timeout (fail fast if the host/VM does not respond). |
166
+ | `TIA_SLOW_TIMEOUT_MS` | `120000` | Timeout for slow/cold ops (project create, catalog, download plan and other slow M1–M3 calls). `tia_compile` does NOT use this variable — it has a separate fixed 120 s timeout. |
167
+ | `TIA_CONNECT_TIMEOUT_MS` | `180000` | Timeout for the calls that start a new TIA (`tia_connect`, `tia_project_create`, `tia_start`, `tia_show_ui`) before a wait is treated as a possible G1 trust-dialog gate. |
168
+ | `TIA_SOFT_DEADLINE_MS` | `20000` | Soft deadline for the 14 long tools before they answer `status:"running"` (capped at `25000`). |
169
+ | `TIA_PLAN_TTL_MS` | `900000` | Validity of a G3 download `planHash` (15 min); the token lives only in this MCP process. |
117
170
 
118
171
  > **The MCP sends no key.** The API is **activated by a license** configured in the GUI ("License" tab) — what
119
172
  > authorizes the server is the host's license, not the client. The MCP is a plain HTTP client: it only points at the base URL.
@@ -231,7 +284,7 @@ guide — with each client's exact format and the Windows `cmd /c npx` gotcha
231
284
  `docs/INSTALL.md` in the repository.
232
285
 
233
286
  **Optional envs** (defaults are built into the MCP — only set them to override): `TIA_API_TIMEOUT_MS` (15000),
234
- `TIA_SLOW_TIMEOUT_MS` (120000), `TIA_CONNECT_TIMEOUT_MS` (30000). E.g. `--env TIA_SLOW_TIMEOUT_MS=180000`
287
+ `TIA_SLOW_TIMEOUT_MS` (120000), `TIA_CONNECT_TIMEOUT_MS` (180000). E.g. `--env TIA_SLOW_TIMEOUT_MS=180000`
235
288
  on `claude mcp add`, or the corresponding key in the `.mcp.json` `env`.
236
289
 
237
290
  Since it is a **project-scoped** server, Claude Code asks for **approval** the 1st time: `claude mcp list`