@renanlido/tia-openness-mcp 1.4.0 → 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 +195 -138
  2. package/dist/index.js +1460 -376
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,16 +1,18 @@
1
1
  # TIA Openness MCP server
2
2
 
3
- Servidor **MCP (stdio)** que é um **cliente HTTP puro** sobre a API `serve` do TIA Openness
4
- (veja `docs/API-HTTP.md` no repositório). Não referencia Openness / net48 / x64 — toda a
5
- complexidade (`AssemblyResolve`, worker serial MTA) vive no processo `serve`. Aqui só falamos **MCP de um
6
- lado** e **HTTP/JSON do outro**.
3
+ [![npm](https://img.shields.io/npm/v/%40renanlido%2Ftia-openness-mcp)](https://www.npmjs.com/package/@renanlido/tia-openness-mcp)
7
4
 
8
- **Cross-platform:** o MCP é Node puro (bundle ESM com shebang) e roda em **macOS / Linux / Windows**
9
- (Node ≥ 20). Só a **API `serve` é Windows-only** (net48/x64 + Openness) — por isso o arranjo canônico é
10
- **MCP no seu laptop (ex.: Mac) → `serve` num host/VM Windows**, apontado por env (veja abaixo).
5
+ An **MCP (stdio) server** that is a **pure HTTP client** of the TIA Openness `serve` API
6
+ (see `docs/API-HTTP.md` in the repository). It does not reference Openness / net48 / x64 — all of that
7
+ complexity (`AssemblyResolve`, serial MTA worker) lives in the `serve` process. Here we only speak **MCP on
8
+ one side** and **HTTP/JSON on the other**.
11
9
 
12
- Distribuído como **pacote npm bundled** (esbuild → 1 arquivo self-contained, sem `node_modules` em runtime),
13
- publicado no **npm público** via **semantic-release** — rode com um one-liner, **sem auth e sem `.npmrc`**:
10
+ **Cross-platform:** the MCP is pure Node (ESM bundle with shebang) and runs on **macOS / Linux / Windows**
11
+ (Node ≥ 20). Only the **`serve` API is Windows-only** (net48/x64 + Openness) — which is why the canonical
12
+ setup is **MCP on your laptop (e.g. a Mac) → `serve` on a Windows host/VM**, pointed at via env (see below).
13
+
14
+ Distributed as a **bundled npm package** (esbuild → 1 self-contained file, no `node_modules` at runtime),
15
+ published to the **public npm registry** via **semantic-release** — run it with a one-liner, **no auth, no `.npmrc`**:
14
16
 
15
17
  ```bash
16
18
  npx -y @renanlido/tia-openness-mcp
@@ -39,216 +41,271 @@ generated from `mcp/content/*.md` (the canonical source) into `src/content.gen.t
39
41
 
40
42
  ## Tools
41
43
 
42
- ### Diagnóstico + M1 read-only (classe R, auto-aprováveis)
43
- | Tool | Endpoint | Descrição |
44
+ ### Diagnostics + M1 read-only (class R, auto-approvable)
45
+ | Tool | Endpoint | Description |
44
46
  |---|---|---|
45
- | `tia_ping` | `GET /health` + `GET /readiness` | Diagnostica conectividade e ATIVAÇÃO do `serve`. `{ base, reachable, activated, latencyMs, hint }`. Ideal p/ Mac→VM. |
46
- | `tia_readiness` | `GET /readiness` | Prontidão do ambiente **no host do serve** (x64, grupo Windows, PublicAPI, registro, instâncias). |
47
+ | `tia_ping` | `GET /health` + `GET /readiness` | Diagnoses connectivity and ACTIVATION of the `serve`. `{ base, reachable, activated, latencyMs, hint }`. Ideal for Mac→VM. |
48
+ | `tia_readiness` | `GET /readiness` | Environment readiness **on the serve host** (x64, Windows group, PublicAPI, registry, instances). |
47
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). |
48
51
 
49
- **M1 (read-only, 1:1 com os GET da API):** `tia_instances`, `tia_status`, `tia_projects`, `tia_project_info`,
52
+ **M1 (read-only, 1:1 with the API's GET endpoints):** `tia_instances`, `tia_status`, `tia_projects`, `tia_project_info`,
50
53
  `tia_project_languages`, `tia_plcs`, `tia_devices`, `tia_device_tree`, `tia_device_connections`,
51
54
  `tia_plc_blocks/types/tags/watchtables`, `tia_catalog`, `tia_connections_diagnose`, `tia_subnets`,
52
- `tia_obj_roots/get/items/info/creationinfos/services`. Nenhuma muta estado.
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.
53
58
 
54
- ### M2 — ciclo de vida (classe M-off) + GATE de confiança (G1)
55
- | Tool | Endpoint | Descrição |
59
+ ### M2 — lifecycle (class M-off) + trust GATE (G1)
60
+ | Tool | Endpoint | Description |
56
61
  |---|---|---|
57
- | `tia_start` | `POST /tia/start` | Inicia/anexa o TIA **sem projeto** (`headless` opcional). |
58
- | `tia_show_ui` | `POST /tia/show-ui` | Relança o TIA headless como **visível** p/ renderizar o diálogo de confiança (habilita o G1). |
59
- | `tia_connect` | `POST /connect` | Abre/anexa um projeto. **Pode bloquear no GATE G1** → devolve um envelope `requires_human_action` (sem retry-storm) com `resumeToken`. UMAC (G6) só se um humano fornecer. |
60
- | `tia_disconnect` | `POST /disconnect` | Desconecta (Gd3: use `tia_project_close` p/ liberar o lock). |
61
- | `tia_project_create` | `POST /project/create` | Cria projeto (Gd4: diretório vazio → senão 409). |
62
- | `tia_project_save` / `tia_project_saveas` / `tia_project_close` / `tia_project_archive` | `POST /project/*` | Ciclo de vida do projeto. |
63
-
64
- **GATE de confiança (G1):** na 1ª conexão por processo `serve`, o TIA exige confirmação manual de um diálogo
65
- de segurança NO HOST. O fluxo proativo é **`tia_start` → `tia_show_ui` → (humano confirma) → `tia_connect`**.
66
- Se o `tia_connect` esbarra no diálogo, ele NÃO faz retry-storm: devolve `{ status:"requires_human_action",
67
- gate:"trust_dialog", youMustDo, resumeToken }`. Após o humano confirmar, re-chame `tia_connect` com o `resumeToken`.
68
-
69
- ### M3 — criação/autoria offline (classe M-off) + guards
70
- Superfície de **criação compliant-by-construction** (~28 tools), todas com `dryRun` (Gd11) e validação por schema (Gd13):
71
- - **Hardware/rede:** `tia_device_create`/`plug`/`unplug`, `tia_device_group_create`, `tia_subnet_create`, `tia_network_connect`/`address`/`disconnect`.
72
- - **Tags/constantes:** `tia_tagtable_create`, `tia_tag_create`/`delete`, `tia_constant_create`.
73
- - **Blocos/tipos:** `tia_fb_create`, `tia_instancedb_create`, `tia_block_delete`/`set_attribute`, `tia_block_group_create`, `tia_type_group_create`/`type_delete`.
74
- - **Código:** `tia_source_put` (SCL textual — Gd6), `tia_import_xml`/`tia_types_import` (SimaticML gráfico — Gd6/Gd7), `tia_export_xml`.
62
+ | `tia_start` | `POST /tia/start` | Starts/attaches TIA **without a project** (optional `headless`). |
63
+ | `tia_show_ui` | `POST /tia/show-ui` | Relaunches a headless TIA as **visible** to render the trust dialog (enables G1). |
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. |
66
+ | `tia_project_create` | `POST /project/create` | Creates a project (Gd4: empty directory → otherwise 409). |
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. |
68
+
69
+ **Trust GATE (G1):** on the 1st connection per `serve` process, TIA requires manual confirmation of a security
70
+ dialog ON THE HOST. The proactive flow is **`tia_start` → `tia_show_ui` → (human confirms) → `tia_connect`**.
71
+ If `tia_connect` hits the dialog, it does NOT retry-storm: it returns `{ status:"requires_human_action",
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`.
78
+
79
+ ### M3 — offline creation/authoring (class M-off) + guards
80
+ A **compliant-by-construction** creation surface (~28 tools), all with `dryRun` (Gd11) and schema validation (Gd13):
81
+ - **Hardware/network:** `tia_device_create`/`plug`/`unplug`, `tia_device_group_create`, `tia_subnet_create`, `tia_network_connect`/`address`/`disconnect`.
82
+ - **Tags/constants:** `tia_tagtable_create`, `tia_tag_create`/`delete`, `tia_constant_create`.
83
+ - **Blocks/types:** `tia_fb_create`, `tia_instancedb_create`, `tia_block_delete`/`set_attribute`, `tia_block_group_create`, `tia_type_group_create`/`type_delete`.
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.
75
86
  - **Watch tables:** `tia_watchtable_create`/`delete`.
76
- - **Camada genérica de escrita:** `tia_obj_set_attributes`/`invoke`/`create`.
87
+ - **Generic write layer:** `tia_obj_set_attributes`/`invoke`/`create`.
77
88
 
78
- **Guards (client-side, antes de tocar o Openness):** **Gd5** — `typeIdentifier` deve usar `/V<fw>` (barra), nunca `:V`
79
- (rejeitado com `kind:"guard_violation"`; resolva o valor via `tia_catalog`). **Gd11** — `dryRun:true` valida os guards
80
- e retorna o **plano por nome SEM mutar** (`status:"plan_ok"`). **Gd6** (advisório) — linguagem textual (SCL) via
81
- `tia_source_put`, gráfica (LAD/FBD/GRAPH) via `tia_import_xml`; UDTs antes dos blocos. **Gd2** (advisório) — idempotência
82
- por nome (cheque a existência com as tools read-only do M1 antes de criar; o Openness não tem rollback).
89
+ **Guards (client-side, before touching Openness):** **Gd5** — `typeIdentifier` must use `/V<fw>` (slash), never `:V`
90
+ (rejected with `kind:"guard_violation"`; resolve the value via `tia_catalog`). **Gd11** — `dryRun:true` validates the guards
91
+ and returns the **plan by name WITHOUT mutating** (`status:"plan_ok"`). **Gd6** (advisory) — textual language (SCL) via
92
+ `tia_source_put`, graphical (LAD/FBD/GRAPH) via `tia_import_xml`; UDTs before blocks. **Gd2** (advisory) — idempotency
93
+ by name (check existence with the M1 read-only tools before creating; Openness has no rollback).
83
94
 
84
- ### M4 — verificação + deploy GATED (hardware)
85
- | Tool | Endpoint | Gate/classe |
95
+ ### M4 — verification + GATED deploy (hardware)
96
+ | Tool | Endpoint | Gate/class |
86
97
  |---|---|---|
87
- | `tia_compile` | `POST /plcs/{plc}/compile` | M-off — **o verificador**. Achata a árvore de mensagens + `verification:{compiled,errors,warnings}` + `nextHints` (Gd8: só ERRO bloqueia). |
88
- | `tia_connection_create` | `POST /connections` | M-off — conexão S7 (G2: exige ≥2 CPUs consistentes; falha anexa diagnóstico). |
89
- | `tia_download` | `POST /plcs/{plc}/download` | **GATE G3** — não baixa sem aprovação humana vinculada ao `planHash`. |
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. |
99
+ | `tia_connection_create` | `POST /connections` | M-off — S7 connection (G2: requires ≥2 consistent CPUs; failure attaches diagnostics). |
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. |
90
102
  | `tia_online` / `tia_offline` / `tia_online_state` | `POST /plcs/{plc}/online`·`/offline`·`GET …/state` | **G4** (online) / R (state). |
91
103
  | `tia_accessible_devices` | `POST /plcs/{plc}/online/accessibledevices` | **GATE G7** (net scan). |
92
- | `tia_mastersecret_set` / `tia_mastersecret_reset` | `…/online/mastersecret` | **GATE G6** (segredo vem do humano, nunca da IA). |
93
-
94
- **GATE de download (G3):** `tia_download` NUNCA baixa sem aprovação. Fluxo: `tia_compile` (precondição Gd9: `errors=0`) →
95
- `tia_download dryRun:true` (devolve `plan` + `planHash`, sem baixar) → um humano aprova → `tia_download approved:true`
96
- com o **mesmo `planHash`**. Hash errado ou `approved` ausente → envelope `requires_human_action / download_approval`.
97
- **Limitação honesta:** o `serve` ainda **auto-confirma** StopAll/Overwrite (backlog C# **P0.5**); até isso ser
98
- re-externalizado, o "Load Preview" real não é exposto e o G3 é a salvaguarda da **camada do MCP** (não bypassável só por texto do LLM).
99
-
100
- ### M5 — ingestão de project-spec (orquestra M1–M4)
101
- Aceita o **project-spec** estruturado (`docs/TIA-BEST-PRACTICES.md` §13.2; JSON Schema em `.claude-plugin/schemas/project-spec.schema.json`):
102
- - **`tia_spec_validate`** (R, determinístico, **sem serve**): valida forma + regras de campo cruzado — R-NAMES (nomes únicos), R-TYPEID (`OrderNumber:<MLFB>/V<fw>`), R-SUBNET (IP∈CIDR, CPU+IO na mesma subnet, 1 IO-system), R-LANGFAM (STL/GRAPH só S7-1500), R-MODLIMIT, e **G5** (`safety.required && autonomous ⇒ REJECT`).
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).
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.
105
-
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.
107
-
108
- ## Configuração (env)
109
-
110
- | Var | Default | Uso |
104
+ | `tia_mastersecret_set` / `tia_mastersecret_reset` | `…/online/mastersecret` | **GATE G6** (the secret comes from a human, never from the AI). |
105
+
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).
125
+
126
+ ### M5 — project-spec ingestion (orchestrates M1–M4)
127
+ Accepts the structured **project-spec** (`docs/TIA-BEST-PRACTICES.md` §13.2; JSON Schema at `.claude-plugin/schemas/project-spec.schema.json`):
128
+ - **`tia_spec_validate`** (R, deterministic, **no serve needed**): validates shape + cross-field rules — R-NAMES (unique names), R-TYPEID (`OrderNumber:<MLFB>/V<fw>`), R-SUBNET (IP∈CIDR, CPU+IO on the same subnet, 1 IO-system), R-LANGFAM (STL/GRAPH only on S7-1500), R-MODLIMIT, and **G5** (`safety.required && autonomous ⇒ REJECT`).
129
+ - **`tia_spec_plan`** (no serve needed): turns a valid spec into the ordered operation sequence (subnets→devices→modules→network→tags→blocks→compile).
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.
131
+
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
134
+ > **hardware deploy** to an S7-1200) and **published to npm**. Roadmap and milestones in `docs/ROADMAP.md` §3.
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
+
160
+ ## Configuration (env)
161
+
162
+ | Var | Default | Purpose |
111
163
  |---|---|---|
112
- | `TIA_API_BASE` | `http://localhost:5000` | Base URL da API `serve`. |
113
- | `TIA_API_TIMEOUT_MS` | `15000` | Timeout por request (falha rápido se o host/VM não responde). |
164
+ | `TIA_API_BASE` | `http://localhost:5000` | Base URL of the `serve` API. |
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. |
114
170
 
115
- > **O MCP não envia chave.** A API é **ativada por uma licença** configurada na GUI (aba "Licença") — quem
116
- > autoriza o servidor é a licença do host, não o cliente. O MCP é um cliente HTTP normal: só aponta a base.
171
+ > **The MCP sends no key.** The API is **activated by a license** configured in the GUI ("License" tab) — what
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.
117
173
 
118
- ## Cenário Mac → VM Windows (apontar o servidor)
174
+ ## Mac → Windows VM scenario (pointing at the server)
119
175
 
120
- 1. **Na VM Windows**, faça tudo pela **GUI de bandeja** (atalho "TIA Openness API"):
121
- - aba **"Licença"** → cole a chave de ativação → **"Validar"** → **"Salvar"** (a API só aceita
122
- conexões externas se estiver **ativada**);
123
- - aba **"API"** → **Host** = `0.0.0.0` (aceita conexões de fora; `localhost` só atende a própria VM),
124
- **Porta** = `5000` → **"Iniciar API"**. O status fica **verde** ("Rodando em http://0.0.0.0:5000/ …").
176
+ 1. **On the Windows VM**, do everything through the **tray GUI** ("TIA Openness API" shortcut):
177
+ - **"License"** tab → paste the activation key → **"Validate"** → **"Save"** (the API only accepts
178
+ external connections when it is **activated**);
179
+ - **"API"** tab → **Host** = `0.0.0.0` (accepts outside connections; `localhost` only serves the VM itself),
180
+ **Port** = `5000` → **"Start API"**. The status turns **green** ("Running at http://0.0.0.0:5000/ …").
125
181
 
126
- Libere a porta no firewall do Windows e garanta rede acessível (bridged ou NAT com port-forward). O
127
- controle de **quem** pode chamar é o firewall/VPN — a licença ativa o servidor, não autentica clientes.
182
+ Open the port in the Windows firewall and make sure the network is reachable (bridged, or NAT with port
183
+ forwarding). Controlling **who** can call is the firewall/VPN's job — the license activates the server, it
184
+ does not authenticate clients.
128
185
 
129
- <details><summary>Alternativa headless por linha de comando (avançado)</summary>
186
+ <details><summary>Headless command-line alternative (advanced)</summary>
130
187
 
131
188
  ```powershell
132
189
  .\bin\Debug\net48\TIAOpenness.exe serve 5000 --host 0.0.0.0 --license TIA1.xxxxx.yyyyy
133
190
  ```
134
191
  </details>
135
- 2. **No Mac**, aponte o MCP para a VM:
192
+ 2. **On the Mac**, point the MCP at the VM:
136
193
  ```bash
137
- export TIA_API_BASE="http://<ip-da-vm>:5000"
194
+ export TIA_API_BASE="http://<vm-ip>:5000"
138
195
  ```
139
- 3. **Diagnostique** com o tool `tia_ping`: `reachable:false` → IP/porta/firewall/VM; `activated:false` →
140
- licença ausente/inválida na VM; ambos `true` → pronto.
196
+ 3. **Diagnose** with the `tia_ping` tool: `reachable:false` → IP/port/firewall/VM; `activated:false` →
197
+ license missing/invalid on the VM; both `true` → ready.
141
198
 
142
199
  ## Build / bundle
143
200
 
144
201
  ```bash
145
- # garanta o Node (no Windows com nvm-windows: $env:Path = "C:\nvm4w\nodejs;" + $env:Path)
202
+ # make sure Node is available (on Windows with nvm-windows: $env:Path = "C:\nvm4w\nodejs;" + $env:Path)
146
203
  cd mcp
147
204
  npm install
148
205
  npm run build # typecheck + esbuild -> dist/index.js (self-contained)
149
206
  ```
150
207
 
151
- ## Instalação
208
+ ## Installation
152
209
 
153
- ### A) Do npm público (uso normal, Mac/Windows/Linux)
210
+ ### A) From public npm (normal use, Mac/Windows/Linux)
154
211
 
155
- Pacote **público** — **sem auth, sem `.npmrc`**. Use direto via `npx` ou instale o binário global:
212
+ **Public** package — **no auth, no `.npmrc`**. Use it directly via `npx` or install the global binary:
156
213
 
157
214
  ```bash
158
- npx -y @renanlido/tia-openness-mcp # roda sem instalar (recomendado p/ .mcp.json)
159
- # ou instale o binário `tia-openness-mcp` no PATH:
215
+ npx -y @renanlido/tia-openness-mcp # runs without installing (recommended for .mcp.json)
216
+ # or install the `tia-openness-mcp` binary on PATH:
160
217
  npm install -g @renanlido/tia-openness-mcp
161
218
  ```
162
219
 
163
- ### B) Local, sem publicar (dev nesta máquina)
220
+ ### B) Local, without publishing (dev on this machine)
164
221
 
165
222
  ```bash
166
223
  cd mcp
167
- npm install -g . # o prepack compila e instala o binário
168
- # ou: npm pack && npm install -g ./renanlido-tia-openness-mcp-*.tgz
224
+ npm install -g . # prepack builds and installs the binary
225
+ # or: npm pack && npm install -g ./renanlido-tia-openness-mcp-*.tgz
169
226
  ```
170
227
 
171
- ## Publicação (npm público + semantic-release)
228
+ ## Publishing (public npm + semantic-release)
172
229
 
173
- Versionamento **automático por commits convencionais** (`feat` → minor, `fix` → patch, `BREAKING CHANGE`
174
- → major). O workflow `.github/workflows/release-mcp.yml` dispara
175
- no push para `main` **quando algo em `mcp/` muda** (e `semantic-release-monorepo` filtra os commits por
176
- esse path), builda e publica no **npm público** (`registry.npmjs.org`, `publishConfig.access: public`).
230
+ **Automatic versioning by conventional commits** (`feat` → minor, `fix` → patch, `BREAKING CHANGE`
231
+ → major). The `.github/workflows/release-mcp.yml` workflow fires on pushes to `main` **when something under
232
+ `mcp/` changes** (and `semantic-release-monorepo` filters the commits by that path), builds and publishes to
233
+ **public npm** (`registry.npmjs.org`, `publishConfig.access: public`).
177
234
 
178
- Usa **dois tokens**: `NPM_TOKEN` (secret a configurar — token *Automation/Publish* do npmjs.org) para o
179
- **publish** via `@semantic-release/npm`, e o `GITHUB_TOKEN` nativo do Actions para criar a release/tag e
180
- commitar o `CHANGELOG`.
235
+ It uses **two tokens**: `NPM_TOKEN` (a repository secret — an *Automation/Publish* token from npmjs.org) for the
236
+ **publish** via `@semantic-release/npm`, and the native Actions `GITHUB_TOKEN` to create the release/tag and
237
+ commit the `CHANGELOG`.
181
238
 
182
- **Para ligar (passos com credencial, uma vez):**
183
- 1. Crie o repo no GitHub (`renanlido/TIAOpenness`) — ou ajuste `repository.url`/scope no `package.json` e
184
- o workflow se o owner for outro.
185
- 2. **Configure o secret `NPM_TOKEN`** no repositório (Settings → Secrets and variables → Actions): gere um
186
- token de *Automation* em npmjs.org com permissão de publish no pacote `@renanlido/tia-openness-mcp`.
239
+ **To wire it up (credential steps, one time):**
240
+ 1. Create the GitHub repo (`renanlido/TIAOpenness`) — or adjust `repository.url`/scope in `package.json` and
241
+ the workflow if the owner differs.
242
+ 2. **Configure the `NPM_TOKEN` secret** on the repository (Settings → Secrets and variables → Actions): generate an
243
+ *Automation* token on npmjs.org with publish permission on the `@renanlido/tia-openness-mcp` package.
187
244
  3. `git add . && git commit -m "feat: initial commit"` → `git branch -M main` →
188
245
  `git remote add origin <url>` → `git push -u origin main`.
189
- 4. A partir daí, **commits convencionais** na `main` disparam o release automaticamente.
246
+ 4. From then on, **conventional commits** on `main` trigger the release automatically.
190
247
 
191
- > Config validada localmente: versões dedupadas em `semantic-release@24`; o dry-run carregou config/plugins
192
- > e só parou por o repo remoto ainda não existir.
248
+ > Config validated locally: versions deduped at `semantic-release@24`; the dry-run loaded config/plugins
249
+ > and only stopped because the remote repo did not exist yet.
193
250
 
194
- ## Registro no Claude Code
251
+ ## Registering in Claude Code
195
252
 
196
- **Não precisa instalar nada** — o recomendado é o `npx`, que baixa e roda o pacote publicado. Um comando
197
- só (escopo de **usuário**):
253
+ **Nothing to install** — the recommended way is `npx`, which downloads and runs the published package. One
254
+ command (**user** scope):
198
255
 
199
256
  ```bash
200
257
  claude mcp add --transport stdio --env TIA_API_BASE=http://localhost:5000 --scope user tia -- npx -y @renanlido/tia-openness-mcp
201
258
  ```
202
259
 
203
- Confira com `claude mcp list` e `claude mcp get tia`. O equivalente por **`.mcp.json`** na raiz do projeto
204
- (escopo de **projeto**) — também via `npx`, sem instalar:
260
+ Check with `claude mcp list` and `claude mcp get tia`. The **`.mcp.json`** equivalent at the project root
261
+ (**project** scope) — also via `npx`, nothing installed:
205
262
 
206
263
  ```jsonc
207
264
  {
208
265
  "mcpServers": {
209
266
  "tia": {
210
- "command": "npx", // baixa e roda o pacote do npm — NADA instalado
267
+ "command": "npx", // downloads and runs the npm package — NOTHING installed
211
268
  "args": ["-y", "@renanlido/tia-openness-mcp"],
212
269
  "env": {
213
- "TIA_API_BASE": "${TIA_API_BASE:-http://localhost:5000}" // IP:porta do serve (use o IP da VM se remoto)
270
+ "TIA_API_BASE": "${TIA_API_BASE:-http://localhost:5000}" // serve IP:port (use the VM IP if remote)
214
271
  }
215
272
  }
216
273
  }
217
274
  }
218
275
  ```
219
276
 
220
- > **`tia-openness-mcp` NÃO é um arquivo do repositório** — é o nome do **bin** do pacote (`package.json` →
221
- > `"bin": { "tia-openness-mcp": "dist/index.js" }`). Esse atalho só fica no seu PATH **se** você instalar
222
- > global (`npm install -g @renanlido/tia-openness-mcp`); só então faria sentido
223
- > `"command": "tia-openness-mcp", "args": []`. O `npx` dispensa esse passo — por isso é o recomendado.
277
+ > **`tia-openness-mcp` is NOT a file in the repository** — it is the package's **bin** name (`package.json` →
278
+ > `"bin": { "tia-openness-mcp": "dist/index.js" }`). That shortcut only lands on your PATH **if** you install
279
+ > globally (`npm install -g @renanlido/tia-openness-mcp`); only then would
280
+ > `"command": "tia-openness-mcp", "args": []` make sense. `npx` skips that step — which is why it is the recommended way.
224
281
 
225
- **Outros clientes** (Claude Desktop, Codex, Gemini CLI, Cursor, VS Code, Windsurf): o guia canônico de
226
- instalação — com os formatos exatos de cada um e a pegadinha do `cmd /c npx` no Windows — está em
227
- `docs/INSTALL.md` no repositório.
282
+ **Other clients** (Claude Desktop, Codex, Gemini CLI, Cursor, VS Code, Windsurf): the canonical install
283
+ guide — with each client's exact format and the Windows `cmd /c npx` gotcha — is in
284
+ `docs/INSTALL.md` in the repository.
228
285
 
229
- **Envs opcionais** (default embutido no MCP — só defina para sobrescrever): `TIA_API_TIMEOUT_MS` (15000),
230
- `TIA_SLOW_TIMEOUT_MS` (120000), `TIA_CONNECT_TIMEOUT_MS` (30000). Ex.: `--env TIA_SLOW_TIMEOUT_MS=180000`
231
- no `claude mcp add`, ou a chave correspondente no `env` do `.mcp.json`.
286
+ **Optional envs** (defaults are built into the MCP — only set them to override): `TIA_API_TIMEOUT_MS` (15000),
287
+ `TIA_SLOW_TIMEOUT_MS` (120000), `TIA_CONNECT_TIMEOUT_MS` (180000). E.g. `--env TIA_SLOW_TIMEOUT_MS=180000`
288
+ on `claude mcp add`, or the corresponding key in the `.mcp.json` `env`.
232
289
 
233
- Como é servidor de **escopo de projeto**, o Claude Code pede **aprovação** na 1ª vez: `claude mcp list`
234
- (deve listar `tia`), rode `claude` no projeto e **aprove**, **recarregue** a sessão, e peça para chamar
290
+ Since it is a **project-scoped** server, Claude Code asks for **approval** the 1st time: `claude mcp list`
291
+ (should list `tia`), run `claude` in the project and **approve**, **reload** the session, and ask it to call
235
292
  `tia_ping` / `tia_readiness`.
236
293
 
237
294
  ## Smoke test
238
295
 
239
296
  ```bash
240
- npm run smoke # alvo = dist/index.js local
241
- # apontar para o binário global:
242
- MCP_COMMAND=tia-openness-mcp MCP_ARGS='[]' npm run smoke # (Windows: use o caminho do .cmd)
297
+ npm run smoke # target = local dist/index.js
298
+ # point at the global binary:
299
+ MCP_COMMAND=tia-openness-mcp MCP_ARGS='[]' npm run smoke # (Windows: use the .cmd path)
243
300
  ```
244
301
 
245
- ## Notas
302
+ ## Notes
246
303
 
247
- - **O MCP não envia chave/segredo.** A API é ativada por licença no host (GUI). Segredos (master secret /
248
- UMAC / senha de cert) nunca vão no `.mcp.json`.
249
- - **Serial-only**: o backend é um worker serial; o MCP nunca deve paralelizar tool calls (Gd1).
250
- - **stdout é do protocolo MCP** — logs vão para **stderr**.
251
- - O bundle é gerado por [scripts/build.mjs](scripts/build.mjs) (esbuild, ESM, shebang + `createRequire`,
252
- versão injetada). `dist/`, `node_modules/`, `*.tgz` são artefatos (gitignored).
253
- - Ao adicionar/alterar tools, **rebuild + reinstalar** o binário (`npm run build && npm i -g .`) para a
254
- instalação local refletir; em produção, o push para `main` republica.
304
+ - **The MCP sends no key/secret.** The API is activated by a license on the host (GUI). Secrets (master secret /
305
+ UMAC / cert password) never go in `.mcp.json`.
306
+ - **Serial-only**: the backend is a serial worker; the MCP must never parallelize tool calls (Gd1).
307
+ - **stdout belongs to the MCP protocol** — logs go to **stderr**.
308
+ - The bundle is produced by [scripts/build.mjs](scripts/build.mjs) (esbuild, ESM, shebang + `createRequire`,
309
+ injected version). `dist/`, `node_modules/`, `*.tgz` are artifacts (gitignored).
310
+ - When adding/changing tools, **rebuild + reinstall** the binary (`npm run build && npm i -g .`) so the local
311
+ install reflects it; in production, pushing to `main` republishes.