节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
3.91 KiB
Stardust
using System.Text.Json;
using NewLife;
using Stardust;

using Stardust.Data.Platform;

namespace Stardust.Web.Mcp.Actions.Apps;

/// <summary>向应用下发命令。通过StarFactory.SendAppCommandAsync异步下发,返回执行结果</summary>
public class AppSendCommandAction : McpActionBase
{
    private readonly StarFactory _starFactory;

    /// <summary>构造函数注入StarFactory</summary>
    public AppSendCommandAction(StarFactory starFactory) => _starFactory = starFactory;

    /// <summary>动作名</summary>
    public override String Name => "app_send_command";

    /// <summary>动作描述</summary>
    public override String Description => "向指定应用下发命令(异步执行)。需要Token已授权该应用。支持指定客户端实例(clientId)或广播给应用所有实例。";

    /// <summary>所属模块</summary>
    public override McpModuleType Module => McpModuleType.App;

    /// <summary>所需资源授权。框架层校验app_id在授权范围</summary>
    public override ResourceRequirement? RequiredResource => new()
    {
        Type = McpResourceType.App.ToWireName(),
        Field = "app_id",
    };

    /// <summary>输入参数JSON Schema</summary>
    public override JsonElement InputSchema
    {
        get
        {
            var json = """
            {
              "type": "object",
              "properties": {
                "app_id": {"type": "integer", "description": "应用ID"},
                "client_id": {"type": "string", "description": "可选,目标客户端实例标识(IP@进程)。不传则广播给应用所有实例"},
                "command": {"type": "string", "description": "命令名,如 app/start / app/stop / app/restart / 截屏 等"},
                "args": {"type": "string", "description": "可选,命令参数"},
                "expire": {"type": "integer", "description": "可选,命令过期时间(秒),默认3600"},
                "timeout": {"type": "integer", "description": "可选,等待回复超时(秒),默认5"}
              },
              "required": ["app_id", "command"]
            }
            """;
            using var doc = JsonDocument.Parse(json);
            return doc.RootElement.Clone();
        }
    }

    /// <summary>调用动作</summary>
    public override async Task<Object> InvokeAsync(JsonElement @params, McpContext context)
    {
        var appId = GetInt32(@params, "app_id");
        if (appId <= 0) throw new McpException(-32602, "Invalid params: app_id must be a positive integer");

        var command = GetString(@params, "command");
        if (command.IsNullOrEmpty()) throw new McpException(-32602, "Invalid params: command is empty");

        var clientId = GetString(@params, "client_id");
        var args = GetString(@params, "args");
        var expire = GetInt32(@params, "expire"); if (expire <= 0) expire = 3600;
        var timeout = GetInt32(@params, "timeout"); if (timeout <= 0) timeout = 5;

        // 根据app_id查找应用(SendAppCommandAsync的appId参数实际是应用Name/Code)
        var app = Stardust.Data.App.FindById(appId);
        if (app == null) throw new McpException(-32601, $"App not found: id={appId}");
        if (app.Name.IsNullOrEmpty()) throw new McpException(-32603, $"App has no Name: id={appId}");
        if (!app.Enable) throw new McpException(-32603, $"App is disabled: id={appId}");

        // 下发命令:SendAppCommandAsync 的第一个参数是应用名(code),不是数字ID
        var reply = await _starFactory.SendAppCommandAsync(app.Name, clientId, command, args, 0, expire, timeout);

        return new
        {
            app_id = appId,
            app_name = app.Name,
            display_name = app.DisplayName,
            client_id = clientId,
            command,
            args,
            expire,
            timeout,
            reply = reply?.Data,
            status = reply?.Status.ToString(),
        };
    }
}