lasal-mcp 0.1.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/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/core/envelope.js +8 -0
- package/dist/core/errors.js +12 -0
- package/dist/core/http.js +19 -0
- package/dist/core/process.js +30 -0
- package/dist/core/response.js +37 -0
- package/dist/core/scratch.js +6 -0
- package/dist/core/staticServer.js +88 -0
- package/dist/server.js +230 -0
- package/dist/state.js +57 -0
- package/dist/tools/applyProjectChanges.js +371 -0
- package/dist/tools/deployAll.js +320 -0
- package/dist/tools/hmiBrowser.js +224 -0
- package/dist/tools/hmiRuntime.js +273 -0
- package/dist/tools/inspectProject.js +162 -0
- package/dist/tools/inspectVisuProject.js +474 -0
- package/dist/tools/larsRuntime.js +536 -0
- package/dist/tools/lasalApps.js +111 -0
- package/dist/tools/plcControl.js +530 -0
- package/dist/tools/plcDiagnostics.js +172 -0
- package/dist/tools/readClassSource.js +147 -0
- package/dist/tools/selectProject.js +47 -0
- package/dist/tools/setTargetIp.js +114 -0
- package/dist/tools/status.js +146 -0
- package/dist/tools/visuControl.js +447 -0
- package/dist/tools/visuDashboard.js +571 -0
- package/dist/utils/batchScript.js +257 -0
- package/dist/utils/config.js +34 -0
- package/dist/utils/editTransaction.js +39 -0
- package/dist/utils/engine.js +163 -0
- package/dist/utils/lars.js +471 -0
- package/dist/utils/lasalXml.js +758 -0
- package/dist/utils/preflight.js +194 -0
- package/dist/utils/projectScanner.js +212 -0
- package/dist/utils/resolvePaths.js +46 -0
- package/dist/utils/respond.js +14 -0
- package/dist/utils/scriptRunner.js +161 -0
- package/dist/utils/visuDashboardIO.js +198 -0
- package/dist/utils/visuPropertyEncoding.js +174 -0
- package/dist/utils/visuScript.js +262 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Sven Jansen and Lasal-MCP contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Sigmatek LASAL MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Svel26/Lasal-MCP/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/lasal-mcp)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](CONTRIBUTING.md)
|
|
7
|
+
|
|
8
|
+
A Model Context Protocol (MCP) server for automating the **Sigmatek LASAL** software suite. It gives AI coding assistants (Claude, Gemini, Cursor, etc.) the ability to compile, deploy to hardware, control PLCs, read/write live values, run HMI simulations, and automate a headless browser — while the agent edits project files directly.
|
|
9
|
+
|
|
10
|
+
> **Warning:** This is NOT an official Sigmatek product. This project is in active development — bugs and unpredictable behavior are likely. **Do not use on production projects** without backups or version control.
|
|
11
|
+
|
|
12
|
+
## Design Philosophy
|
|
13
|
+
|
|
14
|
+
The MCP only exposes tools for operations that **require an external engine or hardware** — compiling, deploying, PLC control, browser automation. For everything else (reading/editing `.st`, `.lcp`, `.lcn`, `.lss`, `.lvp`, dashboard JSON), the AI agent works with the files directly using its native file tools. This keeps the tool set small, reliable, and focused.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Build & Deploy**: Compile CLASS 2 projects, download to PLC, full deploy pipelines.
|
|
19
|
+
- **PLC Control**: Start/stop PLC runtime, read/write live channel values, query state.
|
|
20
|
+
- **Local Simulation (LARS)**: Run one LASAL program per LARS instance — one workspace per station (PLC + HMI) so both run simultaneously on separate ports. Point stations at the local runtime with a single call, compile/download/start/read values exactly like a real PLC, and browse the HMI via DataService. No hardware needed.
|
|
21
|
+
- **CLASS 2 Batch Engine**: Create/delete networks, add/remove objects, manage connections, configure tasks — operations that require the CLASS 2 scripting engine.
|
|
22
|
+
- **VISUDesigner Engine**: Sync datapoints, manage text lists/schemes/media, publish, download to HMI.
|
|
23
|
+
- **HMI Simulation**: Local web runtime via LasalVISUDataService with headless Edge browser automation for visual verification.
|
|
24
|
+
- **PLC Diagnostics**: Tracing, file transfer, static code analysis.
|
|
25
|
+
- **Project Guide**: Built-in resource (`lasal://guide`) documenting all LASAL file formats so the agent can edit project files directly.
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
- **Windows OS** (Sigmatek LASAL suite runs exclusively on Windows).
|
|
30
|
+
- **Node.js** v18 or higher.
|
|
31
|
+
- **Sigmatek LASAL Suite**:
|
|
32
|
+
- **LASAL CLASS 2** (PLC engineering).
|
|
33
|
+
- **VISUDesigner** (HMI design).
|
|
34
|
+
|
|
35
|
+
## Quick Start (with `npx`)
|
|
36
|
+
|
|
37
|
+
No repository clone or build required — configure your favorite MCP client to run directly via `npx`:
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
Add to `%APPDATA%\Claude\claude_desktop_config.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"lasal-mcp": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "lasal-mcp"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Cursor
|
|
55
|
+
|
|
56
|
+
Add to your project's `.cursor/mcp.json` (or global Cursor MCP settings):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"lasal-mcp": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "lasal-mcp"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Claude Code
|
|
70
|
+
|
|
71
|
+
Run:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
claude mcp add lasal-mcp npx -y lasal-mcp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Local Installation & Setup (for Developers)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/Svel26/Lasal-MCP.git
|
|
83
|
+
cd Lasal-MCP
|
|
84
|
+
npm install
|
|
85
|
+
npm run build
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When running from a local clone, point your MCP client to:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"lasal-mcp": {
|
|
94
|
+
"command": "node",
|
|
95
|
+
"args": ["C:/path/to/Lasal-MCP/dist/server.js"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
### Environment Variables
|
|
104
|
+
|
|
105
|
+
| Variable | Default | Purpose |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `LASAL_CLASS2_EXE` | `C:\Program Files (x86)\Sigmatek\Lasal\Class2\Bin\Lasal2.exe` | CLASS 2 IDE path |
|
|
108
|
+
| `LASAL_VISUDESIGNER_EXE` | `C:\Program Files\Sigmatek\Lasal\VISUDesigner\VISUDesigner.exe` | VISUDesigner path |
|
|
109
|
+
| `LASAL_DATASERVICE_EXE` | auto-discovered (latest version) | DataService path |
|
|
110
|
+
| `LASAL_EDGE_EXE` | auto-discovered | Edge browser path |
|
|
111
|
+
| `LASAL_MCP_TIMEOUT_COMPILE` | `600000` | Compile timeout (ms) |
|
|
112
|
+
| `LASAL_MCP_TIMEOUT_DOWNLOAD` | `600000` | Download timeout (ms) |
|
|
113
|
+
| `LASAL_MCP_TIMEOUT_VISU` | `300000` | Visu operation timeout (ms) |
|
|
114
|
+
| `LASAL_MCP_TIMEOUT_SCRIPT` | `120000` | Script execution timeout (ms) |
|
|
115
|
+
| `LASAL_LARS_EXE` | `C:\Program Files (x86)\Sigmatek\Lars\Lars.exe` | LARS runtime path |
|
|
116
|
+
| `LASAL_LARS_CONFIG` | `%APPDATA%\lasalos2.xml` | LARS workspace config file |
|
|
117
|
+
| `LASAL_MCP_HMI_DIR` | `C:\lslvisu` | Local HMI runtime directory |
|
|
118
|
+
| `LASAL_MCP_SCRATCH_MAX_AGE_H` | `24` | Hours before temp files are cleaned |
|
|
119
|
+
|
|
120
|
+
## Available Tools
|
|
121
|
+
|
|
122
|
+
### Engine & Hardware Tools (MCP)
|
|
123
|
+
|
|
124
|
+
| Tool | Description |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `select_project` | Set the active project root directory. |
|
|
127
|
+
| `lasal_status` | Check project, stations, engines, processes, HMI health. |
|
|
128
|
+
| `manage_class2` | Open or close the CLASS 2 IDE. |
|
|
129
|
+
| `manage_visudesigner` | Open or close VISUDesigner. |
|
|
130
|
+
| `build_project` | Compile or download to PLC. |
|
|
131
|
+
| `control_plc` | Start, stop, or query PLC runtime state. |
|
|
132
|
+
| `plc_values` | Read/write live channel values on a running PLC. |
|
|
133
|
+
| `lars_runtime` | Local LARS simulation: list/setup workspaces per station, start/stop, point stations at LARS (`set_station_target`), revert (`restore`), switch ARM projects to the PC target (`target_pc`). |
|
|
134
|
+
| `apply_project_changes` | CLASS 2 batch engine operations (networks, objects, connections, tasks). |
|
|
135
|
+
| `visu_project` | VISUDesigner engine operations (text lists, schemes, media, publish, download). |
|
|
136
|
+
| `hmi_runtime` | Start/stop local HMI web simulation (DataService). |
|
|
137
|
+
| `hmi_browser` | Headless Edge browser for HMI testing and screenshots. |
|
|
138
|
+
| `plc_diagnostics` | Tracing, file transfer, static code analysis. |
|
|
139
|
+
| `deploy_all` | Full pipeline: compile → download → start PLC → sync visu → start HMI. |
|
|
140
|
+
|
|
141
|
+
### Direct File Editing (no MCP needed)
|
|
142
|
+
|
|
143
|
+
The agent edits these files directly with its native file tools:
|
|
144
|
+
|
|
145
|
+
| File | Format | Encoding | What it contains |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `.lsm` | XML | latin1 | Solution — lists all stations |
|
|
148
|
+
| `.lss` | XML | latin1 | Station settings — target IP, project references |
|
|
149
|
+
| `.lcp` | XML | latin1 | CLASS 2 project manifest — class and network file paths |
|
|
150
|
+
| `.st` | XML + ST | latin1 | Class source — XML header + Structured Text body |
|
|
151
|
+
| `.lcn` | XML | latin1 | Network definitions — objects, connections, init values |
|
|
152
|
+
| `.lvp` | Mixed | utf-8 | VISUDesigner project manifest |
|
|
153
|
+
| Dashboard JSON | JSON | utf-8 | HMI dashboards, windows, controls, property bindings |
|
|
154
|
+
|
|
155
|
+
See the built-in `lasal://guide` resource for detailed file format documentation.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run dev # Watch mode
|
|
161
|
+
npm test # Run tests (Vitest)
|
|
162
|
+
npm run test:watch # Tests in watch mode
|
|
163
|
+
npm run lint # ESLint
|
|
164
|
+
npm run format # Prettier
|
|
165
|
+
npm run inspector # MCP Inspector for interactive debugging
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Simulating without hardware (LARS)
|
|
169
|
+
|
|
170
|
+
LARS (LASAL Runtime System) runs LASAL programs on a normal PC. Since each LARS
|
|
171
|
+
instance runs one program, the MCP creates one workspace per station:
|
|
172
|
+
|
|
173
|
+
1. `lars_runtime setup` — creates workspaces for every station in the selected solution (distinct ports per instance).
|
|
174
|
+
2. `lars_runtime start <station>` — launches the LARS window for that station.
|
|
175
|
+
3. `lars_runtime set_station_target <station>` — points the station's `.lss` at the LARS instance.
|
|
176
|
+
4. Now `build_project`, `control_plc`, and `plc_values` work unchanged against the local runtime.
|
|
177
|
+
5. `lars_runtime restore <station>` — reverts the `.lss` to the real hardware target.
|
|
178
|
+
|
|
179
|
+
Notes:
|
|
180
|
+
- Projects compiled for `Processor="ARM"` (most machine PLCs) need
|
|
181
|
+
`lars_runtime target_pc` first — LARS is an x86 runtime and rejects ARM images.
|
|
182
|
+
- HMI panel / Local stations are already PC-targeted and work directly.
|
|
183
|
+
- Downloads to LARS automatically include the PC loader (`addLoaderAnyway`).
|
|
184
|
+
- `hmi_runtime` remaps published stations.json entries to running LARS instances,
|
|
185
|
+
so the web HMI talks to the local simulation.
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
Contributions are welcome! Please check out [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on code standards, adding tools, and submitting pull requests.
|
|
190
|
+
|
|
191
|
+
## Releasing & CI/CD
|
|
192
|
+
|
|
193
|
+
Automated releases and publishing to npm are powered by GitHub Actions. Releases can be triggered manually via workflow dispatch with automated version bumping or by pushing version tags (`v*.*.*`).
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
198
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const ERROR_CODES = {
|
|
2
|
+
PLC_UNREACHABLE: "PLC_UNREACHABLE",
|
|
3
|
+
PROJECT_LOCKED: "PROJECT_LOCKED",
|
|
4
|
+
COMPILE_FAILED: "COMPILE_FAILED",
|
|
5
|
+
CHANNEL_NOT_FOUND: "CHANNEL_NOT_FOUND",
|
|
6
|
+
PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND",
|
|
7
|
+
TRANSIENT: "TRANSIENT",
|
|
8
|
+
};
|
|
9
|
+
export function isTransientError(errors) {
|
|
10
|
+
const transientPatterns = [/connect/i, /timeout/i, /offline/i, /socket/i, /1954/i];
|
|
11
|
+
return errors.some((err) => transientPatterns.some((p) => p.test(err)));
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
export function checkHttpHealth(url, timeoutMs = 1000, anyResponse = false) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
try {
|
|
5
|
+
const req = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
6
|
+
resolve(anyResponse || res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301);
|
|
7
|
+
res.resume();
|
|
8
|
+
});
|
|
9
|
+
req.on("error", () => resolve(false));
|
|
10
|
+
req.on("timeout", () => {
|
|
11
|
+
req.destroy();
|
|
12
|
+
resolve(false);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
resolve(false);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
export function isPidRunning(pid) {
|
|
3
|
+
try {
|
|
4
|
+
execSync(`tasklist /FI "PID eq ${pid}" | findstr ${pid}`, { stdio: "pipe" });
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function getPortForPid(pid) {
|
|
12
|
+
try {
|
|
13
|
+
const out = execSync(`powershell -Command "(Get-NetTCPConnection -OwningProcess ${pid} -State Listen).Port"`, {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
}).trim();
|
|
16
|
+
const ports = out
|
|
17
|
+
.split(/[\r\n]+/)
|
|
18
|
+
.map((p) => parseInt(p.trim()))
|
|
19
|
+
.filter((p) => !isNaN(p) && p > 0);
|
|
20
|
+
if (ports.length > 0) {
|
|
21
|
+
ports.sort((a, b) => a - b);
|
|
22
|
+
const port = ports[0];
|
|
23
|
+
if (port !== undefined) {
|
|
24
|
+
return port;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
return 9980;
|
|
30
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { respond } from "../utils/respond.js";
|
|
2
|
+
export function batchToStepResult(br) {
|
|
3
|
+
return {
|
|
4
|
+
ok: br.ok,
|
|
5
|
+
durationMs: br.durationMs,
|
|
6
|
+
...(br.errors.length ? { errors: br.errors } : {}),
|
|
7
|
+
...(br.warnings.length ? { warnings: br.warnings } : {}),
|
|
8
|
+
...(br.logTail.length ? { logTail: br.logTail } : {}),
|
|
9
|
+
logPath: br.logPath,
|
|
10
|
+
hints: br.hints,
|
|
11
|
+
timedOut: br.timedOut,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function visuToStepResult(vr) {
|
|
15
|
+
return {
|
|
16
|
+
ok: vr.ok,
|
|
17
|
+
durationMs: vr.durationMs,
|
|
18
|
+
...(vr.errors.length ? { errors: vr.errors } : {}),
|
|
19
|
+
...(vr.warnings.length ? { warnings: vr.warnings } : {}),
|
|
20
|
+
logPath: vr.logPath,
|
|
21
|
+
hints: vr.hints,
|
|
22
|
+
timedOut: vr.timedOut,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function batchResultToResponse(br, extra) {
|
|
26
|
+
const body = {
|
|
27
|
+
ok: br.ok,
|
|
28
|
+
durationMs: br.durationMs,
|
|
29
|
+
...(br.errors.length ? { errors: br.errors } : {}),
|
|
30
|
+
...(br.warnings.length ? { warnings: br.warnings } : {}),
|
|
31
|
+
...(br.logTail.length ? { logTail: br.logTail } : {}),
|
|
32
|
+
logPath: br.logPath,
|
|
33
|
+
...(br.hints?.length ? { hints: br.hints } : {}),
|
|
34
|
+
...extra,
|
|
35
|
+
};
|
|
36
|
+
return respond(body);
|
|
37
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
import { createReadStream, existsSync, statSync } from "fs";
|
|
3
|
+
import { join, normalize, extname } from "path";
|
|
4
|
+
const MIME = {
|
|
5
|
+
".html": "text/html; charset=utf-8",
|
|
6
|
+
".js": "text/javascript; charset=utf-8",
|
|
7
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
8
|
+
".css": "text/css; charset=utf-8",
|
|
9
|
+
".json": "application/json; charset=utf-8",
|
|
10
|
+
".png": "image/png",
|
|
11
|
+
".jpg": "image/jpeg",
|
|
12
|
+
".jpeg": "image/jpeg",
|
|
13
|
+
".gif": "image/gif",
|
|
14
|
+
".svg": "image/svg+xml",
|
|
15
|
+
".ico": "image/x-icon",
|
|
16
|
+
".woff": "font/woff",
|
|
17
|
+
".woff2": "font/woff2",
|
|
18
|
+
".ttf": "font/ttf",
|
|
19
|
+
".otf": "font/otf",
|
|
20
|
+
".wasm": "application/wasm",
|
|
21
|
+
".mp4": "video/mp4",
|
|
22
|
+
".mp3": "audio/mpeg",
|
|
23
|
+
".pdf": "application/pdf",
|
|
24
|
+
".lang": "application/octet-stream",
|
|
25
|
+
};
|
|
26
|
+
let server = null;
|
|
27
|
+
let servedRoot = null;
|
|
28
|
+
export function getStaticServerPort() {
|
|
29
|
+
const addr = server?.address();
|
|
30
|
+
return addr && typeof addr === "object" ? addr.port : null;
|
|
31
|
+
}
|
|
32
|
+
/** Serve `rootDir` on 127.0.0.1. Reuses the running server if it already serves the same root. */
|
|
33
|
+
export function startStaticServer(rootDir, preferredPort = 9982) {
|
|
34
|
+
if (server && servedRoot === rootDir) {
|
|
35
|
+
const port = getStaticServerPort();
|
|
36
|
+
if (port)
|
|
37
|
+
return Promise.resolve(port);
|
|
38
|
+
}
|
|
39
|
+
stopStaticServer();
|
|
40
|
+
server = http.createServer((req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
|
|
43
|
+
let filePath = normalize(join(rootDir, urlPath));
|
|
44
|
+
if (!filePath.startsWith(normalize(rootDir))) {
|
|
45
|
+
res.writeHead(403).end();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (existsSync(filePath) && statSync(filePath).isDirectory()) {
|
|
49
|
+
filePath = join(filePath, "index.html");
|
|
50
|
+
}
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
res.writeHead(404).end();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
res.writeHead(200, {
|
|
56
|
+
"Content-Type": MIME[extname(filePath).toLowerCase()] ?? "application/octet-stream",
|
|
57
|
+
"Cache-Control": "no-cache",
|
|
58
|
+
});
|
|
59
|
+
createReadStream(filePath).pipe(res);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
res.writeHead(500).end();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
servedRoot = rootDir;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
server.once("error", (err) => {
|
|
68
|
+
if (err.code === "EADDRINUSE") {
|
|
69
|
+
// Preferred port taken — fall back to an ephemeral port
|
|
70
|
+
server.listen(0, "127.0.0.1", () => resolve(getStaticServerPort()));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
reject(err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
server.listen(preferredPort, "127.0.0.1", () => resolve(getStaticServerPort()));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function stopStaticServer() {
|
|
80
|
+
if (server) {
|
|
81
|
+
try {
|
|
82
|
+
server.close();
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
85
|
+
server = null;
|
|
86
|
+
servedRoot = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { selectProjectSchema, selectProjectHandler } from "./tools/selectProject.js";
|
|
5
|
+
import { lasalStatusSchema, lasalStatusHandler } from "./tools/status.js";
|
|
6
|
+
import { manageVisuDesignerSchema, manageVisuDesignerHandler, manageClass2Schema, manageClass2Handler, } from "./tools/lasalApps.js";
|
|
7
|
+
import { deployAllSchema, deployAllHandler } from "./tools/deployAll.js";
|
|
8
|
+
import { applyProjectChangesSchema, applyProjectChangesHandler } from "./tools/applyProjectChanges.js";
|
|
9
|
+
import { buildProjectSchema, buildProjectHandler, controlPlcSchema, controlPlcHandler, plcValuesSchema, plcValuesHandler, } from "./tools/plcControl.js";
|
|
10
|
+
import { visuProjectSchema, visuProjectHandler } from "./tools/visuControl.js";
|
|
11
|
+
import { hmiRuntimeSchema, hmiRuntimeHandler } from "./tools/hmiRuntime.js";
|
|
12
|
+
import { hmiBrowserSchema, hmiBrowserHandler } from "./tools/hmiBrowser.js";
|
|
13
|
+
import { plcDiagnosticsSchema, plcDiagnosticsHandler } from "./tools/plcDiagnostics.js";
|
|
14
|
+
import { larsRuntimeSchema, larsRuntimeHandler } from "./tools/larsRuntime.js";
|
|
15
|
+
import { cleanupScratch } from "./utils/engine.js";
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: "lasal-mcp",
|
|
18
|
+
version: "0.1.0",
|
|
19
|
+
});
|
|
20
|
+
// ─── Project management ──────────────────────────────────────────────────────
|
|
21
|
+
server.tool("select_project", "Set the active LASAL project by directory path. Call first — all other tools default to this project.", selectProjectSchema, selectProjectHandler);
|
|
22
|
+
server.tool("lasal_status", "Check project selection, station discovery, PLC/HMI reachability, engine paths, running processes, and HMI runtime health. Call to orient or diagnose connection issues.", lasalStatusSchema, lasalStatusHandler);
|
|
23
|
+
server.tool("manage_class2", "Open or close the LASAL CLASS 2 IDE GUI. Close before running batch operations.", manageClass2Schema, manageClass2Handler);
|
|
24
|
+
server.tool("manage_visudesigner", "Open or close the VISUDesigner GUI. Close before running automated visu operations.", manageVisuDesignerSchema, manageVisuDesignerHandler);
|
|
25
|
+
// ─── Build, deploy, PLC control ──────────────────────────────────────────────
|
|
26
|
+
server.tool("build_project", "Compile the CLASS 2 project or download it to the PLC. Compilation kills CLASS 2 IDE. Download pings the PLC first.", buildProjectSchema, buildProjectHandler);
|
|
27
|
+
server.tool("control_plc", "Start, stop, or query PLC runtime state. Pings the target PLC before start/stop.", controlPlcSchema, controlPlcHandler);
|
|
28
|
+
server.tool("plc_values", "Read or write live channel values on a running PLC. Channels use 'ObjectName.ChannelName' format. Auto-coerces types based on ST declarations.", plcValuesSchema, plcValuesHandler);
|
|
29
|
+
server.tool("apply_project_changes", "Run CLASS 2 batch engine operations that cannot be done by editing files directly: create/delete/rename networks, add/remove/rename objects, create/delete connections, set init values, configure tasks, compile, download. Kills CLASS 2 IDE before running.", applyProjectChangesSchema, applyProjectChangesHandler);
|
|
30
|
+
server.tool("plc_diagnostics", "Run PLC diagnostics: trace recording, file upload/download/delete on PLC, or static code analysis.", plcDiagnosticsSchema, plcDiagnosticsHandler);
|
|
31
|
+
// ─── LARS local runtime simulation ───────────────────────────────────────────
|
|
32
|
+
server.tool("lars_runtime", "Manage local LARS (LASAL Runtime System) simulation instances. LARS runs one program per instance — " +
|
|
33
|
+
"use one workspace per station (PLC + HMI) so both run simultaneously on separate ports. " +
|
|
34
|
+
"Actions: list (auto-cleans stale workspaces), setup (create workspaces for all stations), start (auto-creates the workspace " +
|
|
35
|
+
"if the station is known but unconfigured), stop, remove, gc (lazy cleanup of unreferenced workspaces), " +
|
|
36
|
+
"set_station_target (point a station's .lss at its LARS instance), restore (revert .lss to the real target. " +
|
|
37
|
+
"After set_station_target, build_project/control_plc/plc_values/deploy_all operate on the LARS instance automatically.", larsRuntimeSchema, larsRuntimeHandler);
|
|
38
|
+
// ─── VISUDesigner engine operations ──────────────────────────────────────────
|
|
39
|
+
server.tool("visu_project", "Run VISUDesigner engine operations: update stations, publish, manage text lists/schemes/media/code modules, set datapoint properties, or download to HMI. These need the VISUDesigner engine — for direct dashboard JSON editing, edit the files in the project directly.", visuProjectSchema, visuProjectHandler);
|
|
40
|
+
// ─── Deploy pipeline ─────────────────────────────────────────────────────────
|
|
41
|
+
server.tool("deploy_all", "Full deploy pipeline: compile → download PLC → start PLC → verify state → update Visu stations → download Visu → start HMI runtime. Each step is optional via flags.", deployAllSchema, deployAllHandler);
|
|
42
|
+
// ─── HMI runtime & browser ──────────────────────────────────────────────────
|
|
43
|
+
server.tool("hmi_runtime", "Start, stop, or check the local HMI web simulation (LasalVISUDataService). Publishes the project, copies webroot, and spawns the DataService. Use hmi_browser to interact with it afterwards.", hmiRuntimeSchema, hmiRuntimeHandler);
|
|
44
|
+
server.tool("hmi_browser", "Automate a headless Edge browser to test the HMI. Actions: open (navigate), screenshot (capture viewport or element), console (read logs/errors), eval (run JS), click, type, wait, close. ALWAYS use this after deploy to visually verify the HMI works.", hmiBrowserSchema, hmiBrowserHandler);
|
|
45
|
+
// ─── Resource: LASAL file format guide ───────────────────────────────────────
|
|
46
|
+
server.resource("LASAL Project Guide", "lasal://guide", {
|
|
47
|
+
description: "Complete guide to LASAL file formats, file editing, HMI debugging, and the runtime JS API",
|
|
48
|
+
mimeType: "text/markdown",
|
|
49
|
+
}, async () => {
|
|
50
|
+
return {
|
|
51
|
+
contents: [
|
|
52
|
+
{
|
|
53
|
+
uri: "lasal://guide",
|
|
54
|
+
mimeType: "text/markdown",
|
|
55
|
+
text: LASAL_GUIDE,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
const LASAL_GUIDE = `# LASAL Project Guide
|
|
61
|
+
|
|
62
|
+
## File Format Reference
|
|
63
|
+
|
|
64
|
+
All LASAL project files use **ISO-8859-1 (latin1)** encoding unless otherwise noted.
|
|
65
|
+
|
|
66
|
+
### Solution file (.lsm)
|
|
67
|
+
XML file at the project root. Lists all stations in the project.
|
|
68
|
+
|
|
69
|
+
\`\`\`xml
|
|
70
|
+
<Solution>
|
|
71
|
+
<SlnStation Name="PLC">
|
|
72
|
+
<StationFile Path="PLC\\PLC.lss"/>
|
|
73
|
+
</SlnStation>
|
|
74
|
+
<SlnStation Name="HMI">
|
|
75
|
+
<StationFile Path="HMI\\HMI.lss"/>
|
|
76
|
+
</SlnStation>
|
|
77
|
+
</Solution>
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
### Station settings (.lss)
|
|
81
|
+
XML file per station. Contains connection settings and project file references.
|
|
82
|
+
|
|
83
|
+
Key elements:
|
|
84
|
+
- \`<TCPIP IP="10.195.0.50" PORT="1954" SSLTLS="0"/>\` — target IP for downloads
|
|
85
|
+
- \`<ClassProject Path="PLC.lcp"/>\` — link to the CLASS 2 project
|
|
86
|
+
- \`<VisualProject Path="HMI.lvp"/>\` — link to the VISUDesigner project
|
|
87
|
+
|
|
88
|
+
To change the target IP, surgically edit the \`IP\` attribute in the \`<TCPIP>\` element.
|
|
89
|
+
Do NOT rewrite the entire .lss — it contains other settings that must be preserved.
|
|
90
|
+
|
|
91
|
+
### CLASS 2 project (.lcp)
|
|
92
|
+
XML project manifest. Lists all class files and network files in the project.
|
|
93
|
+
|
|
94
|
+
\`\`\`xml
|
|
95
|
+
<ClassProject Version="...">
|
|
96
|
+
<Header>...</Header>
|
|
97
|
+
<ClassFiles>
|
|
98
|
+
<File Path="Motor.st"/>
|
|
99
|
+
<File Path="Sensor.st"/>
|
|
100
|
+
</ClassFiles>
|
|
101
|
+
<NetworkFiles>
|
|
102
|
+
<File Path="Main.lcn"/>
|
|
103
|
+
</NetworkFiles>
|
|
104
|
+
</ClassProject>
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
Use it to discover which .st and .lcn files belong to the project.
|
|
108
|
+
Paths are relative to the .lcp file's directory.
|
|
109
|
+
|
|
110
|
+
### Class source (.st)
|
|
111
|
+
Structured Text class files. Each .st file defines one class. The format has two parts:
|
|
112
|
+
|
|
113
|
+
1. **XML header block** (between \`(* BEGIN_CLASS ... END_CLASS *)\` comment markers):
|
|
114
|
+
Contains class metadata — servers, clients, methods, inheritance.
|
|
115
|
+
|
|
116
|
+
2. **ST body**: Variable declarations and method implementations in IEC 61131-3 Structured Text.
|
|
117
|
+
|
|
118
|
+
Example structure:
|
|
119
|
+
\`\`\`
|
|
120
|
+
(* BEGIN_CLASS
|
|
121
|
+
<ClassDef Name="Motor" SuperClass="UserDef0" ...>
|
|
122
|
+
<Servers>
|
|
123
|
+
<Server Name="s_Speed" ... />
|
|
124
|
+
</Servers>
|
|
125
|
+
<Clients>
|
|
126
|
+
<Client Name="c_Enable" ... />
|
|
127
|
+
</Clients>
|
|
128
|
+
<Methods>
|
|
129
|
+
<Method Name="CyWork" ... />
|
|
130
|
+
</Methods>
|
|
131
|
+
</ClassDef>
|
|
132
|
+
END_CLASS *)
|
|
133
|
+
|
|
134
|
+
//Variables:
|
|
135
|
+
s_Speed : SvrCh_DINT;
|
|
136
|
+
c_Enable : CltCh_BOOL;
|
|
137
|
+
localVar : DINT;
|
|
138
|
+
|
|
139
|
+
//Methods:
|
|
140
|
+
FUNCTION Motor::CyWork
|
|
141
|
+
IF c_Enable THEN
|
|
142
|
+
s_Speed := 100;
|
|
143
|
+
END_IF;
|
|
144
|
+
END_FUNCTION
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
**Server channels** (outputs): Prefixed \`s_\` by convention. Types like \`SvrCh_DINT\`, \`SvrCh_BOOL\`, \`SvrCh_REAL\`.
|
|
148
|
+
**Client channels** (inputs): Prefixed \`c_\` by convention. Types like \`CltCh_DINT\`, \`CltChCmd_General2\`.
|
|
149
|
+
|
|
150
|
+
When adding a server/client:
|
|
151
|
+
1. Add the XML element in the header block (\`<Server>\` or \`<Client>\`)
|
|
152
|
+
2. Add the variable declaration in the \`//Variables:\` section
|
|
153
|
+
3. Both must match in name
|
|
154
|
+
|
|
155
|
+
When editing .st files, always use **latin1** encoding. Non-latin1 characters will corrupt the file.
|
|
156
|
+
|
|
157
|
+
### Network files (.lcn)
|
|
158
|
+
XML files defining object networks — instances of classes and their connections.
|
|
159
|
+
|
|
160
|
+
\`\`\`xml
|
|
161
|
+
<Network Name="Main">
|
|
162
|
+
<Objects>
|
|
163
|
+
<Object Name="Motor1" ClassName="Motor" ...>
|
|
164
|
+
<InitValues>
|
|
165
|
+
<InitValue Server="s_Speed" Value="50"/>
|
|
166
|
+
</InitValues>
|
|
167
|
+
</Object>
|
|
168
|
+
</Objects>
|
|
169
|
+
<Connections>
|
|
170
|
+
<Connection FromObject="Sensor1" FromClient="c_MotorSpeed" ToObject="Motor1" ToServer="s_Speed"/>
|
|
171
|
+
</Connections>
|
|
172
|
+
</Network>
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
Network operations (create/delete networks, add/remove objects, create connections) **require the CLASS 2 batch engine** — use \`apply_project_changes\` for these.
|
|
176
|
+
Init values and connections reference object instances, not class definitions.
|
|
177
|
+
|
|
178
|
+
### Class header (.h)
|
|
179
|
+
Auto-generated companion to .st files. Contains C-like declarations. Usually read-only — changes are made to .st files.
|
|
180
|
+
|
|
181
|
+
### VISUDesigner project (.lvp)
|
|
182
|
+
Binary/text project manifest for the HMI side. References dashboard JSON files, datapoint configurations, text lists, schemes, and media.
|
|
183
|
+
|
|
184
|
+
### Dashboard JSON files
|
|
185
|
+
Located in subdirectories of the .lvp project folder. These are UTF-8 JSON files defining HMI dashboard layouts with controls, properties, and data bindings.
|
|
186
|
+
|
|
187
|
+
Dashboard files can be edited directly — they are standard JSON. Each element has:
|
|
188
|
+
- \`controlId\`: the control type (e.g. "sigTextField", "sigButton")
|
|
189
|
+
- \`name\`: unique element name within the dashboard
|
|
190
|
+
- Properties bound to datapoints, constants, schemes, or text references
|
|
191
|
+
|
|
192
|
+
## Recommended Workflow
|
|
193
|
+
|
|
194
|
+
1. **Orient**: Call \`lasal_status\` to check project state and connectivity.
|
|
195
|
+
2. **Select**: Call \`select_project\` with your project path.
|
|
196
|
+
3. **Edit code**: Read and edit .st files directly using file tools. Use latin1 encoding.
|
|
197
|
+
4. **Structural changes**: Use \`apply_project_changes\` for network/object/connection operations that need the CLASS 2 engine.
|
|
198
|
+
5. **Build & Deploy**: Call \`build_project\` to compile, then \`deploy_all\` to push everything.
|
|
199
|
+
6. **Verify HMI**: Call \`hmi_runtime\` to start simulation, then \`hmi_browser\` to open, screenshot, and interact.
|
|
200
|
+
7. **Live debug**: Use \`plc_values\` to read/write PLC channels in real time.
|
|
201
|
+
|
|
202
|
+
## HMI Runtime JavaScript API
|
|
203
|
+
|
|
204
|
+
Within the HMI web environment (via \`hmi_browser\` eval):
|
|
205
|
+
\`\`\`javascript
|
|
206
|
+
// Read a datapoint
|
|
207
|
+
sig.datapoint.get('Motor1.s_Speed')
|
|
208
|
+
|
|
209
|
+
// Write a datapoint
|
|
210
|
+
sig.datapoint.set('Motor1.s_Speed', 150)
|
|
211
|
+
|
|
212
|
+
// Get active alarms
|
|
213
|
+
sig.alarm.getActiveAlarms()
|
|
214
|
+
|
|
215
|
+
// Get current view
|
|
216
|
+
document.querySelector('sig-app').activeView
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
## Important Notes
|
|
220
|
+
|
|
221
|
+
- All .st/.lcp/.lcn/.lss files are **latin1** encoded — always read/write with latin1
|
|
222
|
+
- The CLASS 2 IDE must be **closed** before batch operations or .st file writes
|
|
223
|
+
- VISUDesigner must be **closed** before visu engine operations
|
|
224
|
+
- Network operations (create network, add object, create connection) **require the batch engine** — you cannot do these by editing files alone
|
|
225
|
+
- Dashboard JSON files **can** be edited directly — no engine needed
|
|
226
|
+
- After any code changes, **always compile** to check for errors before deploying
|
|
227
|
+
`;
|
|
228
|
+
cleanupScratch();
|
|
229
|
+
const transport = new StdioServerTransport();
|
|
230
|
+
await server.connect(transport);
|