using Godot;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Linq;
using FileAccess = Godot.FileAccess;
/// <summary>
/// MCP HTTP API 服务器 v4.1 - 线程安全版本,所有场景树操作在主线程执行
/// </summary>
public partial class McpClient : Node
{
private HttpListener? _httpListener;
private bool _isRunning = false;
private const string ApiUrl = "http://127.0.0.1:7777/";
// 增强日志系统 - 环形缓冲区
private readonly LinkedList<LogEntry> _logBuffer = new();
private const int MaxLogBufferSize = 1000;
private const string LogFilePath = "user://mcp_logs.txt";
private readonly List<LogEntry> _logs = new(); // 保留向后兼容
private bool _autoLogGDPrint = true; // 自动记录 GD.Print
// 信号监听系统 - 全局监听
private readonly List<SignalEvent> _signalEventsBuffer = new();
private const int MaxSignalEventsBufferSize = 5000;
private const string SignalEventsFilePath = "user://mcp_signal_events.txt";
private bool _isGlobalSignalMonitoring = false;
private readonly HashSet<string> _monitoredSignals = new(); // 监听的信号名称过滤
// 线程安全的请求队列
private readonly Queue<PendingRequest> _requestQueue = new();
private readonly object _queueLock = new();
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
public override void _Ready()
{
// 重置日志和信号事件文件
InitializeLogFile();
InitializeSignalEventsFile();
// 开启全局信号监听
StartGlobalSignalMonitoring();
GD.Print("=".PadRight(60, '='));
GD.Print("[MCP] Godot MCP v5.0 - 完整调试系统");
GD.Print("=".PadRight(60, '='));
GD.Print("[MCP] ✓ 全局信号监听已启动");
GD.Print("[MCP] ✓ 日志自动记录已启动");
StartServer();
}
public override void _Process(double delta)
{
// 在主线程处理所有待处理的请求
ProcessPendingRequests();
// 自动捕获控制台输出 (通过重定向 GD.Print 实现需要引擎支持)
// 这里我们使用定期轮询的方式
}
private async void StartServer()
{
try
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add(ApiUrl);
_httpListener.Start();
_isRunning = true;
GD.Print($"[MCP] ✓ 监听: {ApiUrl}");
GD.Print("[MCP] ✓ 48 个独立HTTP端点已就绪 (场景树+信号+日志)");
_ = Task.Run(HandleRequestsAsync);
}
catch (Exception ex)
{
GD.PrintErr($"[MCP] ✗ 启动失败: {ex.Message}");
}
}
private async Task HandleRequestsAsync()
{
while (_isRunning && _httpListener != null)
{
try
{
var context = await _httpListener.GetContextAsync();
_ = Task.Run(() => ProcessRequestAsync(context));
}
catch { }
}
}
private async Task ProcessRequestAsync(HttpListenerContext context)
{
try
{
if (context.Request.HttpMethod != "POST")
{
context.Response.StatusCode = 405;
context.Response.Close();
return;
}
var path = context.Request.Url?.AbsolutePath ?? "/";
GD.Print($"[MCP] {path}");
using var reader = new System.IO.StreamReader(context.Request.InputStream);
var body = await reader.ReadToEndAsync();
// 创建待处理请求并等待主线程处理
var pendingRequest = new PendingRequest
{
Path = path,
Body = body,
CompletionSource = new TaskCompletionSource<ApiResponse>()
};
lock (_queueLock)
{
_requestQueue.Enqueue(pendingRequest);
}
// 等待主线程处理完成
var response = await pendingRequest.CompletionSource.Task;
var json = JsonSerializer.Serialize(response, JsonOptions);
var bytes = Encoding.UTF8.GetBytes(json);
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.ContentLength64 = bytes.Length;
await context.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length);
context.Response.Close();
}
catch (Exception ex)
{
GD.PrintErr($"[MCP] Error: {ex.Message}");
context.Response.StatusCode = 500;
context.Response.Close();
}
}
/// <summary>
/// 在主线程处理所有待处理的请求
/// </summary>
private void ProcessPendingRequests()
{
while (true)
{
PendingRequest? request = null;
lock (_queueLock)
{
if (_requestQueue.Count > 0)
{
request = _requestQueue.Dequeue();
}
}
if (request == null)
break;
try
{
var response = RouteRequest(request.Path, request.Body);
request.CompletionSource.SetResult(response);
}
catch (Exception ex)
{
request.CompletionSource.SetResult(ErrorResponse(ex.Message));
}
}
}
/// <summary>
/// 根据 HTTP 路径路由到对应方法
/// </summary>
private ApiResponse RouteRequest(string path, string body)
{
try
{
return path switch
{
"/get_scene_tree" => GetSceneTree(Deserialize<SceneTreeRequest>(body)),
"/get_node_info" => GetNodeInfo(Deserialize<NodePathRequest>(body)),
"/create_node" => CreateNode(Deserialize<CreateNodeRequest>(body)),
"/delete_node" => DeleteNode(Deserialize<NodePathRequest>(body)),
"/load_scene" => LoadScene(Deserialize<ScenePathRequest>(body)),
"/get_property" => GetProperty(Deserialize<PropertyRequest>(body)),
"/set_property" => SetProperty(Deserialize<SetPropertyRequest>(body)),
"/list_properties" => ListProperties(Deserialize<NodePathRequest>(body)),
"/call_method" => CallMethod(Deserialize<CallMethodRequest>(body)),
"/list_methods" => ListMethods(Deserialize<NodePathRequest>(body)),
"/execute_csharp" => ExecuteCSharp(Deserialize<CodeRequest>(body)),
"/get_global_variables" => GetGlobalVariables(),
"/get_resource_info" => GetResourceInfo(Deserialize<ResourcePathRequest>(body)),
"/list_resources" => ListResources(Deserialize<ListResourcesRequest>(body)),
"/load_resource" => LoadResource(Deserialize<ResourcePathRequest>(body)),
"/get_performance_stats" => GetPerformanceStats(),
"/get_logs" => GetLogs(Deserialize<LogsRequest>(body)),
"/take_screenshot" => TakeScreenshot(Deserialize<ScreenshotRequest>(body)),
"/get_time" => GetTime(),
"/get_node_children" => GetNodeChildren(Deserialize<NodePathRequest>(body)),
"/get_node_parent" => GetNodeParent(Deserialize<NodePathRequest>(body)),
"/find_nodes_by_type" => FindNodesByType(Deserialize<FindNodesRequest>(body)),
"/find_nodes_by_name" => FindNodesByName(Deserialize<FindNodesRequest>(body)),
"/find_nodes_by_group" => FindNodesByGroup(Deserialize<FindNodesRequest>(body)),
"/get_node_ancestors" => GetNodeAncestors(Deserialize<AncestorsRequest>(body)),
"/get_scene_tree_stats" => GetSceneTreeStats(Deserialize<NodePathRequest>(body)),
"/node_exists" => NodeExists(Deserialize<NodePathRequest>(body)),
"/get_node_subtree" => GetNodeSubtree(Deserialize<SubtreeRequest>(body)),
"/search_nodes" => SearchNodes(Deserialize<FindNodesRequest>(body)),
"/get_node_context" => GetNodeContext(Deserialize<NodeContextRequest>(body)),
// 简化场景树
"/get_scene_tree_simple" => GetSceneTreeSimple(Deserialize<SimpleTreeRequest>(body)),
// 信号系统
"/get_node_signals" => GetNodeSignals(Deserialize<NodePathRequest>(body)),
"/get_signal_connections" => GetSignalConnections(Deserialize<SignalConnectionRequest>(body)),
"/connect_signal" => ConnectSignal(Deserialize<SignalConnectionRequest>(body)),
"/disconnect_signal" => DisconnectSignal(Deserialize<SignalConnectionRequest>(body)),
"/emit_signal" => EmitSignal(Deserialize<SignalEmitRequest>(body)),
"/start_signal_monitoring" => StartSignalMonitoring(Deserialize<SignalMonitorRequest>(body)),
"/stop_signal_monitoring" => StopSignalMonitoring(),
"/get_signal_events" => GetSignalEvents(Deserialize<SignalEventQueryRequest>(body)),
"/clear_signal_events" => ClearSignalEvents(),
// 增强日志系统
"/get_logs_filtered" => GetLogsFiltered(Deserialize<LogFilterRequest>(body)),
"/get_log_stats" => GetLogStats(),
"/export_logs" => ExportLogs(Deserialize<LogExportRequest>(body)),
"/clear_logs" => ClearLogs(),
"/add_custom_log" => AddCustomLog(Deserialize<CustomLogRequest>(body)),
_ => ErrorResponse($"未知端点: {path}")
};
}
catch (Exception ex)
{
GD.PrintErr($"[MCP] 路由错误: {ex.Message}");
return ErrorResponse(ex.Message);
}
}
private T Deserialize<T>(string json) where T : new()
{
try
{
return JsonSerializer.Deserialize<T>(json, JsonOptions) ?? new T();
}
catch
{
return new T();
}
}
// ========== 场景树方法 (获取游戏运行时信息) ==========
private ApiResponse GetSceneTree(SceneTreeRequest req)
{
var root = GetTree().Root; // 现在安全了,在主线程执行
var tree = BuildTree(root, req.IncludeProperties);
return Ok(tree);
}
private ApiResponse GetNodeInfo(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"找不到节点: {req.NodePath}");
return Ok(new NodeInfo
{
Name = node.Name,
Type = node.GetType().Name,
Path = node.GetPath().ToString(),
Children = node.GetChildren().Select(c => c.Name.ToString()).ToList(),
Properties = GetProps(node),
Methods = GetMethods(node),
Signals = GetSignals(node)
});
}
private ApiResponse CreateNode(CreateNodeRequest req)
{
var parent = GetNodeOrNull(req.ParentPath);
if (parent == null) return Err($"父节点不存在: {req.ParentPath}");
var node = CreateByType(req.NodeType);
if (node == null) return Err($"无法创建类型: {req.NodeType}");
node.Name = req.NodeName;
parent.AddChild(node);
return Ok(new { path = node.GetPath().ToString() });
}
private ApiResponse DeleteNode(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
node.QueueFree();
return Ok("已删除");
}
private ApiResponse LoadScene(ScenePathRequest req)
{
GetTree().CallDeferred("change_scene_to_file", req.ScenePath);
return Ok($"加载中: {req.ScenePath}");
}
// ========== 属性方法 (游戏运行时属性) ==========
private ApiResponse GetProperty(PropertyRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var value = node.Get(req.PropertyName); // 获取运行时属性值
return Ok(new { value = Conv(value) });
}
private ApiResponse SetProperty(SetPropertyRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
node.Set(req.PropertyName, ToVar(req.Value)); // 设置运行时属性
return Ok($"已设置: {req.PropertyName}");
}
private ApiResponse ListProperties(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
return Ok(GetProps(node));
}
// ========== 方法调用 (运行时方法) ==========
private ApiResponse CallMethod(CallMethodRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var args = new Godot.Collections.Array();
req.Args?.ForEach(a => args.Add(ToVar(a)));
var result = node.Call(req.MethodName, args); // 运行时调用
return Ok(Conv(result));
}
private ApiResponse ListMethods(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
return Ok(GetMethods(node));
}
// ========== 脚本和资源 ==========
private ApiResponse ExecuteCSharp(CodeRequest req) => Ok("需要 Roslyn");
private ApiResponse GetGlobalVariables()
{
return Ok(GetTree().Root.GetChildren().ToDictionary(
c => c.Name.ToString(),
c => (object)new { type = c.GetType().Name, path = c.GetPath().ToString() }
));
}
private ApiResponse GetResourceInfo(ResourcePathRequest req)
{
var r = GD.Load(req.ResourcePath);
return r == null ? Err("资源不存在") : Ok(new { type = r.GetType().Name, path = r.ResourcePath });
}
private ApiResponse ListResources(ListResourcesRequest req)
{
var list = new List<string>();
var dir = DirAccess.Open(req.Path);
if (dir != null)
{
dir.ListDirBegin();
for (var f = dir.GetNext(); f != ""; f = dir.GetNext())
if (!dir.CurrentIsDir()) list.Add(f);
dir.ListDirEnd();
}
return Ok(list);
}
private ApiResponse LoadResource(ResourcePathRequest req)
{
var r = GD.Load(req.ResourcePath);
return r == null ? Err("加载失败") : Ok(new { type = r.GetType().Name });
}
// ========== 调试方法 (运行时统计) ==========
private ApiResponse GetPerformanceStats()
{
return Ok(new
{
fps = Engine.GetFramesPerSecond(), // 实时 FPS
processTime = Performance.GetMonitor(Performance.Monitor.TimeProcess),
physicsTime = Performance.GetMonitor(Performance.Monitor.TimePhysicsProcess),
memoryUsage = Performance.GetMonitor(Performance.Monitor.MemoryStatic), // 实时内存
nodeCount = Performance.GetMonitor(Performance.Monitor.ObjectNodeCount), // 实时节点数
drawCalls = Performance.GetMonitor(Performance.Monitor.RenderTotalDrawCallsInFrame)
});
}
private ApiResponse GetLogs(LogsRequest req) => Ok(_logs.TakeLast(req.Count));
private ApiResponse TakeScreenshot(ScreenshotRequest req)
{
var path = req.SavePath ?? "user://screenshot.png";
var img = GetViewport().GetTexture().GetImage(); // 当前画面截图
img.SavePng(path);
return Ok(new { path });
}
private ApiResponse GetTime() => Ok(new
{
unix = Time.GetUnixTimeFromSystem(),
datetime = DateTime.Now.ToString("s"),
ticks = Time.GetTicksMsec()
});
// ========== 扩展场景树查询方法 ==========
private ApiResponse GetNodeChildren(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var children = node.GetChildren().Select(c => new
{
name = c.Name.ToString(),
type = c.GetType().Name,
path = c.GetPath().ToString()
}).ToList();
return Ok(children);
}
private ApiResponse GetNodeParent(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var parent = node.GetParent();
if (parent == null) return Ok(null);
return Ok(new
{
name = parent.Name.ToString(),
type = parent.GetType().Name,
path = parent.GetPath().ToString()
});
}
private ApiResponse FindNodesByType(FindNodesRequest req)
{
var root = GetNodeOrNull(req.RootPath);
if (root == null) return Err($"根节点不存在: {req.RootPath}");
var results = new List<object>();
FindNodesByTypeRecursive(root, req.NodeType ?? "", results);
return Ok(new { count = results.Count, nodes = results });
}
private void FindNodesByTypeRecursive(Node node, string targetType, List<object> results)
{
if (node.GetType().Name == targetType)
{
results.Add(new
{
name = node.Name.ToString(),
type = node.GetType().Name,
path = node.GetPath().ToString()
});
}
foreach (Node child in node.GetChildren())
{
FindNodesByTypeRecursive(child, targetType, results);
}
}
private ApiResponse FindNodesByName(FindNodesRequest req)
{
var root = GetNodeOrNull(req.RootPath);
if (root == null) return Err($"根节点不存在: {req.RootPath}");
var pattern = req.NamePattern ?? "";
var results = new List<object>();
var comparison = req.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
FindNodesByNameRecursive(root, pattern, results, req.ExactMatch, comparison, req.MaxResults);
return Ok(new { count = results.Count, nodes = results, truncated = results.Count >= req.MaxResults });
}
private void FindNodesByNameRecursive(Node node, string pattern, List<object> results, bool exactMatch, StringComparison comparison, int maxResults)
{
if (results.Count >= maxResults) return;
var nodeName = node.Name.ToString();
bool matches = exactMatch
? nodeName.Equals(pattern, comparison)
: nodeName.Contains(pattern, comparison);
if (matches)
{
results.Add(new
{
name = nodeName,
type = node.GetType().Name,
path = node.GetPath().ToString()
});
}
foreach (Node child in node.GetChildren())
{
if (results.Count >= maxResults) break;
FindNodesByNameRecursive(child, pattern, results, exactMatch, comparison, maxResults);
}
}
private ApiResponse FindNodesByGroup(FindNodesRequest req)
{
var root = GetNodeOrNull(req.RootPath);
if (root == null) return Err($"根节点不存在: {req.RootPath}");
var groupName = req.GroupName ?? "";
var nodes = GetTree().GetNodesInGroup(groupName);
var results = nodes.Select(n => new
{
name = n.Name.ToString(),
type = n.GetType().Name,
path = n.GetPath().ToString(),
groups = n.GetGroups().Select(g => g.ToString()).ToList()
}).Take(req.MaxResults).ToList();
return Ok(new { count = results.Count, nodes = results, groupName });
}
private ApiResponse GetNodeAncestors(AncestorsRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var ancestors = new List<object>();
var current = node.GetParent();
var level = 0;
while (current != null && (req.Levels < 0 || level < req.Levels))
{
var ancestorInfo = new Dictionary<string, object>
{
["level"] = level + 1,
["name"] = current.Name.ToString(),
["type"] = current.GetType().Name,
["path"] = current.GetPath().ToString()
};
if (req.IncludeSiblings)
{
var parent = current.GetParent();
if (parent != null)
{
var siblings = parent.GetChildren()
.Where(c => c.GetPath() != current.GetPath())
.Select(c => new
{
name = c.Name.ToString(),
type = c.GetType().Name,
path = c.GetPath().ToString()
}).ToList();
ancestorInfo["siblings"] = siblings;
ancestorInfo["siblingCount"] = siblings.Count;
}
}
ancestors.Add(ancestorInfo);
current = current.GetParent();
level++;
}
return Ok(new
{
nodePath = req.NodePath,
ancestorCount = ancestors.Count,
ancestors
});
}
private ApiResponse GetSceneTreeStats(NodePathRequest req)
{
var root = GetNodeOrNull(req.NodePath);
if (root == null) return Err($"节点不存在: {req.NodePath}");
var stats = new Dictionary<string, int>();
var groups = new Dictionary<string, int>();
var totalNodes = 0;
var maxDepth = 0;
CollectStatsRecursive(root, stats, groups, ref totalNodes, ref maxDepth, 0);
return Ok(new
{
totalNodes,
maxDepth,
nodesByType = stats.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value),
nodesByGroup = groups.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value),
rootPath = req.NodePath
});
}
private void CollectStatsRecursive(Node node, Dictionary<string, int> stats, Dictionary<string, int> groups, ref int total, ref int maxDepth, int currentDepth)
{
total++;
if (currentDepth > maxDepth) maxDepth = currentDepth;
var type = node.GetType().Name;
stats[type] = stats.GetValueOrDefault(type, 0) + 1;
foreach (var group in node.GetGroups())
{
var groupName = group.ToString();
groups[groupName] = groups.GetValueOrDefault(groupName, 0) + 1;
}
foreach (Node child in node.GetChildren())
{
CollectStatsRecursive(child, stats, groups, ref total, ref maxDepth, currentDepth + 1);
}
}
private ApiResponse NodeExists(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
return Ok(new { exists = node != null, path = req.NodePath });
}
private ApiResponse GetNodeSubtree(SubtreeRequest req)
{
var root = GetNodeOrNull(req.NodePath);
if (root == null) return Err($"节点不存在: {req.NodePath}");
var tree = BuildTreeWithDepth(root, req.MaxDepth, 0, req.IncludeProperties);
return Ok(tree);
}
private Dictionary<string, object> BuildTreeWithDepth(Node n, int maxDepth, int currentDepth, bool includeProperties)
{
var d = new Dictionary<string, object>
{
["name"] = n.Name,
["type"] = n.GetType().Name,
["path"] = n.GetPath().ToString(),
["depth"] = currentDepth
};
if (includeProperties)
{
d["properties"] = GetProps(n);
}
if (maxDepth < 0 || currentDepth < maxDepth)
{
d["children"] = n.GetChildren()
.Select(c => BuildTreeWithDepth(c, maxDepth, currentDepth + 1, includeProperties))
.ToList();
}
else
{
d["children"] = new List<object>();
d["hasChildren"] = n.GetChildCount() > 0;
d["childCount"] = n.GetChildCount();
}
return d;
}
private ApiResponse SearchNodes(FindNodesRequest req)
{
var root = GetNodeOrNull(req.RootPath);
if (root == null) return Err($"根节点不存在: {req.RootPath}");
var results = new List<object>();
SearchNodesRecursive(root, req, results);
return Ok(new
{
count = results.Count,
nodes = results,
truncated = results.Count >= req.MaxResults,
criteria = new
{
namePattern = req.NamePattern,
nodeType = req.NodeType,
groupName = req.GroupName
}
});
}
private void SearchNodesRecursive(Node node, FindNodesRequest req, List<object> results)
{
if (results.Count >= req.MaxResults) return;
bool matches = true;
// 检查名称匹配
if (!string.IsNullOrEmpty(req.NamePattern))
{
var comparison = req.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
var nodeName = node.Name.ToString();
matches = req.ExactMatch
? nodeName.Equals(req.NamePattern, comparison)
: nodeName.Contains(req.NamePattern, comparison);
}
// 检查类型匹配
if (matches && !string.IsNullOrEmpty(req.NodeType))
{
matches = node.GetType().Name == req.NodeType;
}
// 检查组匹配
if (matches && !string.IsNullOrEmpty(req.GroupName))
{
matches = node.IsInGroup(req.GroupName);
}
if (matches)
{
results.Add(new
{
name = node.Name.ToString(),
type = node.GetType().Name,
path = node.GetPath().ToString(),
groups = node.GetGroups().Select(g => g.ToString()).ToList()
});
}
foreach (Node child in node.GetChildren())
{
if (results.Count >= req.MaxResults) break;
SearchNodesRecursive(child, req, results);
}
}
private ApiResponse GetNodeContext(NodeContextRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var context = new Dictionary<string, object>
{
["node"] = new
{
name = node.Name.ToString(),
type = node.GetType().Name,
path = node.GetPath().ToString(),
groups = node.GetGroups().Select(g => g.ToString()).ToList()
}
};
if (req.IncludeParent)
{
var parent = node.GetParent();
context["parent"] = parent != null ? new
{
name = parent.Name.ToString(),
type = parent.GetType().Name,
path = parent.GetPath().ToString()
} : null;
}
if (req.IncludeSiblings)
{
var parent = node.GetParent();
if (parent != null)
{
var siblings = parent.GetChildren()
.Where(c => c.GetPath() != node.GetPath())
.Select(c => new
{
name = c.Name.ToString(),
type = c.GetType().Name,
path = c.GetPath().ToString()
}).ToList();
context["siblings"] = siblings;
context["siblingCount"] = siblings.Count;
}
}
if (req.IncludeChildren)
{
var children = node.GetChildren().Select(c => new
{
name = c.Name.ToString(),
type = c.GetType().Name,
path = c.GetPath().ToString(),
childCount = c.GetChildCount()
}).ToList();
context["children"] = children;
context["childCount"] = children.Count;
}
return Ok(context);
}
// ========== 简化场景树 ==========
private ApiResponse GetSceneTreeSimple(SimpleTreeRequest req)
{
var root = GetNodeOrNull(req.RootPath);
if (root == null) return Err($"节点不存在: {req.RootPath}");
var tree = BuildSimpleTree(root, req.MaxDepth, 0);
return Ok(tree);
}
private Dictionary<string, object> BuildSimpleTree(Node node, int maxDepth, int currentDepth)
{
var result = new Dictionary<string, object>
{
["name"] = node.Name.ToString(),
["type"] = node.GetType().Name
};
if (currentDepth < maxDepth)
{
result["children"] = node.GetChildren()
.Select(c => BuildSimpleTree(c, maxDepth, currentDepth + 1))
.ToList();
}
return result;
}
// ========== 信号系统 ==========
private ApiResponse GetNodeSignals(NodePathRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
var signals = node.GetSignalList()
.Select(s => (Godot.Collections.Dictionary)s)
.Select(s => new
{
name = s["name"].AsString(),
args = ((Godot.Collections.Array)s["args"])
.Select(a => ((Godot.Collections.Dictionary)a)["name"].AsString())
.ToList()
})
.ToList();
return Ok(new { nodePath = req.NodePath, signals });
}
private ApiResponse GetSignalConnections(SignalConnectionRequest req)
{
var node = GetNodeOrNull(req.SourceNodePath);
if (node == null) return Err($"节点不存在: {req.SourceNodePath}");
var connections = node.GetSignalConnectionList(req.SignalName)
.Select(c => (Godot.Collections.Dictionary)c)
.Select(c => new
{
signal = c["signal"].AsString(),
callable = c["callable"].ToString(),
target = c.ContainsKey("target") ? c["target"].ToString() : null
})
.ToList();
return Ok(new { signal = req.SignalName, connections });
}
private ApiResponse ConnectSignal(SignalConnectionRequest req)
{
var source = GetNodeOrNull(req.SourceNodePath);
if (source == null) return Err($"源节点不存在: {req.SourceNodePath}");
if (string.IsNullOrEmpty(req.TargetNodePath) || string.IsNullOrEmpty(req.TargetMethod))
return Err("需要目标节点路径和方法名");
var target = GetNodeOrNull(req.TargetNodePath);
if (target == null) return Err($"目标节点不存在: {req.TargetNodePath}");
try
{
var callable = new Callable(target, req.TargetMethod);
source.Connect(req.SignalName, callable);
LogInfo($"信号已连接: {req.SourceNodePath}.{req.SignalName} → {req.TargetNodePath}.{req.TargetMethod}");
return Ok(new
{
connected = true,
source = req.SourceNodePath,
signal = req.SignalName,
target = req.TargetNodePath,
method = req.TargetMethod
});
}
catch (Exception ex)
{
return Err($"连接信号失败: {ex.Message}");
}
}
private ApiResponse DisconnectSignal(SignalConnectionRequest req)
{
var source = GetNodeOrNull(req.SourceNodePath);
if (source == null) return Err($"源节点不存在: {req.SourceNodePath}");
if (string.IsNullOrEmpty(req.TargetNodePath) || string.IsNullOrEmpty(req.TargetMethod))
return Err("需要目标节点路径和方法名");
var target = GetNodeOrNull(req.TargetNodePath);
if (target == null) return Err($"目标节点不存在: {req.TargetNodePath}");
try
{
var callable = new Callable(target, req.TargetMethod);
source.Disconnect(req.SignalName, callable);
LogInfo($"信号已断开: {req.SourceNodePath}.{req.SignalName} ✗ {req.TargetNodePath}.{req.TargetMethod}");
return Ok(new
{
disconnected = true,
source = req.SourceNodePath,
signal = req.SignalName
});
}
catch (Exception ex)
{
return Err($"断开信号失败: {ex.Message}");
}
}
private ApiResponse EmitSignal(SignalEmitRequest req)
{
var node = GetNodeOrNull(req.NodePath);
if (node == null) return Err($"节点不存在: {req.NodePath}");
try
{
if (req.Args == null || req.Args.Count == 0)
{
node.EmitSignal(req.SignalName);
}
else
{
var variants = req.Args.Select(a => ToVar(a)).ToArray();
node.EmitSignal(req.SignalName, variants);
}
LogInfo($"信号已发射: {req.NodePath}.{req.SignalName}");
return Ok(new
{
emitted = true,
nodePath = req.NodePath,
signal = req.SignalName,
argCount = req.Args?.Count ?? 0
});
}
catch (Exception ex)
{
return Err($"发射信号失败: {ex.Message}");
}
}
private ApiResponse StartSignalMonitoring(SignalMonitorRequest req)
{
// 设置信号过滤 (为空则监听所有)
if (!string.IsNullOrEmpty(req.SignalName))
{
_monitoredSignals.Add(req.SignalName);
}
else
{
_monitoredSignals.Clear(); // 清空过滤器 = 监听所有
}
LogInfo($"信号监听过滤器已更新 - 监听信号: {(req.SignalName ?? "所有")}");
return Ok(new
{
monitoring = _isGlobalSignalMonitoring,
monitoredSignals = _monitoredSignals.Count > 0 ? _monitoredSignals.ToList() : new List<string> { "所有信号" },
currentEventsCount = _signalEventsBuffer.Count
});
}
private ApiResponse StopSignalMonitoring()
{
var eventCount = _signalEventsBuffer.Count;
LogInfo($"信号监听统计 - 共记录 {eventCount} 个事件 (缓冲区)");
return Ok(new
{
monitoring = _isGlobalSignalMonitoring,
note = "全局监听持续运行,可通过设置过滤器控制记录",
totalEvents = eventCount
});
}
private ApiResponse GetSignalEvents(SignalEventQueryRequest req)
{
// 从缓冲区和文件读取信号事件
var allEvents = ReadAllSignalEvents();
var query = allEvents.AsEnumerable();
// 按节点路径过滤
if (!string.IsNullOrEmpty(req.NodePath))
query = query.Where(e => e.NodePath.Contains(req.NodePath, StringComparison.OrdinalIgnoreCase));
// 按信号名称过滤
if (!string.IsNullOrEmpty(req.SignalName))
query = query.Where(e => e.SignalName.Equals(req.SignalName, StringComparison.OrdinalIgnoreCase));
// 按时间范围过滤
if (req.StartTime.HasValue)
{
var startTime = DateTimeOffset.FromUnixTimeSeconds(req.StartTime.Value).DateTime;
query = query.Where(e => e.Timestamp >= startTime);
}
if (req.EndTime.HasValue)
{
var endTime = DateTimeOffset.FromUnixTimeSeconds(req.EndTime.Value).DateTime;
query = query.Where(e => e.Timestamp <= endTime);
}
var events = query.TakeLast(req.Count).ToList();
return Ok(new
{
totalEvents = allEvents.Count,
matchedEvents = query.Count(),
returnedEvents = events.Count,
events = events.Select(e => new
{
timestamp = e.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"),
unixTimestamp = new DateTimeOffset(e.Timestamp).ToUnixTimeSeconds(),
nodePath = e.NodePath,
nodeType = e.NodeType,
signalName = e.SignalName,
args = e.Args
}).ToList()
});
}
private List<SignalEvent> ReadAllSignalEvents()
{
var events = new List<SignalEvent>(_signalEventsBuffer);
// 从文件读取历史事件
try
{
using var file = FileAccess.Open(SignalEventsFilePath, FileAccess.ModeFlags.Read);
if (file != null)
{
while (!file.EofReached())
{
var line = file.GetLine();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("===")) continue;
// 解析格式: [2024-01-01 12:34:56.789] /root/Main (Node2D) :: ready [arg1, arg2]
var match = System.Text.RegularExpressions.Regex.Match(
line,
@"\[([\d\-: .]+)\] (.+?) \((\w+)\) :: (\w+)(?: \[(.+?)\])?"
);
if (match.Success)
{
var argsStr = match.Groups[5].Value;
var args = string.IsNullOrEmpty(argsStr)
? new List<string>()
: argsStr.Split(", ").ToList();
events.Add(new SignalEvent
{
Timestamp = DateTime.Parse(match.Groups[1].Value),
NodePath = match.Groups[2].Value,
NodeType = match.Groups[3].Value,
SignalName = match.Groups[4].Value,
Args = args
});
}
}
}
}
catch { }
return events.OrderBy(e => e.Timestamp).ToList();
}
private ApiResponse ClearSignalEvents()
{
var bufferCount = _signalEventsBuffer.Count;
var fileEvents = ReadAllSignalEvents().Count - bufferCount;
_signalEventsBuffer.Clear();
InitializeSignalEventsFile();
LogInfo($"信号事件已清空 - 缓冲区: {bufferCount}, 文件: {fileEvents}");
return Ok(new
{
cleared = true,
bufferCleared = bufferCount,
fileReset = true
});
}
// ========== 增强日志系统 ==========
private void InitializeLogFile()
{
try
{
using var file = FileAccess.Open(LogFilePath, FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreString($"=== MCP 日志 - 启动时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===\n");
}
}
catch (Exception ex)
{
GD.PrintErr($"[MCP] 初始化日志文件失败: {ex.Message}");
}
}
private void InitializeSignalEventsFile()
{
try
{
using var file = FileAccess.Open(SignalEventsFilePath, FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreString($"=== MCP 信号事件 - 启动时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===\n");
}
}
catch (Exception ex)
{
GD.PrintErr($"[MCP] 初始化信号事件文件失败: {ex.Message}");
}
}
/// <summary>
/// 启动全局信号监听 - 自动记录所有节点的信号触发
/// </summary>
private void StartGlobalSignalMonitoring()
{
_isGlobalSignalMonitoring = true;
// 连接到场景树的 node_added 信号,自动监听新添加的节点
GetTree().NodeAdded += OnNodeAddedToTree;
// 监听已存在的节点
MonitorExistingNodes(GetTree().Root);
}
private void OnNodeAddedToTree(Node node)
{
if (!_isGlobalSignalMonitoring) return;
// 为新节点的所有信号添加监听
var signals = node.GetSignalList();
foreach (var signalDict in signals)
{
var dict = (Godot.Collections.Dictionary)signalDict;
var signalName = dict["name"].AsString();
// 检查是否需要监听此信号
if (_monitoredSignals.Count == 0 || _monitoredSignals.Contains(signalName))
{
try
{
node.Connect(signalName, Callable.From(() => RecordSignalEvent(node, signalName, null)));
}
catch
{
// 某些信号可能有参数,跳过
}
}
}
}
private void MonitorExistingNodes(Node root)
{
OnNodeAddedToTree(root);
foreach (Node child in root.GetChildren())
{
MonitorExistingNodes(child);
}
}
/// <summary>
/// 记录信号事件
/// </summary>
private void RecordSignalEvent(Node node, string signalName, object[]? args)
{
var evt = new SignalEvent
{
Timestamp = DateTime.Now,
NodePath = node.GetPath().ToString(),
NodeType = node.GetType().Name,
SignalName = signalName,
Args = args?.Select(a => a?.ToString() ?? "null").ToList() ?? new List<string>()
};
// 添加到缓冲区
_signalEventsBuffer.Add(evt);
// 如果缓冲区满了,写入最旧的事件到文件
if (_signalEventsBuffer.Count > MaxSignalEventsBufferSize)
{
var oldEvent = _signalEventsBuffer[0];
_signalEventsBuffer.RemoveAt(0);
WriteSignalEventToFile(oldEvent);
}
}
private void WriteSignalEventToFile(SignalEvent evt)
{
try
{
using var file = FileAccess.Open(SignalEventsFilePath, FileAccess.ModeFlags.ReadWrite);
if (file != null)
{
file.SeekEnd();
var argsStr = evt.Args.Count > 0 ? $" [{string.Join(", ", evt.Args)}]" : "";
file.StoreString($"[{evt.Timestamp:yyyy-MM-dd HH:mm:ss.fff}] {evt.NodePath} ({evt.NodeType}) :: {evt.SignalName}{argsStr}\n");
}
}
catch { }
}
private void LogInfo(string message)
{
AddLog("info", message);
GD.Print($"[MCP] {message}");
}
private void LogWarning(string message)
{
AddLog("warning", message);
GD.PushWarning($"[MCP] {message}");
}
private void LogError(string message)
{
AddLog("error", message);
GD.PrintErr($"[MCP] {message}");
}
private void AddLog(string level, string message)
{
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Level = level,
Message = message
};
// 添加到环形缓冲区
_logBuffer.AddLast(entry);
// 如果超过最大大小,移除最旧的并写入文件
if (_logBuffer.Count > MaxLogBufferSize)
{
var oldEntry = _logBuffer.First!.Value;
_logBuffer.RemoveFirst();
WriteLogToFile(oldEntry);
}
// 同时添加到旧的 _logs 列表以保持向后兼容
_logs.Add(entry);
}
private void WriteLogToFile(LogEntry entry)
{
try
{
using var file = FileAccess.Open(LogFilePath, FileAccess.ModeFlags.ReadWrite);
if (file != null)
{
file.SeekEnd();
file.StoreString($"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [{entry.Level.ToUpper()}] {entry.Message}\n");
}
}
catch { }
}
private ApiResponse GetLogsFiltered(LogFilterRequest req)
{
var allLogs = _logBuffer.Concat(ReadLogsFromFile()).ToList();
var query = allLogs.AsEnumerable();
if (!string.IsNullOrEmpty(req.Level))
query = query.Where(log => log.Level.Equals(req.Level, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(req.MessagePattern))
query = query.Where(log => log.Message.Contains(req.MessagePattern, StringComparison.OrdinalIgnoreCase));
if (req.StartTime.HasValue)
{
var startTime = DateTimeOffset.FromUnixTimeSeconds(req.StartTime.Value).DateTime;
query = query.Where(log => log.Timestamp >= startTime);
}
if (req.EndTime.HasValue)
{
var endTime = DateTimeOffset.FromUnixTimeSeconds(req.EndTime.Value).DateTime;
query = query.Where(log => log.Timestamp <= endTime);
}
var logs = query.TakeLast(req.MaxCount).ToList();
return Ok(new
{
totalMatched = logs.Count,
logs
});
}
private List<LogEntry> ReadLogsFromFile()
{
var logs = new List<LogEntry>();
try
{
using var file = FileAccess.Open(LogFilePath, FileAccess.ModeFlags.Read);
if (file != null)
{
while (!file.EofReached())
{
var line = file.GetLine();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("===")) continue;
var match = System.Text.RegularExpressions.Regex.Match(
line,
@"\[([\d\-: ]+)\] \[(\w+)\] (.+)"
);
if (match.Success)
{
logs.Add(new LogEntry
{
Timestamp = DateTime.Parse(match.Groups[1].Value),
Level = match.Groups[2].Value.ToLower(),
Message = match.Groups[3].Value
});
}
}
}
}
catch { }
return logs;
}
private ApiResponse GetLogStats()
{
var allLogs = _logBuffer.Concat(ReadLogsFromFile()).ToList();
var stats = new
{
totalLogs = allLogs.Count,
inBuffer = _logBuffer.Count,
inFile = allLogs.Count - _logBuffer.Count,
byLevel = allLogs.GroupBy(l => l.Level)
.ToDictionary(g => g.Key, g => g.Count()),
oldestLog = allLogs.Count > 0 ? allLogs.First().Timestamp : (DateTime?)null,
newestLog = allLogs.Count > 0 ? allLogs.Last().Timestamp : (DateTime?)null
};
return Ok(stats);
}
private ApiResponse ExportLogs(LogExportRequest req)
{
var filePath = req.FilePath ?? "user://logs_export.txt";
try
{
var allLogs = _logBuffer.Concat(ReadLogsFromFile()).ToList();
using var file = FileAccess.Open(filePath, FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreString($"=== MCP 日志导出 - {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===\n");
file.StoreString($"总计: {allLogs.Count} 条日志\n\n");
foreach (var log in allLogs)
{
file.StoreString($"[{log.Timestamp:yyyy-MM-dd HH:mm:ss}] [{log.Level.ToUpper()}] {log.Message}\n");
}
LogInfo($"日志已导出到: {filePath}");
return Ok(new
{
exported = true,
filePath,
logCount = allLogs.Count
});
}
return Err("无法打开导出文件");
}
catch (Exception ex)
{
return Err($"导出日志失败: {ex.Message}");
}
}
private ApiResponse ClearLogs()
{
var bufferCount = _logBuffer.Count;
var fileCount = ReadLogsFromFile().Count;
_logBuffer.Clear();
_logs.Clear();
InitializeLogFile();
LogInfo($"日志已清空 - 缓冲区: {bufferCount}, 文件: {fileCount}");
return Ok(new
{
cleared = true,
bufferCleared = bufferCount,
fileReset = true
});
}
private ApiResponse AddCustomLog(CustomLogRequest req)
{
AddLog(req.Level, req.Message);
return Ok(new
{
logged = true,
level = req.Level,
message = req.Message
});
}
// ========== 辅助方法 ==========
private Dictionary<string, object> BuildTree(Node n, bool props)
{
var d = new Dictionary<string, object>
{
["name"] = n.Name,
["type"] = n.GetType().Name,
["path"] = n.GetPath().ToString(),
["children"] = n.GetChildren().Select(c => BuildTree(c, props)).ToList()
};
if (props) d["properties"] = GetProps(n);
return d;
}
private Dictionary<string, object?> GetProps(Node n) =>
n.GetPropertyList()
.Select(p => (Godot.Collections.Dictionary)p)
.Where(p => !p["name"].AsString().StartsWith("_"))
.ToDictionary(p => p["name"].AsString(), p =>
{
try { return Conv(n.Get(p["name"].AsString())); }
catch { return null; }
});
private List<string> GetMethods(Node n) =>
n.GetMethodList()
.Select(m => ((Godot.Collections.Dictionary)m)["name"].AsString())
.Where(m => !m.StartsWith("_"))
.ToList();
private List<string> GetSignals(Node n) =>
n.GetSignalList()
.Select(s => ((Godot.Collections.Dictionary)s)["name"].AsString())
.ToList();
private Node? CreateByType(string t)
{
var type = Type.GetType($"Godot.{t}") ?? Type.GetType(t);
return type != null && typeof(Node).IsAssignableFrom(type)
? (Node?)Activator.CreateInstance(type)
: null;
}
private object? Conv(Variant v) => v.VariantType switch
{
Variant.Type.Nil => null,
Variant.Type.Bool => v.AsBool(),
Variant.Type.Int => v.AsInt64(),
Variant.Type.Float => v.AsDouble(),
Variant.Type.String => v.AsString(),
Variant.Type.Vector2 => new { x = v.AsVector2().X, y = v.AsVector2().Y },
Variant.Type.Vector3 => new { x = v.AsVector3().X, y = v.AsVector3().Y, z = v.AsVector3().Z },
_ => v.ToString()
};
private Variant ToVar(object? o) => o switch
{
null => default,
bool b => b,
int i => i,
long l => l,
float f => f,
double d => d,
string s => s,
JsonElement je => je.ValueKind switch
{
JsonValueKind.Null => default,
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Number => je.GetDouble(),
JsonValueKind.String => je.GetString() ?? "",
_ => je.ToString()
},
_ => o.ToString() ?? ""
};
private ApiResponse Ok(object? d) => new() { Success = true, Data = d };
private ApiResponse Err(string msg) => new() { Success = false, Error = msg };
private ApiResponse ErrorResponse(string msg) => new() { Success = false, Error = msg };
public override void _ExitTree()
{
_isRunning = false;
_httpListener?.Stop();
_httpListener?.Close();
GD.Print("[MCP] 已停止");
}
}
// ========== 强类型请求模型 ==========
public class SceneTreeRequest
{
[JsonPropertyName("includeProperties")]
public bool IncludeProperties { get; set; }
}
public class NodePathRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
}
public class CreateNodeRequest
{
[JsonPropertyName("parentPath")]
public string ParentPath { get; set; } = "";
[JsonPropertyName("nodeType")]
public string NodeType { get; set; } = "";
[JsonPropertyName("nodeName")]
public string NodeName { get; set; } = "";
}
public class ScenePathRequest
{
[JsonPropertyName("scenePath")]
public string ScenePath { get; set; } = "";
}
public class PropertyRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("propertyName")]
public string PropertyName { get; set; } = "";
}
public class SetPropertyRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("propertyName")]
public string PropertyName { get; set; } = "";
[JsonPropertyName("value")]
public object? Value { get; set; }
}
public class CallMethodRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("methodName")]
public string MethodName { get; set; } = "";
[JsonPropertyName("args")]
public List<object>? Args { get; set; }
}
public class CodeRequest
{
[JsonPropertyName("code")]
public string Code { get; set; } = "";
}
public class ResourcePathRequest
{
[JsonPropertyName("resourcePath")]
public string ResourcePath { get; set; } = "";
}
public class ListResourcesRequest
{
[JsonPropertyName("path")]
public string Path { get; set; } = "res://";
[JsonPropertyName("filter")]
public string? Filter { get; set; }
}
public class LogsRequest
{
[JsonPropertyName("count")]
public int Count { get; set; } = 50;
}
public class ScreenshotRequest
{
[JsonPropertyName("savePath")]
public string? SavePath { get; set; }
}
public class FindNodesRequest
{
[JsonPropertyName("nodeType")]
public string? NodeType { get; set; }
[JsonPropertyName("namePattern")]
public string? NamePattern { get; set; }
[JsonPropertyName("rootPath")]
public string RootPath { get; set; } = "/root";
[JsonPropertyName("caseSensitive")]
public bool CaseSensitive { get; set; } = false;
[JsonPropertyName("exactMatch")]
public bool ExactMatch { get; set; } = false;
[JsonPropertyName("groupName")]
public string? GroupName { get; set; }
[JsonPropertyName("maxResults")]
public int MaxResults { get; set; } = 50;
}
public class SubtreeRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("maxDepth")]
public int MaxDepth { get; set; } = 2;
[JsonPropertyName("includeProperties")]
public bool IncludeProperties { get; set; } = false;
}
public class AncestorsRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("levels")]
public int Levels { get; set; } = -1;
[JsonPropertyName("includeSiblings")]
public bool IncludeSiblings { get; set; } = false;
}
public class NodeContextRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("includeParent")]
public bool IncludeParent { get; set; } = true;
[JsonPropertyName("includeSiblings")]
public bool IncludeSiblings { get; set; } = true;
[JsonPropertyName("includeChildren")]
public bool IncludeChildren { get; set; } = true;
}
// ========== 响应模型 ==========
public class ApiResponse
{
[JsonPropertyName("success")]
public bool Success { get; set; }
[JsonPropertyName("data")]
public object? Data { get; set; }
[JsonPropertyName("error")]
public string? Error { get; set; }
}
public class NodeInfo
{
[JsonPropertyName("name")]
public string Name { get; set; } = "";
[JsonPropertyName("type")]
public string Type { get; set; } = "";
[JsonPropertyName("path")]
public string Path { get; set; } = "";
[JsonPropertyName("children")]
public List<string> Children { get; set; } = new();
[JsonPropertyName("properties")]
public Dictionary<string, object?> Properties { get; set; } = new();
[JsonPropertyName("methods")]
public List<string> Methods { get; set; } = new();
[JsonPropertyName("signals")]
public List<string> Signals { get; set; } = new();
}
// ========== 简化场景树请求 ==========
public class SimpleTreeRequest
{
[JsonPropertyName("rootPath")]
public string RootPath { get; set; } = "/root";
[JsonPropertyName("maxDepth")]
public int MaxDepth { get; set; } = 3;
}
// ========== 信号系统请求 ==========
public class SignalConnectionRequest
{
[JsonPropertyName("sourceNodePath")]
public string SourceNodePath { get; set; } = "";
[JsonPropertyName("signalName")]
public string SignalName { get; set; } = "";
[JsonPropertyName("targetNodePath")]
public string? TargetNodePath { get; set; }
[JsonPropertyName("targetMethod")]
public string? TargetMethod { get; set; }
}
public class SignalEmitRequest
{
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("signalName")]
public string SignalName { get; set; } = "";
[JsonPropertyName("args")]
public List<object>? Args { get; set; }
}
public class SignalMonitorRequest
{
[JsonPropertyName("nodePath")]
public string? NodePath { get; set; }
[JsonPropertyName("signalName")]
public string? SignalName { get; set; }
[JsonPropertyName("maxEvents")]
public int MaxEvents { get; set; } = 1000;
}
public class SignalEventQueryRequest
{
[JsonPropertyName("count")]
public int Count { get; set; } = 50;
[JsonPropertyName("nodePath")]
public string? NodePath { get; set; }
[JsonPropertyName("signalName")]
public string? SignalName { get; set; }
[JsonPropertyName("startTime")]
public long? StartTime { get; set; }
[JsonPropertyName("endTime")]
public long? EndTime { get; set; }
}
public class SignalEvent
{
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
[JsonPropertyName("nodePath")]
public string NodePath { get; set; } = "";
[JsonPropertyName("nodeType")]
public string NodeType { get; set; } = "";
[JsonPropertyName("signalName")]
public string SignalName { get; set; } = "";
[JsonPropertyName("args")]
public List<string> Args { get; set; } = new();
}
// ========== 增强日志系统请求 ==========
public class LogFilterRequest
{
[JsonPropertyName("level")]
public string? Level { get; set; }
[JsonPropertyName("messagePattern")]
public string? MessagePattern { get; set; }
[JsonPropertyName("startTime")]
public long? StartTime { get; set; }
[JsonPropertyName("endTime")]
public long? EndTime { get; set; }
[JsonPropertyName("maxCount")]
public int MaxCount { get; set; } = 100;
}
public class LogExportRequest
{
[JsonPropertyName("filePath")]
public string? FilePath { get; set; }
}
public class CustomLogRequest
{
[JsonPropertyName("message")]
public string Message { get; set; } = "";
[JsonPropertyName("level")]
public string Level { get; set; } = "info";
}
public class LogEntry
{
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
[JsonPropertyName("level")]
public string Level { get; set; } = "";
[JsonPropertyName("message")]
public string Message { get; set; } = "";
}
/// <summary>
/// 待处理的请求 (用于线程间通信)
/// </summary>
internal class PendingRequest
{
public string Path { get; set; } = "";
public string Body { get; set; } = "";
public TaskCompletionSource<ApiResponse> CompletionSource { get; set; } = null!;
}