using System.ComponentModel;
using System.Text;
using System.Text.Json;
using ModelContextProtocol.Server;
namespace ETTerms.PduMcp;
///
/// MCP tools exposed to the AI for controlling an SNMP PDU (iPoMan II/III).
///
/// Unlike the serial bridge, the PDU is reached directly over SNMP — the ETTerms GUI
/// does NOT need to be running. Connections are held per-IP and persist for the lifetime
/// of this MCP server. Always pdu_connect an IP first, then control its ports.
///
[McpServerToolType]
public static class PduTools
{
private static readonly JsonSerializerOptions Json = new() { WriteIndented = false };
[McpServerTool, Description("Connect to a PDU over SNMP by IP and verify it responds. Required before controlling ports. Returns the model name on success.")]
public static Task pdu_connect(
[Description("PDU IP address, e.g. 192.168.1.21")] string ip)
{
ip = (ip ?? "").Trim();
if (ip.Length == 0) return Err("ip is required");
var pdu = PduRegistry.Instance.GetOrAdd(ip);
var model = pdu.GetModelName();
bool ok = !string.IsNullOrEmpty(model) && model.Contains("PDU");
if (!ok)
{
PduRegistry.Instance.Remove(ip);
return Err($"no SNMP response from PDU at {ip} (check IP/network/community)");
}
return Ok(new { ip, model });
}
[McpServerTool, Description("List PDUs currently connected in this MCP session (by IP).")]
public static Task pdu_list()
=> Ok(new { connected = PduRegistry.Instance.ConnectedIps });
[McpServerTool, Description("Turn a PDU outlet on or off. The PDU must be connected first via pdu_connect.")]
public static Task pdu_set_port(
[Description("PDU IP address")] string ip,
[Description("Outlet/port number (1-12)")] int port,
[Description("true = ON, false = OFF")] bool on)
{
if (!TryResolve(ip, port, out var pdu, out var error)) return Err(error);
bool ok = on ? pdu.SetPortOn(port) : pdu.SetPortOff(port);
if (!ok) return Err($"SNMP set failed for {ip} port {port}");
return Ok(new { ip, port, state = on ? "on" : "off" });
}
[McpServerTool, Description("Read a single outlet's state, current (mA) and power (W).")]
public static Task pdu_get_port(
[Description("PDU IP address")] string ip,
[Description("Outlet/port number (1-12)")] int port)
{
if (!TryResolve(ip, port, out var pdu, out var error)) return Err(error);
return Ok(PortSnapshot(pdu, port));
}
[McpServerTool, Description("Read the state, current (mA) and power (W) of all outlets on the PDU.")]
public static Task pdu_status(
[Description("PDU IP address")] string ip)
{
if (!PduRegistry.Instance.TryGet(ip, out var pdu))
return Err($"PDU {ip} not connected. Call pdu_connect first.");
var ports = new List