-
Notifications
You must be signed in to change notification settings - Fork 526
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
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.