sylo-codesys 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 +90 -0
- package/extensions/index.ts +387 -0
- package/package.json +30 -0
- package/skills/codesys/SKILL.md +247 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yeti-Trix <131923258+Yeti-Trix@users.noreply.github.com>
|
|
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,90 @@
|
|
|
1
|
+
# sylo-codesys
|
|
2
|
+
|
|
3
|
+
Drive a **live, open CODESYS V3.5 project** from Sylo chat — create POUs, write
|
|
4
|
+
Structured Text, compile, read/write PLC variables — with changes appearing in
|
|
5
|
+
the CODESYS IDE in real time.
|
|
6
|
+
|
|
7
|
+
This package does **not** speak MCP and does **not** spawn CODESYS. It is the
|
|
8
|
+
skill + a setup/diagnostic tool. The actual CODESYS tools come from the external
|
|
9
|
+
[`codesys-mcp-sp21-plus`](https://github.com/phobicdotno/Codesys-MCP-SP21-plus)
|
|
10
|
+
stdio MCP server, surfaced into Pi by the **`pi-mcp-adapter`** package once you
|
|
11
|
+
add a `codesys` server to a workspace `.mcp.json`.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- **CODESYS V3.5 SP19 / SP21 / SP22** installed (Windows).
|
|
16
|
+
- **Node.js 18+** (for the MCP server).
|
|
17
|
+
- The **`pi-mcp-adapter`** package enabled in Sylo → Capability manager.
|
|
18
|
+
- The global npm package `codesys-mcp-sp21-plus`:
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g codesys-mcp-sp21-plus
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
1. Enable **sylo-codesys** in Sylo → Capability manager → Sylo optional packages.
|
|
26
|
+
2. Enable **pi-mcp-adapter** (separate package) if it isn't already.
|
|
27
|
+
3. In Sylo chat, ask the agent to call **`codesys_setup_check`**. It detects your
|
|
28
|
+
CODESYS install, verifies the npm package, checks for a `codesys` server in
|
|
29
|
+
your `.mcp.json`, confirms the `mcp` adapter is live, and prints a
|
|
30
|
+
ready-to-paste, augmented `.mcp.json` snippet.
|
|
31
|
+
4. Paste that snippet into your workspace `.mcp.json` (merge into the existing
|
|
32
|
+
`mcpServers` object). The recommended entry looks like:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"codesys": {
|
|
38
|
+
"command": "codesys-mcp-sp21-plus",
|
|
39
|
+
"args": [
|
|
40
|
+
"--codesys-path", "C:\\Program Files\\CODESYS 3.5.22.30\\CODESYS\\Common\\CODESYS.exe",
|
|
41
|
+
"--codesys-profile", "CODESYS V3.5 SP22 Patch 3",
|
|
42
|
+
"--mode", "persistent",
|
|
43
|
+
"--no-auto-launch",
|
|
44
|
+
"--auto-mirror"
|
|
45
|
+
],
|
|
46
|
+
"lifecycle": "lazy-keep-alive",
|
|
47
|
+
"requestTimeoutMs": 180000,
|
|
48
|
+
"directTools": ["get_codesys_status", "get_project_info", "get_all_pou_code", "create_pou", "set_pou_code", "create_method", "create_property", "create_dut", "create_gvl", "create_folder", "delete_object", "rename_object", "save_project", "compile_project", "get_compile_messages", "add_library", "list_project_libraries", "connect_to_device", "read_variable", "write_variable", "start_stop_application", "get_application_state", "mirror_export", "shutdown_codesys"],
|
|
49
|
+
"approveTools": ["delete_object", "write_variable", "force_variables", "unforce_variables", "download_to_device", "reset_application", "shutdown_codesys", "remove_library"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
Get the exact `--codesys-path` / `--codesys-profile` strings with
|
|
55
|
+
`codesys-mcp-sp21-plus --print-config` (the setup tool does this for you).
|
|
56
|
+
|
|
57
|
+
5. Restart the broker so `pi-mcp-adapter` re-reads `.mcp.json`.
|
|
58
|
+
6. In chat, ask the agent to drive CODESYS (e.g. "create a POU `FB_Motor` in
|
|
59
|
+
Application and compile"). The first tool call launches CODESYS (persistent,
|
|
60
|
+
UI visible); with `lazy-keep-alive` it stays resident across turns.
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Sylo agent (LLM)
|
|
66
|
+
│ calls codesys_create_pou, codesys_compile_project, … (directTools)
|
|
67
|
+
│ or mcp({ tool: "create_pou", args }) (proxy, other tools)
|
|
68
|
+
▼
|
|
69
|
+
pi-mcp-adapter (Pi extension; spawns + owns the MCP server stdio process)
|
|
70
|
+
▼
|
|
71
|
+
codesys-mcp-sp21-plus (Node, stdio MCP server)
|
|
72
|
+
│ file-based IPC: writes command .py → watcher polls → result
|
|
73
|
+
▼
|
|
74
|
+
CODESYS.exe (persistent, --runscript=watcher.py, UI visible)
|
|
75
|
+
▼
|
|
76
|
+
open .project (live; operator watches changes in the IDE)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- Modifying tools (`set_pou_code`, `create_pou`, …) **auto-save** the project.
|
|
82
|
+
**Ctrl+Z in the IDE does not recover `set_pou_code` edits.** With
|
|
83
|
+
`--auto-mirror`, each change re-runs `mirror_export` to a `.st` mirror on disk
|
|
84
|
+
— commit that mirror to git for real undo.
|
|
85
|
+
- `connect_to_device` may pop a "Device User Login" modal in the IDE. Pre-set
|
|
86
|
+
`CODESYS_DEVICE_USER` / `CODESYS_DEVICE_PASSWORD` in the server `env` block to
|
|
87
|
+
suppress it.
|
|
88
|
+
- MCP-server compatibility & docs: see the
|
|
89
|
+
[codesys-mcp-sp21-plus README](https://github.com/phobicdotno/Codesys-MCP-SP21-plus#readme)
|
|
90
|
+
and [ARCHITECTURE.md](https://github.com/phobicdotno/Codesys-MCP-SP21-plus/blob/main/ARCHITECTURE.md).
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sylo-codesys — setup + diagnostics for the CODESYS MCP bridge.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: this package does NOT speak MCP itself and does NOT spawn CODESYS.
|
|
5
|
+
* The actual CODESYS tools come from the `codesys-mcp-sp21-plus` stdio MCP
|
|
6
|
+
* server, surfaced into Pi through the **pi-mcp-adapter** package (enabled
|
|
7
|
+
* separately in the Capability manager) once the operator adds a `codesys`
|
|
8
|
+
* server entry to a workspace `.mcp.json`.
|
|
9
|
+
*
|
|
10
|
+
* The single tool registered here, `codesys_setup_check`, validates that wiring:
|
|
11
|
+
* - detects the installed CODESYS V3.5.x under C:\Program Files,
|
|
12
|
+
* - checks the global `codesys-mcp-sp21-plus` npm package (and runs
|
|
13
|
+
* `--print-config` to read the exact codesys-path / codesys-profile),
|
|
14
|
+
* - inspects the workspace `.mcp.json` (+ ~/.pi/agent/mcp.json) for a codesys
|
|
15
|
+
* server entry and reports its key fields,
|
|
16
|
+
* - confirms the `mcp` adapter tool (pi-mcp-adapter) is live, and
|
|
17
|
+
* - emits a ready-to-paste, *augmented* `.mcp.json` snippet: a curated
|
|
18
|
+
* ~24-tool directTools surface, destructive-op approval gating, and a
|
|
19
|
+
* lazy-keep-alive lifecycle that keeps the CODESYS instance resident across
|
|
20
|
+
* turns while launching it lazily on first use.
|
|
21
|
+
*
|
|
22
|
+
* It is read-only with respect to operator config — it never writes the
|
|
23
|
+
* `.mcp.json` (operator-owned config; the host does not silently enable MCP
|
|
24
|
+
* servers). The operator (or the agent via `write`) pastes the snippet.
|
|
25
|
+
*/
|
|
26
|
+
import { execFile } from 'node:child_process'
|
|
27
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
28
|
+
import { homedir } from 'node:os'
|
|
29
|
+
import { join } from 'node:path'
|
|
30
|
+
import { promisify } from 'node:util'
|
|
31
|
+
|
|
32
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
33
|
+
import { Type } from 'typebox'
|
|
34
|
+
|
|
35
|
+
const execFileAsync = promisify(execFile)
|
|
36
|
+
|
|
37
|
+
type ToolContentBlock = { type: 'text'; text: string }
|
|
38
|
+
|
|
39
|
+
function toolText(text: string): { content: ToolContentBlock[]; details: unknown } {
|
|
40
|
+
return { content: [{ type: 'text', text }], details: {} }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Strip // line + block comments and trailing commas so a JSONC .mcp.json parses. */
|
|
44
|
+
function parseJsoncLoose(raw: string): unknown {
|
|
45
|
+
const attempt = (s: string): unknown => {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(s)
|
|
48
|
+
} catch {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const direct = attempt(raw)
|
|
53
|
+
if (direct !== null) return direct
|
|
54
|
+
const stripped = raw
|
|
55
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
56
|
+
.split('\n')
|
|
57
|
+
.map((line) => {
|
|
58
|
+
const i = line.indexOf('//')
|
|
59
|
+
return i === -1 ? line : line.slice(0, i)
|
|
60
|
+
})
|
|
61
|
+
.join('\n')
|
|
62
|
+
.replace(/,(\s*[}\]])/g, '$1')
|
|
63
|
+
return attempt(stripped)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface CodesysInstall {
|
|
67
|
+
path: string
|
|
68
|
+
folder: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function detectCodesysInstalls(): CodesysInstall[] {
|
|
72
|
+
const out: CodesysInstall[] = []
|
|
73
|
+
const progFiles = 'C:\\Program Files'
|
|
74
|
+
try {
|
|
75
|
+
if (!existsSync(progFiles)) return out
|
|
76
|
+
for (const folder of readdirSync(progFiles)) {
|
|
77
|
+
if (!/^CODESYS 3\.5\./i.test(folder)) continue
|
|
78
|
+
const exe = join(progFiles, folder, 'CODESYS', 'Common', 'CODESYS.exe')
|
|
79
|
+
if (existsSync(exe)) out.push({ path: exe, folder })
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore FS errors — this is best-effort detection */
|
|
83
|
+
}
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ServerEntry {
|
|
88
|
+
command?: string
|
|
89
|
+
args?: string[]
|
|
90
|
+
lifecycle?: string
|
|
91
|
+
directTools?: unknown
|
|
92
|
+
approveTools?: unknown
|
|
93
|
+
[k: string]: unknown
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readMcpJson(path: string): Record<string, unknown> | null {
|
|
97
|
+
if (!existsSync(path)) return null
|
|
98
|
+
try {
|
|
99
|
+
const raw = readFileSync(path, 'utf8')
|
|
100
|
+
const v = parseJsoncLoose(raw)
|
|
101
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : null
|
|
102
|
+
} catch {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function findCodesysServers(obj: Record<string, unknown>): Record<string, ServerEntry> | null {
|
|
108
|
+
const servers = obj.mcpServers
|
|
109
|
+
if (!servers || typeof servers !== 'object' || Array.isArray(servers)) return null
|
|
110
|
+
const map = servers as Record<string, ServerEntry>
|
|
111
|
+
const keys = Object.keys(map).filter((k) => /codesys/i.test(k))
|
|
112
|
+
if (keys.length === 0) return null
|
|
113
|
+
const out: Record<string, ServerEntry> = {}
|
|
114
|
+
for (const k of keys) out[k] = map[k]!
|
|
115
|
+
return out
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function argAfter(args: string[] | undefined, flag: string): string | undefined {
|
|
119
|
+
if (!args) return undefined
|
|
120
|
+
const i = args.indexOf(flag)
|
|
121
|
+
return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Curated first-class tool surface (original MCP tool names). */
|
|
125
|
+
const CURATED_DIRECT_TOOLS = [
|
|
126
|
+
'get_codesys_status',
|
|
127
|
+
'get_project_info',
|
|
128
|
+
'get_all_pou_code',
|
|
129
|
+
'create_pou',
|
|
130
|
+
'set_pou_code',
|
|
131
|
+
'create_method',
|
|
132
|
+
'create_property',
|
|
133
|
+
'create_dut',
|
|
134
|
+
'create_gvl',
|
|
135
|
+
'create_folder',
|
|
136
|
+
'delete_object',
|
|
137
|
+
'rename_object',
|
|
138
|
+
'save_project',
|
|
139
|
+
'compile_project',
|
|
140
|
+
'get_compile_messages',
|
|
141
|
+
'add_library',
|
|
142
|
+
'list_project_libraries',
|
|
143
|
+
'connect_to_device',
|
|
144
|
+
'read_variable',
|
|
145
|
+
'write_variable',
|
|
146
|
+
'start_stop_application',
|
|
147
|
+
'get_application_state',
|
|
148
|
+
'mirror_export',
|
|
149
|
+
'shutdown_codesys',
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
/** Tools that must be confirmed before running (destructive / online forces). */
|
|
153
|
+
const APPROVE_TOOLS = [
|
|
154
|
+
'delete_object',
|
|
155
|
+
'write_variable',
|
|
156
|
+
'force_variables',
|
|
157
|
+
'unforce_variables',
|
|
158
|
+
'download_to_device',
|
|
159
|
+
'reset_application',
|
|
160
|
+
'shutdown_codesys',
|
|
161
|
+
'remove_library',
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
function buildAugmentedEntry(codesysPath: string, profile: string): Record<string, unknown> {
|
|
165
|
+
return {
|
|
166
|
+
command: 'codesys-mcp-sp21-plus',
|
|
167
|
+
args: [
|
|
168
|
+
'--codesys-path',
|
|
169
|
+
codesysPath,
|
|
170
|
+
'--codesys-profile',
|
|
171
|
+
profile,
|
|
172
|
+
'--mode',
|
|
173
|
+
'persistent',
|
|
174
|
+
'--no-auto-launch',
|
|
175
|
+
'--auto-mirror',
|
|
176
|
+
],
|
|
177
|
+
lifecycle: 'lazy-keep-alive',
|
|
178
|
+
requestTimeoutMs: 180000,
|
|
179
|
+
directTools: CURATED_DIRECT_TOOLS,
|
|
180
|
+
approveTools: APPROVE_TOOLS,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function extractPathProfileFromPrintConfig(raw: string): {
|
|
185
|
+
path?: string
|
|
186
|
+
profile?: string
|
|
187
|
+
} {
|
|
188
|
+
const v = parseJsoncLoose(raw) as { mcpServers?: Record<string, ServerEntry> } | null
|
|
189
|
+
if (!v?.mcpServers) return {}
|
|
190
|
+
const keys = Object.keys(v.mcpServers)
|
|
191
|
+
// Prefer an SP22 entry; fall back to the first.
|
|
192
|
+
const pick = keys.find((k) => /sp22/i.test(k)) ?? keys[0]
|
|
193
|
+
if (!pick) return {}
|
|
194
|
+
const entry = v.mcpServers[pick]!
|
|
195
|
+
return {
|
|
196
|
+
path: argAfter(entry.args, '--codesys-path'),
|
|
197
|
+
profile: argAfter(entry.args, '--codesys-profile'),
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function runPrintConfig(): Promise<{ ok: boolean; raw?: string; error?: string }> {
|
|
202
|
+
try {
|
|
203
|
+
const { stdout } = await execFileAsync('codesys-mcp-sp21-plus', ['--print-config'], {
|
|
204
|
+
windowsHide: true,
|
|
205
|
+
timeout: 15_000,
|
|
206
|
+
})
|
|
207
|
+
return { ok: true, raw: stdout }
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const e = err as NodeJS.ErrnoException
|
|
210
|
+
if (e.code === 'ENOENT') return { ok: false, error: 'not-installed' }
|
|
211
|
+
return { ok: false, error: e.message ?? String(err) }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export default function piSyloCodesysExtension(pi: ExtensionAPI): void {
|
|
216
|
+
pi.registerTool({
|
|
217
|
+
name: 'codesys_setup_check',
|
|
218
|
+
label: 'CODESYS MCP setup check',
|
|
219
|
+
description:
|
|
220
|
+
'Validate the CODESYS MCP bridge wiring for this Sylo: detect the CODESYS V3.5 install, ' +
|
|
221
|
+
'check the global `codesys-mcp-sp21-plus` npm package, inspect the workspace `.mcp.json` for a ' +
|
|
222
|
+
'codesys server entry, confirm the `mcp` adapter tool (pi-mcp-adapter) is live, and emit a ' +
|
|
223
|
+
'ready-to-paste augmented `.mcp.json` snippet (curated directTools + destructive-op approval ' +
|
|
224
|
+
'gating + lazy-keep-alive lifecycle). The CODESYS tools themselves come from the MCP server via ' +
|
|
225
|
+
'pi-mcp-adapter (directTools + the `mcp` proxy), NOT from this tool. Run once after enabling the ' +
|
|
226
|
+
'sylo-codesys package and whenever the wiring changes.',
|
|
227
|
+
parameters: Type.Object({
|
|
228
|
+
mcp_json_path: Type.Optional(
|
|
229
|
+
Type.String({
|
|
230
|
+
description:
|
|
231
|
+
'Path to the `.mcp.json` to inspect (defaults to <cwd>/.mcp.json). ' +
|
|
232
|
+
'Also checks ~/.pi/agent/mcp.json.',
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
print_config: Type.Optional(
|
|
236
|
+
Type.Boolean({
|
|
237
|
+
description:
|
|
238
|
+
'Run `codesys-mcp-sp21-plus --print-config` to read the exact CODESYS path/profile ' +
|
|
239
|
+
'(default true; needs the package installed).',
|
|
240
|
+
}),
|
|
241
|
+
),
|
|
242
|
+
}),
|
|
243
|
+
async execute(_toolCallId, params) {
|
|
244
|
+
const lines: string[] = []
|
|
245
|
+
const wantPrint = params.print_config !== false
|
|
246
|
+
|
|
247
|
+
// 1. CODESYS install detection
|
|
248
|
+
lines.push('=== CODESYS install ===')
|
|
249
|
+
const installs = detectCodesysInstalls()
|
|
250
|
+
if (installs.length === 0) {
|
|
251
|
+
lines.push(' No CODESYS 3.5.x install found under C:\\Program Files.')
|
|
252
|
+
} else {
|
|
253
|
+
for (const i of installs) lines.push(` ${i.folder} -> ${i.path}`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. codesys-mcp-sp21-plus package + --print-config
|
|
257
|
+
lines.push('')
|
|
258
|
+
lines.push('=== codesys-mcp-sp21-plus (npm) ===')
|
|
259
|
+
let detectedPath: string | undefined
|
|
260
|
+
let detectedProfile: string | undefined
|
|
261
|
+
let packageInstalled = false
|
|
262
|
+
if (wantPrint) {
|
|
263
|
+
const pc = await runPrintConfig()
|
|
264
|
+
if (pc.ok && pc.raw) {
|
|
265
|
+
packageInstalled = true
|
|
266
|
+
const ex = extractPathProfileFromPrintConfig(pc.raw)
|
|
267
|
+
detectedPath = ex.path
|
|
268
|
+
detectedProfile = ex.profile
|
|
269
|
+
lines.push(' Installed. `--print-config` OK.')
|
|
270
|
+
if (detectedPath) lines.push(` path: ${detectedPath}`)
|
|
271
|
+
if (detectedProfile) lines.push(` profile: ${detectedProfile}`)
|
|
272
|
+
} else if (pc.error === 'not-installed') {
|
|
273
|
+
lines.push(' NOT installed / not on PATH. Install with:')
|
|
274
|
+
lines.push(' npm install -g codesys-mcp-sp21-plus')
|
|
275
|
+
} else {
|
|
276
|
+
lines.push(` \`--print-config\` failed: ${pc.error}`)
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
lines.push(' (print_config=false — skipped)')
|
|
280
|
+
}
|
|
281
|
+
if (!detectedPath && installs.length > 0) {
|
|
282
|
+
detectedPath = installs[0]!.path
|
|
283
|
+
lines.push(` (using filesystem-scanned path for the snippet: ${detectedPath})`)
|
|
284
|
+
}
|
|
285
|
+
if (!detectedProfile) {
|
|
286
|
+
lines.push(
|
|
287
|
+
' profile: UNKNOWN — run `codesys-mcp-sp21-plus --print-config` to get the exact string.',
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 3. .mcp.json inspection
|
|
292
|
+
lines.push('')
|
|
293
|
+
lines.push('=== .mcp.json ===')
|
|
294
|
+
const cwd = process.cwd()
|
|
295
|
+
const candidates = [
|
|
296
|
+
{
|
|
297
|
+
label: 'workspace',
|
|
298
|
+
path: (params.mcp_json_path ?? '').trim() || join(cwd, '.mcp.json'),
|
|
299
|
+
},
|
|
300
|
+
{ label: 'user-global', path: join(homedir(), '.pi', 'agent', 'mcp.json') },
|
|
301
|
+
]
|
|
302
|
+
let foundCodesysEntry = false
|
|
303
|
+
for (const c of candidates) {
|
|
304
|
+
const obj = readMcpJson(c.path)
|
|
305
|
+
if (!obj) {
|
|
306
|
+
lines.push(` ${c.label}: (none) ${c.path}`)
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
lines.push(` ${c.label}: ${c.path}`)
|
|
310
|
+
const codesys = findCodesysServers(obj)
|
|
311
|
+
if (codesys) {
|
|
312
|
+
foundCodesysEntry = true
|
|
313
|
+
for (const [k, e] of Object.entries(codesys)) {
|
|
314
|
+
const cp = argAfter(e.args, '--codesys-path')
|
|
315
|
+
const cf = argAfter(e.args, '--codesys-profile')
|
|
316
|
+
const mode = argAfter(e.args, '--mode')
|
|
317
|
+
const lifecycle = typeof e.lifecycle === 'string' ? e.lifecycle : '(unset)'
|
|
318
|
+
const dt = Array.isArray(e.directTools) ? e.directTools.length : 0
|
|
319
|
+
lines.push(` server "${k}":`)
|
|
320
|
+
lines.push(` command: ${e.command ?? '?'}`)
|
|
321
|
+
if (cp) lines.push(` codesys-path: ${cp}`)
|
|
322
|
+
if (cf) lines.push(` codesys-profile: ${cf}`)
|
|
323
|
+
if (mode) lines.push(` mode: ${mode}`)
|
|
324
|
+
lines.push(` lifecycle: ${lifecycle}, directTools: ${dt}`)
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
lines.push(' (no codesys* server entry — add one using the snippet below)')
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 4. pi-mcp-adapter live?
|
|
332
|
+
lines.push('')
|
|
333
|
+
lines.push('=== pi-mcp-adapter ===')
|
|
334
|
+
let mcpTool = false
|
|
335
|
+
try {
|
|
336
|
+
mcpTool = pi.getAllTools().some((t) => t.name === 'mcp')
|
|
337
|
+
} catch {
|
|
338
|
+
/* getAllTools not available yet — treat as unknown */
|
|
339
|
+
}
|
|
340
|
+
if (mcpTool) {
|
|
341
|
+
lines.push(' `mcp` tool is registered — pi-mcp-adapter is enabled.')
|
|
342
|
+
} else {
|
|
343
|
+
lines.push(
|
|
344
|
+
' `mcp` tool NOT registered — enable the pi-mcp-adapter package in the Capability manager, then restart the broker.',
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
if (mcpTool && !foundCodesysEntry) {
|
|
348
|
+
lines.push(
|
|
349
|
+
' (adapter is live but no codesys server is configured — paste the snippet below into a .mcp.json.)',
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 5. Ready-to-paste snippet
|
|
354
|
+
if (detectedPath && detectedProfile) {
|
|
355
|
+
lines.push('')
|
|
356
|
+
lines.push('=== Augmented .mcp.json snippet (merge into your .mcp.json) ===')
|
|
357
|
+
const entry = buildAugmentedEntry(detectedPath, detectedProfile)
|
|
358
|
+
lines.push(JSON.stringify({ mcpServers: { codesys: entry } }, null, 2))
|
|
359
|
+
lines.push('')
|
|
360
|
+
lines.push('Notes:')
|
|
361
|
+
lines.push(
|
|
362
|
+
' - directTools promote a ~24-tool curated surface as first-class Pi tools named codesys_<tool> ' +
|
|
363
|
+
'(codesys_create_pou, codesys_set_pou_code, codesys_compile_project, ...).',
|
|
364
|
+
)
|
|
365
|
+
lines.push(
|
|
366
|
+
' - approveTools require confirmation before destructive/online ops (delete_object, write_variable, force_variables, download_to_device, ...).',
|
|
367
|
+
)
|
|
368
|
+
lines.push(
|
|
369
|
+
' - lazy-keep-alive: CODESYS spawns on the first tool call and stays resident across turns; ' +
|
|
370
|
+
'--no-auto-launch keeps it from starting at broker boot.',
|
|
371
|
+
)
|
|
372
|
+
lines.push(
|
|
373
|
+
' - --auto-mirror re-runs mirror_export after each change so the .st mirror stays in sync for undo ' +
|
|
374
|
+
'(Ctrl+Z in the IDE does NOT recover set_pou_code edits).',
|
|
375
|
+
)
|
|
376
|
+
lines.push(' - After editing .mcp.json, restart the broker so pi-mcp-adapter re-reads it.')
|
|
377
|
+
} else if (!packageInstalled) {
|
|
378
|
+
lines.push('')
|
|
379
|
+
lines.push(
|
|
380
|
+
'Next: `npm install -g codesys-mcp-sp21-plus`, then re-run codesys_setup_check to get the exact .mcp.json snippet.',
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return toolText(lines.join('\n'))
|
|
385
|
+
},
|
|
386
|
+
})
|
|
387
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sylo-codesys",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drive a live, open CODESYS V3.5 project from chat via the codesys-mcp-sp21-plus MCP server, bridged through pi-mcp-adapter. Skill + setup/diagnostic tool. No host MCP client.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
"extensions",
|
|
11
|
+
"skills",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"pi": {
|
|
16
|
+
"extensions": [
|
|
17
|
+
"./extensions/index.ts"
|
|
18
|
+
],
|
|
19
|
+
"skills": [
|
|
20
|
+
"./skills/codesys"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@earendil-works/pi-coding-agent": "^0.84.2"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"typebox": "^1.1.24"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT"
|
|
30
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codesys
|
|
3
|
+
description: Drive a live, open CODESYS V3.5 project from chat — create POUs, write Structured Text, compile, read/write PLC variables — via the codesys-mcp-sp21-plus MCP server bridged through pi-mcp-adapter.
|
|
4
|
+
metadata:
|
|
5
|
+
sylo:
|
|
6
|
+
category: automation
|
|
7
|
+
icon: cpu
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CODESYS — live project via MCP
|
|
11
|
+
|
|
12
|
+
You drive a **live, open CODESYS V3.5 project** from chat. Every change is
|
|
13
|
+
applied to the project the operator has open and appears in the CODESYS IDE in
|
|
14
|
+
real time. It is the standalone CODESYS chat experience for live projects.
|
|
15
|
+
|
|
16
|
+
## How this is wired (the part you cannot see)
|
|
17
|
+
|
|
18
|
+
These CODESYS tools are **not** built into Sylo. They come from the external
|
|
19
|
+
`codesys-mcp-sp21-plus` stdio MCP server, surfaced into Pi by the
|
|
20
|
+
**pi-mcp-adapter** package once a `codesys` server is configured in a
|
|
21
|
+
workspace `.mcp.json`. If the wiring is missing or broken, your tool calls will
|
|
22
|
+
fail — fix the wiring, don't keep retrying blindly.
|
|
23
|
+
|
|
24
|
+
**Run `codesys_setup_check` first** whenever CODESYS is the task or a call
|
|
25
|
+
fails. It reports: is CODESYS installed? is `codesys-mcp-sp21-plus` on PATH? is
|
|
26
|
+
there a `codesys` server in `.mcp.json`? is the `mcp` adapter tool live? It also
|
|
27
|
+
emits a ready-to-paste `.mcp.json` snippet. Tell the operator the gaps and the
|
|
28
|
+
exact fix (install command / enable pi-mcp-adapter / paste snippet / restart
|
|
29
|
+
broker).
|
|
30
|
+
|
|
31
|
+
## Two ways to call the tools
|
|
32
|
+
|
|
33
|
+
1. **Direct tools** (if `directTools` is configured in `.mcp.json`): a curated
|
|
34
|
+
~24-tool surface is registered as first-class Pi tools named `codesys_<mcp_name>`:
|
|
35
|
+
`codesys_get_codesys_status`, `codesys_get_project_info`, `codesys_get_all_pou_code`,
|
|
36
|
+
`codesys_create_pou`, `codesys_set_pou_code`, `codesys_create_method`,
|
|
37
|
+
`codesys_create_property`, `codesys_create_dut`, `codesys_create_gvl`,
|
|
38
|
+
`codesys_create_folder`, `codesys_delete_object`, `codesys_rename_object`,
|
|
39
|
+
`codesys_save_project`, `codesys_compile_project`, `codesys_get_compile_messages`,
|
|
40
|
+
`codesys_add_library`, `codesys_list_project_libraries`, `codesys_connect_to_device`,
|
|
41
|
+
`codesys_read_variable`, `codesys_write_variable`, `codesys_start_stop_application`,
|
|
42
|
+
`codesys_get_application_state`, `codesys_mirror_export`, `codesys_shutdown_codesys`.
|
|
43
|
+
Prefer these when they fit — no search round-trip.
|
|
44
|
+
|
|
45
|
+
2. **The `mcp` proxy** for everything else (the other ~78 tools, plus resources).
|
|
46
|
+
Discover → describe → call:
|
|
47
|
+
```
|
|
48
|
+
mcp({ search: "symbol config" }) // find candidate tools
|
|
49
|
+
mcp({ describe: "create_symbol_config" }) // read the schema
|
|
50
|
+
mcp({ tool: "create_symbol_config", args: { projectFilePath: "C:/p.project", ... } })
|
|
51
|
+
```
|
|
52
|
+
**Project structure is an MCP resource, not a tool**: read it via the proxy
|
|
53
|
+
with the resource URI `codesys://project/{+project_path}/structure` (the
|
|
54
|
+
adapter exposes resources as tools when `exposeResources` is on). For a plain
|
|
55
|
+
project-info read, use the `codesys_get_project_info` direct tool.
|
|
56
|
+
|
|
57
|
+
## `projectFilePath` is required on every tool
|
|
58
|
+
|
|
59
|
+
Every CODESYS tool takes `projectFilePath` (absolute path to the open `.project`
|
|
60
|
+
file, forward or back slashes both work). **Determine it before calling:**
|
|
61
|
+
- Ask the operator which `.project` they have open, or
|
|
62
|
+
- Look in the workspace for `*.project`, or
|
|
63
|
+
- Run `codesys_setup_check` (it lists the detected CODESYS install but not the
|
|
64
|
+
project — the project path still comes from the operator/workspace).
|
|
65
|
+
|
|
66
|
+
The server's `ensure_project_open` helper opens the project if it isn't already
|
|
67
|
+
open, so passing the correct path is sufficient.
|
|
68
|
+
|
|
69
|
+
## Discipline
|
|
70
|
+
|
|
71
|
+
- **Inspect before editing.** Call `codesys_get_project_info` + `codesys_get_all_pou_code`
|
|
72
|
+
(or the structure resource) to understand the project. Don't edit blind.
|
|
73
|
+
- **Always compile after authoring.** After `create_pou` / `set_pou_code` /
|
|
74
|
+
`create_method` / `create_dut` / `create_gvl` / `add_library` / `rename_object`,
|
|
75
|
+
call `codesys_compile_project` and **report errors/warnings to the operator
|
|
76
|
+
before declaring success.** Use `codesys_get_compile_messages` for detail.
|
|
77
|
+
These modifying tools **auto-save** the project on success.
|
|
78
|
+
- **Confirm destructive ops.** `delete_object`, `write_variable` (forces),
|
|
79
|
+
`download_to_device`, `reset_application`, `shutdown_codesys`, `remove_library`
|
|
80
|
+
are approval-gated (the operator clicks to confirm). Still announce intent in
|
|
81
|
+
chat first — e.g. "I'm about to delete `Application/OldPou` — confirm?"
|
|
82
|
+
- **Undo caveat.** `set_pou_code` writes the live binary; **Ctrl+Z in the IDE
|
|
83
|
+
does NOT recover it.** With `--auto-mirror` configured, each change re-runs
|
|
84
|
+
`mirror_export` to a `.st` mirror on disk — commit that mirror to git for
|
|
85
|
+
real undo. Call `codesys_mirror_export` to refresh it on demand.
|
|
86
|
+
|
|
87
|
+
## Online / runtime
|
|
88
|
+
|
|
89
|
+
- `codesys_connect_to_device` may pop a **"Device User Login" modal in the
|
|
90
|
+
CODESYS IDE** that the agent cannot see or dismiss. **Announce this to the
|
|
91
|
+
operator before calling** and tell them to be ready to click. To suppress it,
|
|
92
|
+
set `CODESYS_DEVICE_USER` / `CODESYS_DEVICE_PASSWORD` in the `codesys` server
|
|
93
|
+
`env` block of `.mcp.json`.
|
|
94
|
+
- Read/write variables require an online connection first (`codesys_connect_to_device`).
|
|
95
|
+
- `codesys_get_application_state` returns run / stop / exception + logged-in status.
|
|
96
|
+
|
|
97
|
+
## Lifecycle
|
|
98
|
+
|
|
99
|
+
The first tool call launches CODESYS **persistent (UI visible)**; with the
|
|
100
|
+
recommended `lazy-keep-alive` lifecycle the instance stays resident across turns
|
|
101
|
+
so subsequent calls are fast (cold launch + watcher ready can take 60s+). To stop
|
|
102
|
+
CODESYS from chat, call `codesys_shutdown_codesys` (it relaunches on the next
|
|
103
|
+
tool call). If the operator already has CODESYS open with the project, the
|
|
104
|
+
bridge uses that instance.
|
|
105
|
+
|
|
106
|
+
## ⚠️ UI lock — CODESYS scripting engine is single-threaded
|
|
107
|
+
|
|
108
|
+
**While the MCP bridge is active, the operator has limited ability to interact
|
|
109
|
+
with the CODESYS IDE.** The bridge runs a watcher script inside CODESYS's
|
|
110
|
+
scripting engine, which is single-threaded. The watcher loop (`system.delay()`
|
|
111
|
+
polling) blocks new commands — direct double-click navigation and menu actions
|
|
112
|
+
are locked.
|
|
113
|
+
|
|
114
|
+
**Workaround:** right-clicking still works (it's a raw Win32 event that
|
|
115
|
+
bypasses CODESYS's `StartLengthyOperation` gate). After right-clicking on an
|
|
116
|
+
object, double-clicking to open it works. This is not a bug — it's a side
|
|
117
|
+
effect of `system.delay()` pumping the Win32 message loop while
|
|
118
|
+
`StartLengthyOperation()` blocks CODESYS-level commands. See the [CODESYS
|
|
119
|
+
scripting docs on `system.delay`](https://product-help.se.com/docs/Machine+Expert/V1.1/en/ScriptEngine/topics/system.htm).
|
|
120
|
+
|
|
121
|
+
**What works vs. what's blocked in the agent's instance:**
|
|
122
|
+
- ✅ Right-click (raw Win32 event bypasses the gate)
|
|
123
|
+
- ✅ Open a POU via right-click → double-click
|
|
124
|
+
- ❌ Scroll the editor (CODESYS editor command — blocked)
|
|
125
|
+
- ❌ Menu bar, typing, editing (blocked)
|
|
126
|
+
|
|
127
|
+
You can open a POU and see the top of the code, but **you cannot scroll to
|
|
128
|
+
read further down.** For reading long routines, use the second read-only
|
|
129
|
+
instance (below) or ask the agent to dump the code to chat.
|
|
130
|
+
|
|
131
|
+
This is a fundamental limitation of the CODESYS scripting engine, not a bug in
|
|
132
|
+
the MCP server.
|
|
133
|
+
|
|
134
|
+
### Recommended workflow: second read-only CODESYS instance
|
|
135
|
+
|
|
136
|
+
The cleanest workflow uses **two CODESYS instances** — one for the agent
|
|
137
|
+
(MCP bridge), one for the operator (read-only inspection):
|
|
138
|
+
|
|
139
|
+
1. **Agent's instance** — launched by the MCP server, runs the watcher script,
|
|
140
|
+
UI is locked. This is where tool calls create POUs, write ST, compile, etc.
|
|
141
|
+
2. **Operator's instance** — a second CODESYS window opened separately (e.g.
|
|
142
|
+
launch CODESYS manually, File → Open Project, pick the same `.project` file).
|
|
143
|
+
This instance is fully interactive — the operator can navigate, inspect code,
|
|
144
|
+
double-click POUs, etc. without interfering with the bridge.
|
|
145
|
+
3. **Seeing live updates** — CODESYS does NOT auto-detect external file changes
|
|
146
|
+
and has no "reload from disk" button. But the operator can **reopen the
|
|
147
|
+
project without closing CODESYS first** (File → Open Project → select the
|
|
148
|
+
same `.project`). This reloads the project from disk with all the agent's
|
|
149
|
+
changes, without killing the agent's MCP-connected instance.
|
|
150
|
+
4. **When the agent is done with a batch** — the operator reopens the project
|
|
151
|
+
in the read-only instance to inspect the results. No kill, no relaunch, no
|
|
152
|
+
Sylo restart needed.
|
|
153
|
+
|
|
154
|
+
This avoids the kill/relaunch cycle entirely for routine inspection. Only use
|
|
155
|
+
the recovery procedure below if the watcher actually dies (cancel, close, or
|
|
156
|
+
crash).
|
|
157
|
+
|
|
158
|
+
### Recovery when the watcher dies (cancel, close, or crash)
|
|
159
|
+
|
|
160
|
+
No Sylo restart is needed. The MCP server child process is still alive
|
|
161
|
+
(lazy-keep-alive keeps it resident), but the watcher inside CODESYS is dead —
|
|
162
|
+
tool calls will time out (60s). To recover:
|
|
163
|
+
|
|
164
|
+
1. The agent kills the dead CODESYS process from bash:
|
|
165
|
+
```bash
|
|
166
|
+
taskkill /F /IM CODESYS.exe
|
|
167
|
+
```
|
|
168
|
+
2. The next tool call relaunches CODESYS with a fresh watcher and reopens the
|
|
169
|
+
project automatically.
|
|
170
|
+
|
|
171
|
+
Alternatively, call `codesys_launch_codesys_with_project` with
|
|
172
|
+
`killExisting=true` — it kills the orphan and relaunches in one step.
|
|
173
|
+
|
|
174
|
+
### Orphaned CODESYS after Sylo broker restart
|
|
175
|
+
|
|
176
|
+
If the Sylo broker restarts (or the machine reboots), the MCP server child
|
|
177
|
+
process dies but the CODESYS window it launched stays open as an orphan. The
|
|
178
|
+
new MCP server instance refuses to share IPC with the orphan:
|
|
179
|
+
|
|
180
|
+
> `Refusing to launch: 1 CODESYS.exe instance(s) of the same install already
|
|
181
|
+
> running... This MCP server cannot share IPC with an instance it didn't spawn.`
|
|
182
|
+
|
|
183
|
+
Fix: kill the orphan (`taskkill /F /IM CODESYS.exe`) or call
|
|
184
|
+
`launch_codesys_with_project` with `killExisting=true`, then the next tool call
|
|
185
|
+
works normally.
|
|
186
|
+
|
|
187
|
+
## Argument cheat-sheet (most-used tools)
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
// Create a POU (type: Program | FunctionBlock | Function; language: ST | LD | FBD | SFC | IL | CFC)
|
|
191
|
+
codesys_create_pou({
|
|
192
|
+
projectFilePath: "C:/Projects/MyPLC.project",
|
|
193
|
+
name: "FB_Motor",
|
|
194
|
+
type: "FunctionBlock",
|
|
195
|
+
language: "ST",
|
|
196
|
+
parentPath: "Application", // relative path under the project root
|
|
197
|
+
declarationCode: "FUNCTION_BLOCK FB_Motor\nVAR\n bRun : BOOL;\nEND_VAR",
|
|
198
|
+
implementationCode: "IF bRun THEN\n ;\nEND_IF" // optional
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Write declaration and/or implementation of an existing POU / method / property
|
|
202
|
+
codesys_set_pou_code({
|
|
203
|
+
projectFilePath: "C:/Projects/MyPLC.project",
|
|
204
|
+
pouPath: "Application/FB_Motor", // slash path to the object
|
|
205
|
+
declarationCode: "...", // omit a field to leave it unchanged
|
|
206
|
+
implementationCode: "..."
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Create a method on an FB (returnType optional; pass code or leave empty + set_pou_code later)
|
|
210
|
+
codesys_create_method({
|
|
211
|
+
projectFilePath: "...",
|
|
212
|
+
parentPouPath: "Application/FB_Motor",
|
|
213
|
+
methodName: "Reset",
|
|
214
|
+
returnType: "BOOL",
|
|
215
|
+
implementationCode: "Reset := TRUE;"
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// DUT (dutType: Structure | Enumeration | Union | Alias) / GVL / folder
|
|
219
|
+
codesys_create_dut({ projectFilePath: "...", name: "ST_Axis", dutType: "Structure", parentPath: "Application" })
|
|
220
|
+
codesys_create_gvl({ projectFilePath: "...", name: "GVL_IO", parentPath: "Application", declarationCode: "VAR_GLOBAL\n bReady : BOOL;\nEND_VAR" })
|
|
221
|
+
|
|
222
|
+
// Build + messages
|
|
223
|
+
codesys_compile_project({ projectFilePath: "..." })
|
|
224
|
+
codesys_get_compile_messages({ projectFilePath: "..." })
|
|
225
|
+
|
|
226
|
+
// Libraries (name must match the installed Library Repository; refuses unknown libs)
|
|
227
|
+
codesys_add_library({ projectFilePath: "...", libraryName: "Standard" })
|
|
228
|
+
codesys_list_project_libraries({ projectFilePath: "..." })
|
|
229
|
+
|
|
230
|
+
// Online
|
|
231
|
+
codesys_connect_to_device({ projectFilePath: "..." }) // may pop a login modal
|
|
232
|
+
codesys_read_variable({ projectFilePath: "...", variablePath: "PLC_PRG.bMotorRunning" })
|
|
233
|
+
codesys_write_variable({ projectFilePath: "...", variablePath: "PLC_PRG.bMotorRunning", value: "TRUE" })
|
|
234
|
+
codesys_start_stop_application({ projectFilePath: "...", action: "start" }) // action: start | stop
|
|
235
|
+
codesys_get_application_state({ projectFilePath: "..." })
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Searching across POUs:** there is no `search_code` tool. Call
|
|
239
|
+
`codesys_get_all_pou_code` (returns all declarations + implementations) and grep
|
|
240
|
+
the returned text. The project tree is the `codesys://project/{+path}/structure`
|
|
241
|
+
resource, read via the `mcp` proxy.
|
|
242
|
+
|
|
243
|
+
## Scope
|
|
244
|
+
|
|
245
|
+
This skill is only for **live CODESYS V3.5 projects** driven through the MCP
|
|
246
|
+
server. It is not for projects edited as plain files on disk with no CODESYS
|
|
247
|
+
running.
|