using System.Text.Json;
using NewLife;
using Stardust.Data.Configs;
namespace Stardust.Mcp.Tools.Config;
public class ConfigListTool : McpToolBase
{
public override String Name => "stardust_config_list";
public override String Description => "List configuration items of one application. Returns keys, versions and enable state, never values.";
public override McpRiskLevel Risk => McpRiskLevel.L0;
public override String ScopeDomain => "config";
public override JsonElement InputSchema => JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
app_id = new { type = "integer", description = "应用编å·" },
},
required = new[] { "app_id" },
});
public override Task<Object> InvokeAsync(JsonElement args, McpContext ctx)
{
var app = McpGuard.App(ctx, GetInt32(args, "app_id"));
// å•应用定ä½ç”¨ FindByAppId。MVC/WebApi AppConfigController.Search 在 appId 上先 FindAllByAppId(没有则æ’å…¥ï¼‰ï¼Œå† AppConfig.Searchï¼›MCP åªè¯»ï¼Œä¸æ’入。
var cfg = AppConfig.FindByAppId(app.Id);
if (cfg == null)
return Task.FromResult<Object>(McpResult.Ok($"{app.Name} 还没有é…ç½®", new { records = Array.Empty<Object>() }));
// 定ä½åŽå†æ ¸å¯¹é¡¹ç›®ã€‚ä¸ç¬¦æ—¶ä¸èƒ½æŠŠè¯¥é…置的å称写进摘è¦ã€‚
if (cfg.ProjectId != ctx.ProjectId)
return Task.FromResult<Object>(McpResult.Ok("还没有é…ç½®", new { records = Array.Empty<Object>() }));
// é…置项与 Web/WebApi ConfigDataController.Search 相åŒï¼šç©ºé”®ã€ç©ºä½œç”¨åŸŸã€æ—¶é—´ MinValueã€ä¸æŒ‰å…³é”®å—。
var rows = ConfigData.Search(cfg.Id, "", "", DateTime.MinValue, DateTime.MinValue, null, null)
.Select(e => new { e.Id, e.Key, e.Version, e.Enable, secret = McpGuard.Secret(e.Key) }).ToArray();
return Task.FromResult<Object>(McpResult.Ok($"{app.Name} é…ç½® {rows.Length} 项", new { config_id = cfg.Id, version = cfg.Version, records = rows }));
}
}
public class ConfigGetTool : McpToolBase
{
public override String Name => "stardust_config_get";
public override String Description => "Read one configuration value. Sensitive keys are automatically masked as *** based on StarServerSetting.SensitiveKeywords.";
public override McpRiskLevel Risk => McpRiskLevel.L0;
public override String ScopeDomain => "config";
public override JsonElement InputSchema => JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
config_id = new { type = "integer", description = "é…置编å·" },
key = new { type = "string", description = "é…置键,å¯ç©ºè¡¨ç¤ºæŸ¥å…¨éƒ¨" },
},
required = new[] { "config_id" },
});
public override Task<Object> InvokeAsync(JsonElement args, McpContext ctx)
{
var cfg = McpGuard.Config(ctx, GetInt32(args, "config_id"));
// 与 MVC/WebApi ConfigDataController.Search ä¸€æ ·èµ°ä½œç”¨åŸŸä¸º string çš„é‡è½½ã€‚null ä¼šåŒæ—¶åŒ¹é… string 与 bool?。
var key = GetString(args, "key") ?? "";
var rows = ConfigData.Search(cfg.Id, key, "", DateTime.MinValue, DateTime.MinValue, null, null)
.Select(e => new
{
e.Key,
value = McpGuard.Secret(e.Key) ? "***" : e.Value,
masked = McpGuard.Secret(e.Key),
e.Version,
}).ToArray();
return Task.FromResult<Object>(McpResult.Ok($"é…ç½® {cfg.Name} è¯»å– {rows.Length} é¡¹ï¼ˆæ•æ„Ÿå€¼å·²è„±æ•)", new { records = rows }));
}
}
public class ConfigSetTool : McpToolBase
{
public override String Name => "stardust_config_set";
public override String Description => "Write configuration draft keys. Does not publish; publishing must be done manually in the web console.";
public override McpRiskLevel Risk => McpRiskLevel.L1;
public override String ScopeDomain => "config";
public override JsonElement InputSchema => JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
config_id = new { type = "integer", description = "é…置编å·" },
configs = new { type = "object", additionalProperties = new { type = "string" }, description = "键值对è‰ç¨¿" },
},
required = new[] { "config_id", "configs" },
});
public override async Task<Object> InvokeAsync(JsonElement args, McpContext ctx)
{
var cfg = McpGuard.Config(ctx, GetInt32(args, "config_id"));
if (!args.TryGetProperty("configs", out var configs) || configs.ValueKind != JsonValueKind.Object)
throw new McpException(-32602, "æ— æ•ˆå‚æ•°ï¼šconfigs 必须是对象。");
var map = new Dictionary<String, Object>(StringComparer.OrdinalIgnoreCase);
foreach (var p in configs.EnumerateObject()) map[p.Name] = p.Value.ValueKind == JsonValueKind.String ? p.Value.GetString() : p.Value.ToString();
var ops = ctx.Services.GetService(typeof(IMcpOperations)) as IMcpOperations
?? throw new McpException(-32000, "宿主未注册 MCP 写æ“作适é…器。");
var count = await ops.SetConfigAsync(cfg.Id, map);
return McpResult.Ok($"已写入 {count} 项è‰ç¨¿ï¼Œå°šæœªå‘布。å‘布需在管ç†åŽå°æ‰‹åЍæ“作", new { config_id = cfg.Id, written = count },
null, "è‰ç¨¿ä¸ä¼šè‡ªåŠ¨ç”Ÿæ•ˆï¼›å¦‚éœ€æ’¤é”€ï¼Œç›´æŽ¥è¦†ç›–åŽŸå€¼æˆ–åœ¨ç®¡ç†åŽå°æ”¾å¼ƒè‰ç¨¿");
}
}
public class ConfigRollbackTool : McpToolBase
{
public override String Name => "stardust_config_rollback";
public override String Description => "Rollback configuration to a publish history id. Only publish history can be rolled back.";
public override McpRiskLevel Risk => McpRiskLevel.L1;
public override String ScopeDomain => "config";
public override JsonElement InputSchema => JsonSerializer.SerializeToElement(new
{
type = "object",
properties = new
{
history_id = new { type = "integer", description = "å‘布历å²ç¼–å·" },
},
required = new[] { "history_id" },
});
public override async Task<Object> InvokeAsync(JsonElement args, McpContext ctx)
{
var historyId = GetInt32(args, "history_id");
if (historyId <= 0) throw new McpException(-32602, "æ— æ•ˆå‚æ•°ï¼šç¼ºå°‘ history_id。");
var history = ConfigHistory.FindById(historyId) ?? throw new McpException(-32602, "é…置历å²ä¸å˜åœ¨ã€‚");
McpGuard.Config(ctx, history.ConfigId);
if (!history.Action.EqualIgnoreCase("Publish")) throw new McpException(-32602, "åªæœ‰å‘布历å²å¯ä»¥å›žæ»šã€‚");
// å¤ç”¨å®¿ä¸» ConfigService.Rollback(å«ç‰ˆæœ¬æ¢å¤ã€Rollback 历å²ä¸Ž config/publish 通知),MCP ä¸ç›´æŽ¥æ“作实体
var ops = ctx.Services.GetService(typeof(IMcpOperations)) as IMcpOperations
?? throw new McpException(-32000, "宿主未注册 MCP 写æ“作适é…器。");
var rs = await ops.RollbackConfigAsync(history.Id);
var summary = $"é…置已从 v{rs.Before} 回滚到 v{rs.Version},é‡å‘布 {rs.Republished} 项,通知 {rs.Notified} 个应用";
if (!rs.NotifyError.IsNullOrEmpty()) summary += $";通知失败:{rs.NotifyError}";
return McpResult.Ok(summary,
new { before = rs.Before, version = rs.Version, republished = rs.Republished, notified = rs.Notified, notify_error = rs.NotifyError },
null, "æ¤æ“作本身å³å›žæ»šï¼›å¦‚需å†å›žæ»šåˆ°æ›´æ—©ç‰ˆæœ¬ï¼Œè°ƒç”¨ stardust_config_rollback 选择对应历å²è®°å½•");
}
}
|