0
0
Fork 0

feat(launch): launch_codesys_with_project tool

Detached spawn of an arbitrary CODESYS.exe with a .project as CLI arg
and optional --Profile= override. Useful when you want an SP22-saved
project opened in an SP21 IDE for SIM/inspection work, or when this
MCP is bound to install A but you want install B to handle the open
without registering a second server.

The launched IDE is not managed by this MCP: no IPC, no watcher, no
shutdown_codesys. Validates the exe + project paths up front and
returns the new PID.
This commit is contained in:
Karstein Phobic Nyvold Kvistad 2026-05-12 18:57:07 +02:00
parent 885ef84215
commit 7b0c5db125
3 changed files with 45 additions and 3 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "codesys-mcp-sp21-plus",
"version": "0.9.11",
"version": "0.9.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codesys-mcp-sp21-plus",
"version": "0.9.11",
"version": "0.9.12",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",

View file

@ -1,6 +1,6 @@
{
"name": "codesys-mcp-sp21-plus",
"version": "0.9.11",
"version": "0.9.12",
"description": "Codesys-MCP-SP21+ -- fork of luke-harriman/Codesys-MCP carrying CODESYS V3.5 SP22 Patch 1 fixes (and forward-compat with later SPs): script-engine API drift, online/runtime tool auto-login, dual-SHA release classifier, set_pou_code omitted-decl wipe fix, add_library managed-overload, etc. MCP server for CODESYS with persistent UI instance and file-based IPC.",
"main": "dist/server.js",
"bin": {

View file

@ -863,6 +863,48 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
}
);
s.tool(
'launch_codesys_with_project',
"Launch a (potentially different) CODESYS install and open a project in it -- detached from this MCP. Useful for cross-version inspection (e.g. opening an SP22-saved project in an SP21 IDE for a SIM workflow), or for opening a project in an install this MCP isn't bound to. The launched IDE is NOT managed by this server: no IPC, no watcher, no shutdown_codesys. This is intentional -- the goal is just to hand the user a running IDE on a chosen install.",
{
projectFilePath: z.string().describe("Path to the .project file to open. Forward or back slashes both work."),
codesysPath: z.string().optional().describe("Optional override for the CODESYS.exe to launch. Defaults to this server's configured --codesys-path. Use to launch a DIFFERENT install (e.g. SP21 while this server runs SP22)."),
profileName: z.string().optional().describe("Optional --Profile= value for the launched IDE (e.g. 'CODESYS V3.5 SP21 Patch 5'). Omit to let CODESYS pick the project's saved profile or its install default. Mismatched profiles will pop the IDE's profile-conversion dialog -- which is sometimes exactly what you want."),
},
async (args: { projectFilePath: string; codesysPath?: string; profileName?: string }) => {
const targetExe = (args.codesysPath ?? config.codesysPath).trim();
const projectPath = resolvePath(args.projectFilePath, workspaceDir);
if (!fs.existsSync(targetExe)) {
return { content: [{ type: 'text' as const, text: `CODESYS executable not found: ${targetExe}` }], isError: true };
}
// Strip the surrounding single-quotes that resolvePath() adds for use as
// a Python string literal. The shell spawn below quotes argv itself.
const cleanProjectPath = projectPath.replace(/^'|'$/g, '');
if (!fs.existsSync(cleanProjectPath)) {
return { content: [{ type: 'text' as const, text: `Project file not found: ${cleanProjectPath}` }], isError: true };
}
try {
const { spawn } = require('child_process') as typeof import('child_process');
const argv: string[] = [];
if (args.profileName) argv.push(`--Profile=${args.profileName}`);
argv.push(cleanProjectPath);
const child = spawn(targetExe, argv, { detached: true, stdio: 'ignore' });
child.unref();
const profileHint = args.profileName ? ` --Profile="${args.profileName}"` : '';
return {
content: [{
type: 'text' as const,
text: `Launched ${targetExe}${profileHint} with project ${cleanProjectPath}. PID ${child.pid ?? 'unknown'}. The IDE is detached -- this MCP does not manage its lifecycle.`,
}],
isError: false,
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: 'text' as const, text: `Launch failed: ${msg}` }], isError: true };
}
}
);
s.tool(
'shutdown_codesys',
'Shut down the persistent CODESYS instance.',