From 4200b15b40c6fef61a4de2d464c7e15eb214e7c5 Mon Sep 17 00:00:00 2001 From: Karstein Phobic Nyvold Kvistad Date: Mon, 11 May 2026 17:45:30 +0200 Subject: [PATCH] fix(device-tools): surface JSON output in tool responses scan_network_devices, verify_device_reachable, and rebind_device_to_scan_result all emit JSON between marker pairs in their script stdout. The tool handlers wrapped that output with formatToolResponse which returns only the success line on success -- hiding the actual data so the agent had nothing to act on. Adds extractMarkerJson() helper that pulls the block between the start and end markers and pretty-prints it, then returns the JSON in the tool response. Now the agent sees the full scan results / reachability candidates / rebind outcome. --- package-lock.json | 4 ++-- package.json | 2 +- src/server.ts | 38 +++++++++++++++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index abcf674..84c655c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codesys-mcp-sp21-plus", - "version": "0.9.5", + "version": "0.9.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codesys-mcp-sp21-plus", - "version": "0.9.5", + "version": "0.9.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", diff --git a/package.json b/package.json index 1e5e656..817471c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codesys-mcp-sp21-plus", - "version": "0.9.5", + "version": "0.9.6", "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": { diff --git a/src/server.ts b/src/server.ts index fccca41..8df76f7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1845,6 +1845,23 @@ export async function startMcpServer(config: ServerConfig): Promise { } ); + // Extract a JSON block between marker lines and pretty-print it; if no + // markers found, return the raw output. Used by the device tools so the + // agent actually sees the scan results / reachability candidates. + const extractMarkerJson = (output: string, startMarker: string, endMarker: string): string => { + const startIdx = output.indexOf(startMarker); + const endIdx = output.indexOf(endMarker); + if (startIdx === -1 || endIdx === -1 || startIdx >= endIdx) { + return output.trim(); + } + const raw = output.substring(startIdx + startMarker.length, endIdx).trim(); + try { + return JSON.stringify(JSON.parse(raw), null, 2); + } catch { + return raw; + } + }; + s.tool( 'scan_network_devices', "Drive the gateway's Scan Network on the project's configured device. Returns the list of physical CODESYS targets currently visible to the gateway (device_name, type_name, vendor_name, address, device_id). Useful when the cached device address is stale and you need to find where the PLC actually is now. Set useCache=true to return the gateway's last scan result without re-scanning (cheap polling).", @@ -1860,7 +1877,12 @@ export async function startMcpServer(config: ServerConfig): Promise { ['ensure_project_open', 'find_target_device'] ); const result = await executor.executeScript(script, 60_000); - return formatToolResponse(result, `Network scan completed for ${args.projectFilePath}.`); + const success = result.success && result.output.includes('SCRIPT_SUCCESS'); + if (!success) { + return formatToolResponse(result, ''); + } + const json = extractMarkerJson(result.output, '### NETWORK_SCAN_START ###', '### NETWORK_SCAN_END ###'); + return { content: [{ type: 'text' as const, text: `Network scan for ${args.projectFilePath}:\n${json}` }], isError: false }; } ); @@ -1878,7 +1900,12 @@ export async function startMcpServer(config: ServerConfig): Promise { ['ensure_project_open', 'find_target_device'] ); const result = await executor.executeScript(script, 60_000); - return formatToolResponse(result, `Device reachability checked for ${args.projectFilePath}.`); + const success = result.success && result.output.includes('SCRIPT_SUCCESS'); + if (!success) { + return formatToolResponse(result, ''); + } + const json = extractMarkerJson(result.output, '### DEVICE_REACHABILITY_START ###', '### DEVICE_REACHABILITY_END ###'); + return { content: [{ type: 'text' as const, text: `Reachability for ${args.projectFilePath}:\n${json}` }], isError: false }; } ); @@ -1904,7 +1931,12 @@ export async function startMcpServer(config: ServerConfig): Promise { ['ensure_project_open', 'find_target_device'] ); const result = await executor.executeScript(script, 60_000); - return formatToolResponse(result, `Device rebind attempted for ${args.projectFilePath}.`); + const success = result.success && result.output.includes('SCRIPT_SUCCESS'); + if (!success) { + return formatToolResponse(result, ''); + } + const json = extractMarkerJson(result.output, '### REBIND_RESULT_START ###', '### REBIND_RESULT_END ###'); + return { content: [{ type: 'text' as const, text: `Rebind for ${args.projectFilePath}:\n${json}` }], isError: false }; } );