@renanlido/tia-openness-mcp 1.0.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 +207 -0
  2. package/dist/index.js +32515 -0
  3. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # TIA Openness MCP server
2
+
3
+ Servidor **MCP (stdio)** que é um **cliente HTTP puro** sobre a API `serve` do TIA Openness
4
+ (veja [../docs/API-HTTP.md](../docs/API-HTTP.md)). 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**.
7
+
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).
11
+
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`**:
14
+
15
+ ```bash
16
+ npx -y @renanlido/tia-openness-mcp
17
+ ```
18
+
19
+ ## Tools
20
+
21
+ ### Diagnóstico + M1 read-only (classe R, auto-aprováveis)
22
+ | Tool | Endpoint | Descrição |
23
+ |---|---|---|
24
+ | `tia_ping` | `GET /health` + `GET /readiness` | Diagnostica conectividade e ATIVAÇÃO do `serve`. `{ base, reachable, activated, latencyMs, hint }`. Ideal p/ Mac→VM. |
25
+ | `tia_readiness` | `GET /readiness` | Prontidão do ambiente **no host do serve** (x64, grupo Windows, PublicAPI, registro, instâncias). |
26
+
27
+ **M1 (read-only, 1:1 com os GET da API):** `tia_instances`, `tia_status`, `tia_projects`, `tia_project_info`,
28
+ `tia_project_languages`, `tia_plcs`, `tia_devices`, `tia_device_tree`, `tia_device_connections`,
29
+ `tia_plc_blocks/types/tags/watchtables`, `tia_catalog`, `tia_connections_diagnose`, `tia_subnets`,
30
+ `tia_obj_roots/get/items/info/creationinfos/services`. Nenhuma muta estado.
31
+
32
+ ### M2 — ciclo de vida (classe M-off) + GATE de confiança (G1)
33
+ | Tool | Endpoint | Descrição |
34
+ |---|---|---|
35
+ | `tia_start` | `POST /tia/start` | Inicia/anexa o TIA **sem projeto** (`headless` opcional). |
36
+ | `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). |
37
+ | `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. |
38
+ | `tia_disconnect` | `POST /disconnect` | Desconecta (Gd3: use `tia_project_close` p/ liberar o lock). |
39
+ | `tia_project_create` | `POST /project/create` | Cria projeto (Gd4: diretório vazio → senão 409). |
40
+ | `tia_project_save` / `tia_project_saveas` / `tia_project_close` / `tia_project_archive` | `POST /project/*` | Ciclo de vida do projeto. |
41
+
42
+ **GATE de confiança (G1):** na 1ª conexão por processo `serve`, o TIA exige confirmação manual de um diálogo
43
+ de segurança NO HOST. O fluxo proativo é **`tia_start` → `tia_show_ui` → (humano confirma) → `tia_connect`**.
44
+ Se o `tia_connect` esbarra no diálogo, ele NÃO faz retry-storm: devolve `{ status:"requires_human_action",
45
+ gate:"trust_dialog", youMustDo, resumeToken }`. Após o humano confirmar, re-chame `tia_connect` com o `resumeToken`.
46
+
47
+ ### M3 — criação/autoria offline (classe M-off) + guards
48
+ Superfície de **criação compliant-by-construction** (~28 tools), todas com `dryRun` (Gd11) e validação por schema (Gd13):
49
+ - **Hardware/rede:** `tia_device_create`/`plug`/`unplug`, `tia_device_group_create`, `tia_subnet_create`, `tia_network_connect`/`address`/`disconnect`.
50
+ - **Tags/constantes:** `tia_tagtable_create`, `tia_tag_create`/`delete`, `tia_constant_create`.
51
+ - **Blocos/tipos:** `tia_fb_create`, `tia_instancedb_create`, `tia_block_delete`/`set_attribute`, `tia_block_group_create`, `tia_type_group_create`/`type_delete`.
52
+ - **Código:** `tia_source_put` (SCL textual — Gd6), `tia_import_xml`/`tia_types_import` (SimaticML gráfico — Gd6/Gd7), `tia_export_xml`.
53
+ - **Watch tables:** `tia_watchtable_create`/`delete`.
54
+ - **Camada genérica de escrita:** `tia_obj_set_attributes`/`invoke`/`create`.
55
+
56
+ **Guards (client-side, antes de tocar o Openness):** **Gd5** — `typeIdentifier` deve usar `/V<fw>` (barra), nunca `:V`
57
+ (rejeitado com `kind:"guard_violation"`; resolva o valor via `tia_catalog`). **Gd11** — `dryRun:true` valida os guards
58
+ e retorna o **plano por nome SEM mutar** (`status:"plan_ok"`). **Gd6** (advisório) — linguagem textual (SCL) via
59
+ `tia_source_put`, gráfica (LAD/FBD/GRAPH) via `tia_import_xml`; UDTs antes dos blocos. **Gd2** (advisório) — idempotência
60
+ por nome (cheque a existência com as tools read-only do M1 antes de criar; o Openness não tem rollback).
61
+
62
+ ### M4 — verificação + deploy GATED (hardware)
63
+ | Tool | Endpoint | Gate/classe |
64
+ |---|---|---|
65
+ | `tia_compile` | `POST /plcs/{plc}/compile` | M-off — **o verificador**. Achata a árvore de mensagens + `verification:{compiled,errors,warnings}` + `nextHints` (Gd8: só ERRO bloqueia). |
66
+ | `tia_connection_create` | `POST /connections` | M-off — conexão S7 (G2: exige ≥2 CPUs consistentes; falha anexa diagnóstico). |
67
+ | `tia_download` | `POST /plcs/{plc}/download` | **GATE G3** — não baixa sem aprovação humana vinculada ao `planHash`. |
68
+ | `tia_online` / `tia_offline` / `tia_online_state` | `POST /plcs/{plc}/online`·`/offline`·`GET …/state` | **G4** (online) / R (state). |
69
+ | `tia_accessible_devices` | `POST /plcs/{plc}/online/accessibledevices` | **GATE G7** (net scan). |
70
+ | `tia_mastersecret_set` / `tia_mastersecret_reset` | `…/online/mastersecret` | **GATE G6** (segredo vem do humano, nunca da IA). |
71
+
72
+ **GATE de download (G3):** `tia_download` NUNCA baixa sem aprovação. Fluxo: `tia_compile` (precondição Gd9: `errors=0`) →
73
+ `tia_download dryRun:true` (devolve `plan` + `planHash`, sem baixar) → um humano aprova → `tia_download approved:true`
74
+ com o **mesmo `planHash`**. Hash errado ou `approved` ausente → envelope `requires_human_action / download_approval`.
75
+ **Limitação honesta:** o `serve` ainda **auto-confirma** StopAll/Overwrite (backlog C# **P0.5**); até isso ser
76
+ 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).
77
+
78
+ ### M5 — ingestão de project-spec (orquestra M1–M4)
79
+ Aceita o **project-spec** estruturado ([../docs/TIA-BEST-PRACTICES.md](../docs/TIA-BEST-PRACTICES.md) §13.2; JSON Schema em [../.claude-plugin/schemas/project-spec.schema.json](../.claude-plugin/schemas/project-spec.schema.json)):
80
+ - **`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`).
81
+ - **`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
+ - **`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
+
84
+ > **MCP M0–M5 COMPLETO** (73 tools). O que falta é **runtime** (validar contra um TIA real — precisa reload + serve + hardware nos gates) e o **C# P0.5** (re-externalizar o auto-confirm do download p/ o G3 expor o Load-Preview de verdade). Detalhe em [../docs/ROADMAP.md](../docs/ROADMAP.md) §3.
85
+
86
+ ## Configuração (env)
87
+
88
+ | Var | Default | Uso |
89
+ |---|---|---|
90
+ | `TIA_API_BASE` | `http://localhost:5000` | Base URL da API `serve`. |
91
+ | `TIA_API_TIMEOUT_MS` | `15000` | Timeout por request (falha rápido se o host/VM não responde). |
92
+
93
+ > **O MCP não envia chave.** A API é **ativada por uma licença** configurada na GUI (aba "Licença") — quem
94
+ > autoriza o servidor é a licença do host, não o cliente. O MCP é um cliente HTTP normal: só aponta a base.
95
+
96
+ ## Cenário Mac → VM Windows (apontar o servidor)
97
+
98
+ 1. **Na VM Windows**, ative o servidor com uma licença válida (aba "Licença" da GUI, ou `--license`) e
99
+ suba aceitando conexões externas (a API **recusa bind não-loopback se NÃO estiver ativada**):
100
+ ```powershell
101
+ .\bin\Debug\net48\TIAOpenness.exe serve 5000 --host 0.0.0.0 --license TIA1.xxxxx.yyyyy
102
+ ```
103
+ Libere a porta no firewall do Windows e garanta rede acessível (bridged ou NAT com port-forward).
104
+ Controle de **acesso** de quem chama = firewall/VPN (a licença ativa o servidor, não autentica clientes).
105
+ 2. **No Mac**, aponte o MCP para a VM:
106
+ ```bash
107
+ export TIA_API_BASE="http://<ip-da-vm>:5000"
108
+ ```
109
+ 3. **Diagnostique** com o tool `tia_ping`: `reachable:false` → IP/porta/firewall/VM; `activated:false` →
110
+ licença ausente/inválida na VM; ambos `true` → pronto.
111
+
112
+ ## Build / bundle
113
+
114
+ ```bash
115
+ # garanta o Node (no Windows com nvm-windows: $env:Path = "C:\nvm4w\nodejs;" + $env:Path)
116
+ cd mcp
117
+ npm install
118
+ npm run build # typecheck + esbuild -> dist/index.js (self-contained)
119
+ ```
120
+
121
+ ## Instalação
122
+
123
+ ### A) Do npm público (uso normal, Mac/Windows/Linux)
124
+
125
+ Pacote **público** — **sem auth, sem `.npmrc`**. Use direto via `npx` ou instale o binário global:
126
+
127
+ ```bash
128
+ npx -y @renanlido/tia-openness-mcp # roda sem instalar (recomendado p/ .mcp.json)
129
+ # ou instale o binário `tia-openness-mcp` no PATH:
130
+ npm install -g @renanlido/tia-openness-mcp
131
+ ```
132
+
133
+ ### B) Local, sem publicar (dev nesta máquina)
134
+
135
+ ```bash
136
+ cd mcp
137
+ npm install -g . # o prepack compila e instala o binário
138
+ # ou: npm pack && npm install -g ./renanlido-tia-openness-mcp-*.tgz
139
+ ```
140
+
141
+ ## Publicação (npm público + semantic-release)
142
+
143
+ Versionamento **automático por commits convencionais** (`feat` → minor, `fix` → patch, `BREAKING CHANGE`
144
+ → major). O workflow [../.github/workflows/release-mcp.yml](../.github/workflows/release-mcp.yml) dispara
145
+ no push para `main` **quando algo em `mcp/` muda** (e `semantic-release-monorepo` filtra os commits por
146
+ esse path), builda e publica no **npm público** (`registry.npmjs.org`, `publishConfig.access: public`).
147
+
148
+ Usa **dois tokens**: `NPM_TOKEN` (secret a configurar — token *Automation/Publish* do npmjs.org) para o
149
+ **publish** via `@semantic-release/npm`, e o `GITHUB_TOKEN` nativo do Actions para criar a release/tag e
150
+ commitar o `CHANGELOG`.
151
+
152
+ **Para ligar (passos com credencial, uma vez):**
153
+ 1. Crie o repo no GitHub (`renanlido/TIAOpenness`) — ou ajuste `repository.url`/scope no `package.json` e
154
+ o workflow se o owner for outro.
155
+ 2. **Configure o secret `NPM_TOKEN`** no repositório (Settings → Secrets and variables → Actions): gere um
156
+ token de *Automation* em npmjs.org com permissão de publish no pacote `@renanlido/tia-openness-mcp`.
157
+ 3. `git add . && git commit -m "feat: initial commit"` → `git branch -M main` →
158
+ `git remote add origin <url>` → `git push -u origin main`.
159
+ 4. A partir daí, **commits convencionais** na `main` disparam o release automaticamente.
160
+
161
+ > Config validada localmente: versões dedupadas em `semantic-release@24`; o dry-run carregou config/plugins
162
+ > e só parou por o repo remoto ainda não existir.
163
+
164
+ ## Registro no Claude Code (escopo de projeto)
165
+
166
+ O [../.mcp.json](../.mcp.json) na raiz já registra o servidor `tia` apontando para o binário instalado:
167
+
168
+ ```jsonc
169
+ {
170
+ "mcpServers": {
171
+ "tia": {
172
+ "command": "tia-openness-mcp", // binário do pacote (PATH); requer instalação (A ou B)
173
+ "args": [],
174
+ "env": {
175
+ "TIA_API_BASE": "${TIA_API_BASE:-http://localhost:5000}" // IP:porta do serve (use o IP da VM se remoto)
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ **Envs opcionais** (default embutido no MCP — só defina para sobrescrever): `TIA_API_TIMEOUT_MS` (15000),
183
+ `TIA_SLOW_TIMEOUT_MS` (120000), `TIA_CONNECT_TIMEOUT_MS` (30000). Ex.: `--env TIA_SLOW_TIMEOUT_MS=180000`
184
+ no `claude mcp add`, ou a chave correspondente no `env` do `.mcp.json`.
185
+
186
+ Como é servidor de **escopo de projeto**, o Claude Code pede **aprovação** na 1ª vez: `claude mcp list`
187
+ (deve listar `tia`), rode `claude` no projeto e **aprove**, **recarregue** a sessão, e peça para chamar
188
+ `tia_ping` / `tia_readiness`.
189
+
190
+ ## Smoke test
191
+
192
+ ```bash
193
+ npm run smoke # alvo = dist/index.js local
194
+ # apontar para o binário global:
195
+ MCP_COMMAND=tia-openness-mcp MCP_ARGS='[]' npm run smoke # (Windows: use o caminho do .cmd)
196
+ ```
197
+
198
+ ## Notas
199
+
200
+ - **O MCP não envia chave/segredo.** A API é ativada por licença no host (GUI). Segredos (master secret /
201
+ UMAC / senha de cert) nunca vão no `.mcp.json`.
202
+ - **Serial-only**: o backend é um worker serial; o MCP nunca deve paralelizar tool calls (Gd1).
203
+ - **stdout é do protocolo MCP** — logs vão para **stderr**.
204
+ - O bundle é gerado por [scripts/build.mjs](scripts/build.mjs) (esbuild, ESM, shebang + `createRequire`,
205
+ versão injetada). `dist/`, `node_modules/`, `*.tgz` são artefatos (gitignored).
206
+ - Ao adicionar/alterar tools, **rebuild + reinstalar** o binário (`npm run build && npm i -g .`) para a
207
+ instalação local refletir; em produção, o push para `main` republica.