using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; namespace MCPForUnity.Editor.Tools { /// /// Registry for all MCP command handlers via reflection. /// public static class CommandRegistry { private static readonly Dictionary> _handlers = new(); private static bool _initialized = false; /// /// Initialize and auto-discover all tools marked with [McpForUnityTool] /// public static void Initialize() { if (_initialized) return; AutoDiscoverTools(); _initialized = true; } /// /// Convert PascalCase or camelCase to snake_case /// private static string ToSnakeCase(string name) { if (string.IsNullOrEmpty(name)) return name; // Insert underscore before uppercase letters (except first) var s1 = Regex.Replace(name, "(.)([A-Z][a-z]+)", "$1_$2"); var s2 = Regex.Replace(s1, "([a-z0-9])([A-Z])", "$1_$2"); return s2.ToLower(); } /// /// Auto-discover all types with [McpForUnityTool] attribute /// private static void AutoDiscoverTools() { try { var toolTypes = AppDomain.CurrentDomain.GetAssemblies() .Where(a => !a.IsDynamic) .SelectMany(a => { try { return a.GetTypes(); } catch { return new Type[0]; } }) .Where(t => t.GetCustomAttribute() != null); foreach (var type in toolTypes) { RegisterToolType(type); } McpLog.Info($"Auto-discovered {_handlers.Count} tools"); } catch (Exception ex) { McpLog.Error($"Failed to auto-discover MCP tools: {ex.Message}"); } } private static void RegisterToolType(Type type) { var attr = type.GetCustomAttribute(); // Get command name (explicit or auto-generated) string commandName = attr.CommandName; if (string.IsNullOrEmpty(commandName)) { commandName = ToSnakeCase(type.Name); } // Check for duplicate command names if (_handlers.ContainsKey(commandName)) { McpLog.Warn( $"Duplicate command name '{commandName}' detected. " + $"Tool {type.Name} will override previously registered handler." ); } // Find HandleCommand method var method = type.GetMethod( "HandleCommand", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(JObject) }, null ); if (method == null) { McpLog.Warn( $"MCP tool {type.Name} is marked with [McpForUnityTool] " + $"but has no public static HandleCommand(JObject) method" ); return; } try { var handler = (Func)Delegate.CreateDelegate( typeof(Func), method ); _handlers[commandName] = handler; } catch (Exception ex) { McpLog.Error($"Failed to register tool {type.Name}: {ex.Message}"); } } /// /// Get a command handler by name /// public static Func GetHandler(string commandName) { if (!_handlers.TryGetValue(commandName, out var handler)) { throw new InvalidOperationException( $"Unknown or unsupported command type: {commandName}" ); } return handler; } } }