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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/dist/core/envelope.js +8 -0
  4. package/dist/core/errors.js +12 -0
  5. package/dist/core/http.js +19 -0
  6. package/dist/core/process.js +30 -0
  7. package/dist/core/response.js +37 -0
  8. package/dist/core/scratch.js +6 -0
  9. package/dist/core/staticServer.js +88 -0
  10. package/dist/server.js +230 -0
  11. package/dist/state.js +57 -0
  12. package/dist/tools/applyProjectChanges.js +371 -0
  13. package/dist/tools/deployAll.js +320 -0
  14. package/dist/tools/hmiBrowser.js +224 -0
  15. package/dist/tools/hmiRuntime.js +273 -0
  16. package/dist/tools/inspectProject.js +162 -0
  17. package/dist/tools/inspectVisuProject.js +474 -0
  18. package/dist/tools/larsRuntime.js +536 -0
  19. package/dist/tools/lasalApps.js +111 -0
  20. package/dist/tools/plcControl.js +530 -0
  21. package/dist/tools/plcDiagnostics.js +172 -0
  22. package/dist/tools/readClassSource.js +147 -0
  23. package/dist/tools/selectProject.js +47 -0
  24. package/dist/tools/setTargetIp.js +114 -0
  25. package/dist/tools/status.js +146 -0
  26. package/dist/tools/visuControl.js +447 -0
  27. package/dist/tools/visuDashboard.js +571 -0
  28. package/dist/utils/batchScript.js +257 -0
  29. package/dist/utils/config.js +34 -0
  30. package/dist/utils/editTransaction.js +39 -0
  31. package/dist/utils/engine.js +163 -0
  32. package/dist/utils/lars.js +471 -0
  33. package/dist/utils/lasalXml.js +758 -0
  34. package/dist/utils/preflight.js +194 -0
  35. package/dist/utils/projectScanner.js +212 -0
  36. package/dist/utils/resolvePaths.js +46 -0
  37. package/dist/utils/respond.js +14 -0
  38. package/dist/utils/scriptRunner.js +161 -0
  39. package/dist/utils/visuDashboardIO.js +198 -0
  40. package/dist/utils/visuPropertyEncoding.js +174 -0
  41. package/dist/utils/visuScript.js +262 -0
  42. 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
+ [![CI](https://github.com/Svel26/Lasal-MCP/actions/workflows/ci.yml/badge.svg)](https://github.com/Svel26/Lasal-MCP/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/lasal-mcp.svg)](https://www.npmjs.com/package/lasal-mcp)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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,8 @@
1
+ export function truncateArray(arr, limit, label) {
2
+ if (arr.length <= limit)
3
+ return { items: arr };
4
+ return {
5
+ items: arr.slice(0, limit),
6
+ truncated: `Showing ${limit} of ${arr.length} ${label}. Use filters to narrow results.`,
7
+ };
8
+ }
@@ -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,6 @@
1
+ import { existsSync, mkdirSync } from "fs";
2
+ import { SCRATCH } from "../utils/engine.js";
3
+ export function ensureScratch() {
4
+ if (!existsSync(SCRATCH))
5
+ mkdirSync(SCRATCH, { recursive: true });
6
+ }
@@ -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);