using ModelContextProtocol.Server;
using Plugin;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.Extensions.Logging;
using IoTGateway.Model;
using MQTTnet.Protocol;
using MQTTnet;
using Newtonsoft.Json;
using System.Threading.Tasks;
using System;
using MQTTnet.Extensions.ManagedClient;
using PluginInterface;
namespace IoTGateway.MCP
{
/// <summary>
/// 操作结果响应类
/// </summary>
public class OperationResult
{
/// <summary>
/// 操作是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 详细消息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 创建成功的响应
/// </summary>
/// <param name="message">成功消息</param>
/// <returns>操作结果</returns>
public static OperationResult CreateSuccess(string message)
{
return new OperationResult { Success = true, Message = message };
}
/// <summary>
/// 创建失败的响应
/// </summary>
/// <param name="message">错误消息</param>
/// <returns>操作结果</returns>
public static OperationResult CreateError(string message)
{
return new OperationResult { Success = false, Message = message };
}
}
public class VariableInfo
{
public object Value { get; set; }
public bool IsWritable { get; set; }
public string ProtectType { get; set; }
public string Expressions { get; set; }
}
[McpServerToolType]
public sealed class DeviceTool
{
private readonly ILogger _logger;
private readonly DeviceService _deviceService;
private readonly IManagedMqttClient _mqttClient;
public DeviceTool(
DeviceService deviceService,
IManagedMqttClient mqttClient,
ILogger<DeviceTool> logger)
{
_deviceService = deviceService;
_mqttClient = mqttClient;
_logger = logger;
// 记录工具类被创建,用于调试
_logger.LogInformation("DeviceTool created successfully");
}
// 设备列表项结构体
public class DeviceListItem
{
public string Name { get; set; } // 设备实例名
public bool IsTypeName { get; set; } = false; // 固定为 false,表明是实例名
public string Type { get; set; } // 设备类型(驱动名)
public string InstanceId { get; set; }
public string NameHint { get; set; } = "这是设备的实际名称,用户提到的设备名称就是设备本身,请直接使用此名称,不要猜测它是别的什么"; // 提示大模型直接使用设备名称
}
/// <summary>
/// Get the list of sub-devices with meta info.
/// </summary>
/// <returns></returns>
[McpServerTool(Name = "DevicesList"), Description("Get a list of all available device instances.")]
public IEnumerable<DeviceListItem> DevicesList()
{
_logger.LogInformation("DevicesList method called");
var devices = _deviceService.DeviceThreads.Select(x => new DeviceListItem
{
Name = x.Device.DeviceName,
IsTypeName = false, //给大模型看的,防止将设备名称被大模型误判为设备类型导致其他问题
Type = x.Device.Driver?.DriverName ?? "Unknown",
InstanceId = x.Device.ID.ToString(),
NameHint = "这是设备的实际名称,用户提到的设备名称就是设备本身,请直接使用此名称,不要猜测它是别的什么"
}).ToList();
_logger.LogInformation($"DevicesList returned {devices.Count} devices: {string.Join(", ", devices.Select(d => d.Name))}");
return devices;
}
/// <summary>
/// Get the current connection status of the sub-device.
/// </summary>
/// <param name="deviceName"></param>
/// <returns></returns>
[McpServerTool, Description("Get the current connection status of the sub-device.")]
public bool? GetDeviceStatus(
[Description("name of device")] string deviceName
)
{
_logger.LogInformation($"GetDeviceStatus method called for device: {deviceName}");
var status = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Driver.IsConnected;
_logger.LogInformation($"GetDeviceStatus returned: {status}");
return status;
}
/// <summary>
/// Get the current value of a variable of a sub-device.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
[McpServerTool, Description("Get all variables for a specific device.")]
public Dictionary<string, VariableInfo> GetDeviceVariables(
[Description("name of device")] string deviceName
)
{
_logger.LogInformation($"GetDeviceVariables method called for device: {deviceName}");
var device = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Device;
if (device == null)
return new Dictionary<string, VariableInfo>();
var variables = device.DeviceVariables.ToDictionary(
x => x.Name,
x => new VariableInfo
{
Value = x.CookedValue,
IsWritable = x.ProtectType != ProtectTypeEnum.ReadOnly,
ProtectType = x.ProtectType.ToString(),
Expressions = x.Expressions
});
_logger.LogInformation($"GetDeviceVariables returned {variables?.Count ?? 0} variables");
return variables;
}
/// <summary>
/// Get the current value of a variable of a sub-device.
/// </summary>
/// <param name="deviceName"></param>
/// <param name="variableName"></param>
/// <returns></returns>
[McpServerTool, Description("Gets a single variable's details from a device.")]
public VariableInfo GetDeviceVariable(
[Description("name of device")] string deviceName,
[Description("name of variable")] string variableName)
{
_logger.LogInformation($"GetDeviceVariable method called for device: {deviceName}, variable: {variableName}");
var variable = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Device
.DeviceVariables.FirstOrDefault(x => x.Name == variableName);
if (variable == null)
return null;
var result = new VariableInfo
{
Value = variable.CookedValue,
IsWritable = variable.ProtectType != ProtectTypeEnum.ReadOnly,
ProtectType = variable.ProtectType.ToString(),
Expressions = variable.Expressions
};
_logger.LogInformation($"GetDeviceVariable returned: {JsonConvert.SerializeObject(result)}");
return result;
}
/// <summary>
/// Set the value of a variable of a sub-device. This function sends the raw value directly.
/// </summary>
/// <param name="deviceName">设备名称</param>
/// <param name="variableName">变量名称</param>
/// <param name="value">The raw value to set. The system does not apply any expressions to this value before sending.</param>
/// <returns>设置是否成功及详细信息</returns>
[McpServerTool, Description("Set the value of a variable of a sub-device.")]
public async Task<OperationResult> SetDeviceVariable(
[Description("name of device")] string deviceName,
[Description("name of variable")] string variableName,
[Description("The raw value to set")] object value)
{
_logger.LogInformation($"SetDeviceVariable method called for device: {deviceName}, variable: {variableName}, value: {value}");
try
{
var deviceThread = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName);
if (deviceThread == null)
{
var errorResult = OperationResult.CreateError($"Device '{deviceName}' not found");
_logger.LogWarning($"SetDeviceVariable failed: {errorResult.Message}");
return errorResult;
}
var deviceVariable = deviceThread.Device.DeviceVariables.FirstOrDefault(x => x.Name == variableName);
if (deviceVariable == null || deviceVariable.ProtectType == ProtectTypeEnum.ReadOnly)
{
var errorResult = OperationResult.CreateError($"Variable '{variableName}' not found or is read-only");
_logger.LogWarning($"SetDeviceVariable failed: {errorResult.Message}");
return errorResult;
}
// 创建RPC请求
var rpcRequest = new RpcRequest
{
RequestId = Guid.NewGuid().ToString(),
DeviceName = deviceName,
Method = "write",
Params = new Dictionary<string, object> { { variableName, value } }
};
// 触发RPC请求执行
deviceThread.MyMqttClient_OnExcRpc(this, rpcRequest);
// 等待一小段时间让RPC请求处理
await Task.Delay(100);
var successResult = OperationResult.CreateSuccess($"Successfully set {deviceName}.{variableName} = {value}");
_logger.LogInformation($"SetDeviceVariable succeeded: {successResult.Message}");
return successResult;
}
catch (Exception ex)
{
var errorResult = OperationResult.CreateError($"Failed to set variable: {ex.Message}");
_logger.LogError(ex, $"SetDeviceVariable failed: {errorResult.Message}");
return errorResult;
}
}
/// <summary>
/// Set multiple variable values of a sub-device at once.
/// </summary>
/// <param name="deviceName">设备名称</param>
/// <param name="variables">变量名和值的字典</param>
/// <returns>设置是否成功及详细信息</returns>
[McpServerTool, Description("Set multiple variable values of a sub-device at once.")]
public async Task<OperationResult> SetDeviceVariables(
[Description("name of device")] string deviceName,
[Description("dictionary of variable names and values")] Dictionary<string, object> variables)
{
_logger.LogInformation($"SetDeviceVariables method called for device: {deviceName}, variables: {JsonConvert.SerializeObject(variables)}");
try
{
var deviceThread = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName);
if (deviceThread == null)
{
var errorResult = OperationResult.CreateError($"Device '{deviceName}' not found");
_logger.LogWarning($"SetDeviceVariables failed: {errorResult.Message}");
return errorResult;
}
// 过滤只保留可写的变量
var writeParams = new Dictionary<string, object>();
var skippedVariables = new List<string>();
foreach (var variable in variables)
{
var deviceVariable = deviceThread.Device.DeviceVariables.FirstOrDefault(x => x.Name == variable.Key);
if (deviceVariable != null && deviceVariable.ProtectType != ProtectTypeEnum.ReadOnly)
{
writeParams.Add(variable.Key, variable.Value);
}
else
{
skippedVariables.Add(variable.Key);
}
}
if (writeParams.Count == 0)
{
var errorResult = OperationResult.CreateError($"No writable variables found among: {string.Join(", ", variables.Keys)}");
_logger.LogWarning($"SetDeviceVariables failed: {errorResult.Message}");
return errorResult;
}
// 创建RPC请求
var rpcRequest = new RpcRequest
{
RequestId = Guid.NewGuid().ToString(),
DeviceName = deviceName,
Method = "write",
Params = writeParams
};
// 触发RPC请求执行
deviceThread.MyMqttClient_OnExcRpc(this, rpcRequest);
// 等待一小段时间让RPC请求处理
await Task.Delay(100);
var successMessage = $"Successfully set {writeParams.Count} variables for device {deviceName}: {string.Join(", ", writeParams.Keys)}";
if (skippedVariables.Count > 0)
{
successMessage += $". Skipped {skippedVariables.Count} read-only or non-existent variables: {string.Join(", ", skippedVariables)}";
}
var successResult = OperationResult.CreateSuccess(successMessage);
_logger.LogInformation($"SetDeviceVariables succeeded: {successResult.Message}");
return successResult;
}
catch (Exception ex)
{
var errorResult = OperationResult.CreateError($"Failed to set variables: {ex.Message}");
_logger.LogError(ex, $"SetDeviceVariables failed: {errorResult.Message}");
return errorResult;
}
}
}
}