Skip to main content
Glama

Godot 4 Runtime MCP Server

by MingHuiLiu
ARCHITECTURE_V5.1.md10.2 kB
# Godot MCP v5.1 架构说明 - 主动记录 vs 被动查询 ## 🔄 架构改进 ### v5.0 → v5.1 核心变化 **v5.0 架构** (旧): - MCP 工具主动发起 "开始监听" 请求 - Godot 插件被动响应,临时记录信号 - 需要 AI 提前知道要监听什么 **v5.1 架构** (新): - Godot 插件启动时自动开始全局监听 - 自动记录所有信号到缓冲区/文件 - MCP 工具只负责查询历史数据 - AI 可以随时查询,无需提前准备 --- ## 🎯 设计理念 ### 问题场景 **AI Agent 的困境**: ``` AI: 我想知道玩家在过去 5 分钟内死了几次 但我无法提前知道需要监听 "died" 信号 游戏已经运行了,现在开始监听已经晚了 ``` **解决方案**: ``` Godot 插件在游戏启动时就开始记录所有信号 AI 随时可以查询历史数据: "给我过去 5 分钟内,包含 'Player' 节点的 'died' 信号" ``` --- ## 📋 职责划分 ### Godot 插件职责 (主动维护) #### 1. 信号系统 ```csharp // 游戏启动时自动执行 _Ready() { StartGlobalSignalMonitoring(); // 监听所有节点的所有信号 InitializeSignalEventsFile(); // 重置信号事件文件 } // 自动记录信号事件 RecordSignalEvent(node, signalName, args) { // 添加到缓冲区 _signalEventsBuffer.Add(event); // 缓冲区满时溢出到文件 if (_signalEventsBuffer.Count > 5000) { WriteSignalEventToFile(oldEvent); } } ``` **文件格式** (`user://mcp_signal_events.txt`): ``` === MCP 信号事件 - 启动时间: 2024-01-01 10:00:00 === [2024-01-01 10:05:23.456] /root/Game/Player (CharacterBody2D) :: died [] [2024-01-01 10:08:15.789] /root/Game/Player (CharacterBody2D) :: died [] [2024-01-01 10:15:30.123] /root/Game/Player (CharacterBody2D) :: health_changed [75, 100] ``` #### 2. 日志系统 ```csharp // 环形缓冲区 LinkedList<LogEntry> _logBuffer; // 最近 1000 条 // 自动记录日志 LogInfo(message) { AddLog("info", message); GD.Print(message); } AddLog(level, message) { // 添加到缓冲区 _logBuffer.AddLast(entry); // 缓冲区满时溢出到文件 if (_logBuffer.Count > 1000) { WriteLogToFile(oldEntry); } } ``` **文件格式** (`user://mcp_logs.txt`): ``` === MCP 日志 - 启动时间: 2024-01-01 10:00:00 === [2024-01-01 10:05:23] [INFO] 游戏开始 [2024-01-01 10:05:24] [WARNING] 内存使用过高 [2024-01-01 10:05:25] [ERROR] 加载资源失败 ``` --- ### MCP 服务器职责 (被动查询) #### 1. 信号查询工具 **get_signal_events** - 查询历史信号事件 ```csharp [McpServerTool] public async Task<string> GetSignalEvents( int count = 50, string? nodePath = null, // 部分匹配 string? signalName = null, long? startTime = null, // Unix 时间戳 long? endTime = null) { // 发送查询请求到 Godot // Godot 从缓冲区 + 文件中查找 return await _godotClient.GetSignalEventsAsync(...); } ``` **查询示例**: ```json // 查询游戏开始后 5 分钟内玩家死亡次数 { "nodePath": "Player", "signalName": "died", "startTime": 1735689600, "endTime": 1735689900, "count": 1000 } ``` **start_signal_monitoring** - 设置过滤器 (可选) ```csharp [McpServerTool] public async Task<string> StartSignalMonitoring( string? signalName = null) // 仅记录特定信号 { // 设置过滤器,减少记录量 // 默认记录所有信号 } ``` #### 2. 日志查询工具 **get_logs_filtered** - 查询历史日志 ```csharp [McpServerTool] public async Task<string> GetLogsFiltered( string? level = null, string? messagePattern = null, long? startTime = null, long? endTime = null, int maxCount = 100) { // Godot 从缓冲区 + 文件中查找 } ``` --- ## 🔍 查询流程 ### 信号事件查询流程 ``` AI Agent 请求 ↓ MCP Server (GetSignalEvents 工具) ↓ HTTP POST /get_signal_events Godot Plugin ↓ ReadAllSignalEvents() { 1. 从内存缓冲区读取 (最近 5000 条) 2. 从文件读取 (更早的事件) 3. 合并 + 排序 4. 应用过滤条件: - nodePath (部分匹配) - signalName (精确匹配) - startTime / endTime (时间范围) 5. 返回最近 N 条 } ↓ 返回 JSON 响应 ↓ AI Agent 分析 ``` --- ## 📊 数据流示例 ### 场景: AI 调查玩家死亡原因 **时间线**: ``` 10:00:00 - 游戏启动 (Godot 自动开始记录) 10:05:23 - 玩家第一次死亡 (信号: Player.died) 10:08:15 - 玩家第二次死亡 (信号: Player.died) 10:10:00 - AI 介入调查 ``` **AI 查询**: ```json POST /get_signal_events { "nodePath": "Player", "signalName": "died", "count": 10 } ``` **Godot 处理**: 1. 从缓冲区查找 "Player" + "died" 事件 2. 从文件查找 (如果有) 3. 返回: ```json { "matchedEvents": 2, "events": [ { "timestamp": "2024-01-01 10:05:23.456", "unixTimestamp": 1735689923, "nodePath": "/root/Game/Player", "signalName": "died" }, { "timestamp": "2024-01-01 10:08:15.789", "unixTimestamp": 1735690095, "nodePath": "/root/Game/Player", "signalName": "died" } ] } ``` **AI 进一步查询死亡前的事件**: ```json POST /get_signal_events { "nodePath": "Player", "startTime": 1735689918, // 死亡前 5 秒 "endTime": 1735689923, // 死亡时刻 "count": 50 } ``` **分析**: ``` 10:05:18 - Player.health_changed [50, 100] 10:05:20 - Player.health_changed [25, 50] 10:05:22 - Player.health_changed [0, 25] 10:05:23 - Player.died [] ``` 结论: 玩家在 5 秒内连续受到伤害导致死亡 --- ## 💾 数据持久化策略 ### 环形缓冲区机制 **信号事件**: - 内存: LinkedList (最近 5000 个) - 文件: user://mcp_signal_events.txt (更早的事件) - 每次启动重置文件 **日志**: - 内存: LinkedList (最近 1000 条) - 文件: user://mcp_logs.txt (更早的日志) - 每次启动重置文件 **优势**: 1. ✅ 快速访问最近数据 (内存) 2. ✅ 完整历史记录 (文件) 3. ✅ 内存可控 (环形缓冲区) 4. ✅ 无需手动管理 (自动溢出) 5. ✅ 每次启动干净 (避免文件膨胀) --- ## 🎯 关键优势 ### 1. AI Agent 无需提前准备 ``` 旧方式: AI: "我要监听玩家死亡事件" → start_monitoring [等待事件发生] AI: "查询事件" → get_events 新方式: [游戏已运行 10 分钟] AI: "查询过去 10 分钟内玩家死亡事件" → get_events ✅ 直接得到历史数据 ``` ### 2. 支持时间范围查询 ``` AI: "游戏开始后前 5 分钟发生了什么?" → startTime = 游戏启动时间 → endTime = 启动时间 + 300 秒 ``` ### 3. 部分匹配查询 ``` AI: "查询包含 Player 的节点" → nodePath = "Player" → 匹配: /root/Game/Player, /root/UI/PlayerUI, etc. ``` ### 4. 自动管理,无泄漏 - 环形缓冲区自动淘汰旧数据 - 每次启动重置文件 - 无需手动清理 --- ## 🔧 实现细节 ### Godot 插件核心代码 ```csharp public partial class McpClient : Node { // 信号事件缓冲区 private readonly List<SignalEvent> _signalEventsBuffer = new(); private const int MaxSignalEventsBufferSize = 5000; public override void _Ready() { // 初始化文件 InitializeSignalEventsFile(); // 启动全局监听 StartGlobalSignalMonitoring(); } private void StartGlobalSignalMonitoring() { // 连接场景树的 node_added 信号 GetTree().NodeAdded += OnNodeAddedToTree; // 监听已存在的节点 MonitorExistingNodes(GetTree().Root); } private void OnNodeAddedToTree(Node node) { // 为所有信号添加监听 foreach (var signal in node.GetSignalList()) { node.Connect(signalName, Callable.From(() => RecordSignalEvent(node, signalName, null))); } } 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()).ToList() ?? new() }; _signalEventsBuffer.Add(evt); // 缓冲区满时溢出 if (_signalEventsBuffer.Count > MaxSignalEventsBufferSize) { WriteSignalEventToFile(_signalEventsBuffer[0]); _signalEventsBuffer.RemoveAt(0); } } } ``` --- ## 📈 性能影响 ### 内存使用 - 信号缓冲区: ~5000 events × 200 bytes = 1 MB - 日志缓冲区: ~1000 logs × 150 bytes = 150 KB - **总计**: ~1.2 MB (可忽略) ### CPU 开销 - 信号记录: < 0.1 ms per event - 文件写入 (溢出时): < 5 ms - 对游戏性能影响: **可忽略** ### 磁盘使用 - 信号事件文件: 取决于游戏长度 (通常 < 10 MB) - 日志文件: 通常 < 1 MB - 每次启动重置,不会累积 --- ## 🎓 使用建议 ### AI Agent 最佳实践 **场景 1: 调试当前问题** ``` 1. 直接查询最近事件 (无需时间范围) 2. 逐步缩小范围 (添加 nodePath, signalName 过滤) 3. 分析事件序列 ``` **场景 2: 分析历史问题** ``` 1. 确定问题发生的时间范围 2. 使用 startTime/endTime 精确查询 3. 查看问题前后的事件 ``` **场景 3: 性能分析** ``` 1. 查询特定时间段的所有信号 2. 统计信号触发频率 3. 找到异常频繁的信号 ``` --- ## ✅ 总结 **v5.1 架构优势**: 1. ✅ **AI 友好**: 无需提前准备,随时查询历史 2. ✅ **完整记录**: 游戏启动后的所有信号和日志 3. ✅ **灵活查询**: 时间范围 + 节点 + 信号多维度过滤 4. ✅ **自动管理**: 环形缓冲区 + 文件溢出,无需手动维护 5. ✅ **性能友好**: 内存和 CPU 开销可忽略 **适用场景**: - 🐛 事后调试 (游戏已运行一段时间) - 📊 性能分析 (查询特定时间段) - 🔍 事件追踪 (查询信号触发顺序) - 💾 历史回溯 (分析过去发生的事情)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MingHuiLiu/godot4-runtime-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server