139 lines
4.3 KiB
C#
139 lines
4.3 KiB
C#
|
|
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
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// Registry for all MCP command handlers via reflection.
|
||
|
|
/// </summary>
|
||
|
|
public static class CommandRegistry
|
||
|
|
{
|
||
|
|
private static readonly Dictionary<string, Func<JObject, object>> _handlers = new();
|
||
|
|
private static bool _initialized = false;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Initialize and auto-discover all tools marked with [McpForUnityTool]
|
||
|
|
/// </summary>
|
||
|
|
public static void Initialize()
|
||
|
|
{
|
||
|
|
if (_initialized) return;
|
||
|
|
|
||
|
|
AutoDiscoverTools();
|
||
|
|
_initialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Convert PascalCase or camelCase to snake_case
|
||
|
|
/// </summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Auto-discover all types with [McpForUnityTool] attribute
|
||
|
|
/// </summary>
|
||
|
|
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<McpForUnityToolAttribute>() != 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<McpForUnityToolAttribute>();
|
||
|
|
|
||
|
|
// 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<JObject, object>)Delegate.CreateDelegate(
|
||
|
|
typeof(Func<JObject, object>),
|
||
|
|
method
|
||
|
|
);
|
||
|
|
_handlers[commandName] = handler;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
McpLog.Error($"Failed to register tool {type.Name}: {ex.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Get a command handler by name
|
||
|
|
/// </summary>
|
||
|
|
public static Func<JObject, object> GetHandler(string commandName)
|
||
|
|
{
|
||
|
|
if (!_handlers.TryGetValue(commandName, out var handler))
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
$"Unknown or unsupported command type: {commandName}"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return handler;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|