- 
                Notifications
    You must be signed in to change notification settings 
- Fork 486
Make it easier to add tools #301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
            msanatan
  merged 31 commits into
  CoplayDev:main
from
msanatan:feature/auto-tool-discovery
  
      
      
   
  Oct 3, 2025 
      
    
      
        
          +2,894
        
        
          −2,739
        
        
          
        
      
    
  
  
     Merged
                    Changes from 19 commits
      Commits
    
    
            Show all changes
          
          
            31 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      391a0d9
              
                Add a decorate that wraps around the `mcp.tool` decorator.
              
              
                msanatan 5c74980
              
                Register tools that's defined in the tools folder
              
              
                msanatan 00ccf14
              
                Update Python tools to use new decorator
              
              
                msanatan 31ce85e
              
                Convert script_apply_edits tool
              
              
                msanatan 5f1ab98
              
                Convert last remaining tools with new decorator
              
              
                msanatan 0821309
              
                Create an attribute so we can identify tools via Reflection
              
              
                msanatan d036e36
              
                Add attribute to all C# tools
              
              
                msanatan c84069b
              
                Use reflection to load tools
              
              
                msanatan b7f2070
              
                Initialize command registry to load tools at startup
              
              
                msanatan 2e9aa06
              
                Update tests
              
              
                msanatan 084c27e
              
                Move Dev docs to docs folder
              
              
                msanatan bc5695e
              
                Add docs for adding custom tools
              
              
                msanatan f154e43
              
                Update function docs for Python decorator
              
              
                msanatan 1e13517
              
                Add working example of adding a screenshot tool
              
              
                msanatan bf6480c
              
                docs: update relative links in README files
              
              
                msanatan 2f87357
              
                docs: update telemetry documentation path reference
              
              
                msanatan 8173e01
              
                rename CursorHelp.md to docs/CURSOR_HELP.md
              
              
                msanatan d46b0e6
              
                docs: update CUSTOM_TOOLS.md with improved tool naming documentation …
              
              
                msanatan 7b5c156
              
                docs: restructure development documentation and add custom tools guide
              
              
                msanatan 5a65781
              
                Merge branch 'main' into feature/auto-tool-discovery
              
              
                msanatan 0d07efd
              
                docs: update developer documentation and add README links
              
              
                msanatan f79fda6
              
                feat(tools): enhance tool registration with wrapped function assignment
              
              
                msanatan d8fd19d
              
                Remove AI generated code that was never used...
              
              
                msanatan a2d76b6
              
                feat: Rebuild MCP server installation with embedded source
              
              
                msanatan 8e7b202
              
                Add the rebuild server step
              
              
                msanatan 1d5291e
              
                docs: clarify tool description field requirements and client compatib…
              
              
                msanatan 7db81f1
              
                fix: move initialization flag after tool discovery to prevent race co…
              
              
                msanatan 3d107e5
              
                refactor: remove redundant TryParseVersion overrides in platform dete…
              
              
                msanatan 4314e2a
              
                refactor: remove duplicate UV validation code from platform detectors
              
              
                msanatan fe13260
              
                Update UnityMcpBridge/Editor/Tools/CommandRegistry.cs
              
              
                msanatan 2af3413
              
                refactor: replace WriteToConfig reflection with direct McpConfigurati…
              
              
                msanatan File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,50 +1,129 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Text.RegularExpressions; | ||
| using MCPForUnity.Editor.Helpers; | ||
| using Newtonsoft.Json.Linq; | ||
| using MCPForUnity.Editor.Tools.MenuItems; | ||
| using MCPForUnity.Editor.Tools.Prefabs; | ||
|  | ||
| namespace MCPForUnity.Editor.Tools | ||
| { | ||
| /// <summary> | ||
| /// Registry for all MCP command handlers (Refactored Version) | ||
| /// Registry for all MCP command handlers via reflection. | ||
| /// </summary> | ||
| public static class CommandRegistry | ||
| { | ||
| // Maps command names (matching those called from Python via ctx.bridge.unity_editor.HandlerName) | ||
| // to the corresponding static HandleCommand method in the appropriate tool class. | ||
| private static readonly Dictionary<string, Func<JObject, object>> _handlers = new() | ||
| 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() | ||
| { | ||
| { "manage_script", ManageScript.HandleCommand }, | ||
| { "manage_scene", ManageScene.HandleCommand }, | ||
| { "manage_editor", ManageEditor.HandleCommand }, | ||
| { "manage_gameobject", ManageGameObject.HandleCommand }, | ||
| { "manage_asset", ManageAsset.HandleCommand }, | ||
| { "read_console", ReadConsole.HandleCommand }, | ||
| { "manage_menu_item", ManageMenuItem.HandleCommand }, | ||
| { "manage_shader", ManageShader.HandleCommand}, | ||
| { "manage_prefabs", ManagePrefabs.HandleCommand}, | ||
| }; | ||
| if (_initialized) return; | ||
| _initialized = true; | ||
|  | ||
| AutoDiscoverTools(); | ||
| } | ||
|  | ||
| /// <summary> | ||
| /// Gets a command handler by name. | ||
| /// Convert PascalCase or camelCase to snake_case | ||
| /// </summary> | ||
| /// <param name="commandName">Name of the command handler (e.g., "HandleManageAsset").</param> | ||
| /// <returns>The command handler function if found, null otherwise.</returns> | ||
| public static Func<JObject, object> GetHandler(string commandName) | ||
| private static string ToSnakeCase(string name) | ||
| { | ||
| if (!_handlers.TryGetValue(commandName, out var handler)) | ||
| 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 | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Unknown or unsupported command type: {commandName}"); | ||
| 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}"); | ||
| } | ||
| } | ||
|  | ||
| return handler; | ||
| 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); | ||
| } | ||
|  | ||
| // 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}"); | ||
| } | ||
| } | ||
|         
                  msanatan marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| public static void Add(string commandName, Func<JObject, object> handler) | ||
| /// <summary> | ||
| /// Get a command handler by name | ||
| /// </summary> | ||
| public static Func<JObject, object> GetHandler(string commandName) | ||
| { | ||
| _handlers.Add(commandName, handler); | ||
| if (!_handlers.TryGetValue(commandName, out var handler)) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"Unknown or unsupported command type: {commandName}" | ||
| ); | ||
| } | ||
| return handler; | ||
| } | ||
| } | ||
| } | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using System; | ||
|  | ||
| namespace MCPForUnity.Editor.Tools | ||
| { | ||
| /// <summary> | ||
| /// Marks a class as an MCP tool handler for auto-discovery. | ||
| /// The class must have a public static HandleCommand(JObject) method. | ||
| /// </summary> | ||
| [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] | ||
| public class McpForUnityToolAttribute : Attribute | ||
| { | ||
| /// <summary> | ||
| /// The command name used to route requests to this tool. | ||
| /// If not specified, defaults to the PascalCase class name converted to snake_case. | ||
| /// </summary> | ||
| public string CommandName { get; } | ||
|  | ||
| /// <summary> | ||
| /// Create an MCP tool attribute with auto-generated command name. | ||
| /// The command name will be derived from the class name (PascalCase → snake_case). | ||
| /// Example: ManageAsset → manage_asset | ||
| /// </summary> | ||
| public McpForUnityToolAttribute() | ||
| { | ||
| CommandName = null; // Will be auto-generated | ||
| } | ||
|  | ||
| /// <summary> | ||
| /// Create an MCP tool attribute with explicit command name. | ||
| /// </summary> | ||
| /// <param name="commandName">The command name (e.g., "manage_asset")</param> | ||
| public McpForUnityToolAttribute(string commandName) | ||
| { | ||
| CommandName = commandName; | ||
| } | ||
| } | ||
| } | 
        
          
          
            11 changes: 11 additions & 0 deletions
          
          11 
        
  UnityMcpBridge/Editor/Tools/McpForUnityToolAttribute.cs.meta
  
  
      
      
   
        
      
      
    Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
      
      Oops, something went wrong.
      
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.