Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 Oct 1, 2025
5c74980
Register tools that's defined in the tools folder
msanatan Oct 1, 2025
00ccf14
Update Python tools to use new decorator
msanatan Oct 1, 2025
31ce85e
Convert script_apply_edits tool
msanatan Oct 1, 2025
5f1ab98
Convert last remaining tools with new decorator
msanatan Oct 1, 2025
0821309
Create an attribute so we can identify tools via Reflection
msanatan Oct 1, 2025
d036e36
Add attribute to all C# tools
msanatan Oct 1, 2025
c84069b
Use reflection to load tools
msanatan Oct 2, 2025
b7f2070
Initialize command registry to load tools at startup
msanatan Oct 2, 2025
2e9aa06
Update tests
msanatan Oct 2, 2025
084c27e
Move Dev docs to docs folder
msanatan Oct 2, 2025
bc5695e
Add docs for adding custom tools
msanatan Oct 2, 2025
f154e43
Update function docs for Python decorator
msanatan Oct 2, 2025
1e13517
Add working example of adding a screenshot tool
msanatan Oct 2, 2025
bf6480c
docs: update relative links in README files
msanatan Oct 2, 2025
2f87357
docs: update telemetry documentation path reference
msanatan Oct 2, 2025
8173e01
rename CursorHelp.md to docs/CURSOR_HELP.md
msanatan Oct 2, 2025
d46b0e6
docs: update CUSTOM_TOOLS.md with improved tool naming documentation …
msanatan Oct 2, 2025
7b5c156
docs: restructure development documentation and add custom tools guide
msanatan Oct 2, 2025
5a65781
Merge branch 'main' into feature/auto-tool-discovery
msanatan Oct 3, 2025
0d07efd
docs: update developer documentation and add README links
msanatan Oct 3, 2025
f79fda6
feat(tools): enhance tool registration with wrapped function assignment
msanatan Oct 3, 2025
d8fd19d
Remove AI generated code that was never used...
msanatan Oct 3, 2025
a2d76b6
feat: Rebuild MCP server installation with embedded source
msanatan Oct 3, 2025
8e7b202
Add the rebuild server step
msanatan Oct 3, 2025
1d5291e
docs: clarify tool description field requirements and client compatib…
msanatan Oct 3, 2025
7db81f1
fix: move initialization flag after tool discovery to prevent race co…
msanatan Oct 3, 2025
3d107e5
refactor: remove redundant TryParseVersion overrides in platform dete…
msanatan Oct 3, 2025
4314e2a
refactor: remove duplicate UV validation code from platform detectors
msanatan Oct 3, 2025
fe13260
Update UnityMcpBridge/Editor/Tools/CommandRegistry.cs
msanatan Oct 3, 2025
2af3413
refactor: replace WriteToConfig reflection with direct McpConfigurati…
msanatan Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,19 @@ claude mcp add UnityMCP -- "C:/Users/USERNAME/AppData/Local/Microsoft/WinGet/Lin

## 开发和贡献 🛠️

### 开发者
### 添加自定义工具

MCP for Unity 使用与 Unity 的 C# 脚本绑定的 Python MCP 服务器来实现工具功能。如果您想使用自己的工具扩展功能,请参阅 **[CUSTOM_TOOLS.md](docs/CUSTOM_TOOLS.md)** 了解如何操作。

### 贡献项目

如果您正在为 MCP for Unity 做贡献或想要测试核心更改,我们有开发工具来简化您的工作流程:

- **开发部署脚本**:快速部署和测试您对 MCP for Unity Bridge 和 Python 服务器的更改
- **自动备份系统**:具有简单回滚功能的安全测试
- **热重载工作流程**:核心开发的快速迭代周期

📖 **查看 [README-DEV.md](README-DEV.md)** 获取完整的开发设置和工作流程文档。
📖 **查看 [README-DEV.md](docs/README-DEV.md)** 获取完整的开发设置和工作流程文档。

### 贡献 🤝

Expand All @@ -299,7 +303,7 @@ Unity MCP 包含**注重隐私的匿名遥测**来帮助我们改进产品。我

- **🔒 匿名**:仅随机 UUID,无个人数据
- **🚫 轻松退出**:设置 `DISABLE_TELEMETRY=true` 环境变量
- **📖 透明**:查看 [TELEMETRY.md](TELEMETRY.md) 获取完整详情
- **📖 透明**:查看 [TELEMETRY.md](docs/TELEMETRY.md) 获取完整详情

您的隐私对我们很重要。所有遥测都是可选的,旨在尊重您的工作流程。

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,19 @@ On Windows, set `command` to the absolute shim, e.g. `C:\\Users\\YOU\\AppData\\L

## Development & Contributing 🛠️

### For Developers
### Adding Custom Tools

MCP for Unity uses a Python MCP Server tied with Unity's C# scripts for tools. If you'd like to extend the functionality with your own tools, learn how to do so in **[CUSTOM_TOOLS.md](docs/CUSTOM_TOOLS.md)**.

### Contributing to the Project

If you're contributing to MCP for Unity or want to test core changes, we have development tools to streamline your workflow:

- **Development Deployment Scripts**: Quickly deploy and test your changes to MCP for Unity Bridge and Python Server
- **Automatic Backup System**: Safe testing with easy rollback capabilities
- **Hot Reload Workflow**: Fast iteration cycle for core development

📖 **See [README-DEV.md](README-DEV.md)** for complete development setup and workflow documentation.
📖 **See [README-DEV.md](docs/README-DEV.md)** for complete development setup and workflow documentation.

### Contributing 🤝

Expand All @@ -302,7 +306,7 @@ Unity MCP includes **privacy-focused, anonymous telemetry** to help us improve t

- **🔒 Anonymous**: Random UUIDs only, no personal data
- **🚫 Easy opt-out**: Set `DISABLE_TELEMETRY=true` environment variable
- **📖 Transparent**: See [TELEMETRY.md](TELEMETRY.md) for full details
- **📖 Transparent**: See [TELEMETRY.md](docs/TELEMETRY.md) for full details

Your privacy matters to us. All telemetry is optional and designed to respect your workflow.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Data;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;
using MCPForUnity.Editor.Windows;

namespace MCPForUnityTests.Editor.Windows
namespace MCPForUnityTests.Editor.Helpers
{
public class WriteToConfigTests
{
Expand Down Expand Up @@ -68,7 +65,7 @@ public void TearDown()
public void AddsEnvAndDisabledFalse_ForWindsurf()
{
var configPath = Path.Combine(_tempRoot, "windsurf.json");
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
WriteInitialConfig(configPath, isVSCode: false, command: _fakeUvPath, directory: "/old/path");

var client = new McpClient { name = "Windsurf", mcpType = McpTypes.Windsurf };
InvokeWriteToConfig(configPath, client);
Expand All @@ -85,7 +82,7 @@ public void AddsEnvAndDisabledFalse_ForWindsurf()
public void AddsEnvAndDisabledFalse_ForKiro()
{
var configPath = Path.Combine(_tempRoot, "kiro.json");
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
WriteInitialConfig(configPath, isVSCode: false, command: _fakeUvPath, directory: "/old/path");

var client = new McpClient { name = "Kiro", mcpType = McpTypes.Kiro };
InvokeWriteToConfig(configPath, client);
Expand All @@ -102,7 +99,7 @@ public void AddsEnvAndDisabledFalse_ForKiro()
public void DoesNotAddEnvOrDisabled_ForCursor()
{
var configPath = Path.Combine(_tempRoot, "cursor.json");
WriteInitialConfig(configPath, isVSCode:false, command:_fakeUvPath, directory:"/old/path");
WriteInitialConfig(configPath, isVSCode: false, command: _fakeUvPath, directory: "/old/path");

var client = new McpClient { name = "Cursor", mcpType = McpTypes.Cursor };
InvokeWriteToConfig(configPath, client);
Expand All @@ -118,7 +115,7 @@ public void DoesNotAddEnvOrDisabled_ForCursor()
public void DoesNotAddEnvOrDisabled_ForVSCode()
{
var configPath = Path.Combine(_tempRoot, "vscode.json");
WriteInitialConfig(configPath, isVSCode:true, command:_fakeUvPath, directory:"/old/path");
WriteInitialConfig(configPath, isVSCode: true, command: _fakeUvPath, directory: "/old/path");

var client = new McpClient { name = "VSCode", mcpType = McpTypes.VSCode };
InvokeWriteToConfig(configPath, client);
Expand Down Expand Up @@ -219,25 +216,15 @@ private static void WriteInitialConfig(string configPath, bool isVSCode, string
File.WriteAllText(configPath, root.ToString());
}

private static MCPForUnityEditorWindow CreateWindow()
{
return ScriptableObject.CreateInstance<MCPForUnityEditorWindow>();
}

private static void InvokeWriteToConfig(string configPath, McpClient client)
{
var window = CreateWindow();
var mi = typeof(MCPForUnityEditorWindow).GetMethod("WriteToConfig", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(mi, "Could not find WriteToConfig via reflection");

// pythonDir is unused by WriteToConfig, but pass server src to keep it consistent
var result = (string)mi!.Invoke(window, new object[] {
/* pythonDir */ string.Empty,
/* configPath */ configPath,
/* mcpClient */ client
});

Assert.AreEqual("Configured successfully", result, "WriteToConfig should return success");
var result = McpConfigurationHelper.WriteMcpConfiguration(
pythonDir: string.Empty,
configPath: configPath,
mcpClient: client
);

Assert.AreEqual("Configured successfully", result, "WriteMcpConfiguration should return success");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using MCPForUnity.Editor.Tools;

Expand All @@ -8,34 +11,41 @@ namespace MCPForUnityTests.Editor.Tools
public class CommandRegistryTests
{
[Test]
public void GetHandler_ThrowException_ForUnknownCommand()
public void GetHandler_ThrowsException_ForUnknownCommand()
{
var unknown = "HandleDoesNotExist";
try
{
var handler = CommandRegistry.GetHandler(unknown);
Assert.Fail("Should throw InvalidOperation for unknown handler.");
}
catch (InvalidOperationException)
{
var unknown = "nonexistent_command_that_should_not_exist";

}
catch
Assert.Throws<InvalidOperationException>(() =>
{
Assert.Fail("Should throw InvalidOperation for unknown handler.");
}
CommandRegistry.GetHandler(unknown);
}, "Should throw InvalidOperationException for unknown handler");
}

[Test]
public void GetHandler_ReturnsManageGameObjectHandler()
public void AutoDiscovery_RegistersAllBuiltInTools()
{
var handler = CommandRegistry.GetHandler("manage_gameobject");
Assert.IsNotNull(handler, "Expected a handler for manage_gameobject.");
// Verify that all expected built-in tools are registered by trying to get their handlers
var expectedTools = new[]
{
"manage_asset",
"manage_editor",
"manage_gameobject",
"manage_scene",
"manage_script",
"manage_shader",
"read_console",
"manage_menu_item",
"manage_prefabs"
};

var methodInfo = handler.Method;
Assert.AreEqual("HandleCommand", methodInfo.Name, "Handler method name should be HandleCommand.");
Assert.AreEqual(typeof(ManageGameObject), methodInfo.DeclaringType, "Handler should be declared on ManageGameObject.");
Assert.IsNull(handler.Target, "Handler should be a static method (no target instance).");
foreach (var toolName in expectedTools)
{
Assert.DoesNotThrow(() =>
{
var handler = CommandRegistry.GetHandler(toolName);
Assert.IsNotNull(handler, $"Handler for '{toolName}' should not be null");
}, $"Expected tool '{toolName}' to be auto-registered");
}
}
}
}
161 changes: 0 additions & 161 deletions UnityMcpBridge/Editor/Dependencies/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,69 +80,6 @@ public static DependencyCheckResult CheckAllDependencies()
return result;
}

/// <summary>
/// Quick check if system is ready for MCP operations
/// </summary>
public static bool IsSystemReady()
{
try
{
var result = CheckAllDependencies();
return result.IsSystemReady;
}
catch
{
return false;
}
}

/// <summary>
/// Get a summary of missing dependencies
/// </summary>
public static string GetMissingDependenciesSummary()
{
try
{
var result = CheckAllDependencies();
var missing = result.GetMissingRequired();

if (missing.Count == 0)
{
return "All required dependencies are available.";
}

var names = missing.Select(d => d.Name).ToArray();
return $"Missing required dependencies: {string.Join(", ", names)}";
}
catch (Exception ex)
{
return $"Error checking dependencies: {ex.Message}";
}
}

/// <summary>
/// Check if a specific dependency is available
/// </summary>
public static bool IsDependencyAvailable(string dependencyName)
{
try
{
var detector = GetCurrentPlatformDetector();

return dependencyName.ToLowerInvariant() switch
{
"python" => detector.DetectPython().IsAvailable,
"uv" => detector.DetectUV().IsAvailable,
"mcpserver" or "mcp-server" => detector.DetectMCPServer().IsAvailable,
_ => false
};
}
catch
{
return false;
}
}

/// <summary>
/// Get installation recommendations for the current platform
/// </summary>
Expand Down Expand Up @@ -175,104 +112,6 @@ public static (string pythonUrl, string uvUrl) GetInstallationUrls()
}
}

/// <summary>
/// Validate that the MCP server can be started
/// </summary>
public static bool ValidateMCPServerStartup()
{
try
{
// Check if Python and UV are available
if (!IsDependencyAvailable("python") || !IsDependencyAvailable("uv"))
{
return false;
}

// Try to ensure server is installed
ServerInstaller.EnsureServerInstalled();

// Check if server files exist
var serverStatus = GetCurrentPlatformDetector().DetectMCPServer();
return serverStatus.IsAvailable;
}
catch (Exception ex)
{
McpLog.Error($"Error validating MCP server startup: {ex.Message}");
return false;
}
}

/// <summary>
/// Attempt to repair the Python environment
/// </summary>
public static bool RepairPythonEnvironment()
{
try
{
McpLog.Info("Attempting to repair Python environment...");
return ServerInstaller.RepairPythonEnvironment();
}
catch (Exception ex)
{
McpLog.Error($"Error repairing Python environment: {ex.Message}");
return false;
}
}

/// <summary>
/// Get detailed dependency information for diagnostics
/// </summary>
public static string GetDependencyDiagnostics()
{
try
{
var result = CheckAllDependencies();
var detector = GetCurrentPlatformDetector();

var diagnostics = new System.Text.StringBuilder();
diagnostics.AppendLine($"Platform: {detector.PlatformName}");
diagnostics.AppendLine($"Check Time: {result.CheckedAt:yyyy-MM-dd HH:mm:ss} UTC");
diagnostics.AppendLine($"System Ready: {result.IsSystemReady}");
diagnostics.AppendLine();

foreach (var dep in result.Dependencies)
{
diagnostics.AppendLine($"=== {dep.Name} ===");
diagnostics.AppendLine($"Available: {dep.IsAvailable}");
diagnostics.AppendLine($"Required: {dep.IsRequired}");

if (!string.IsNullOrEmpty(dep.Version))
diagnostics.AppendLine($"Version: {dep.Version}");

if (!string.IsNullOrEmpty(dep.Path))
diagnostics.AppendLine($"Path: {dep.Path}");

if (!string.IsNullOrEmpty(dep.Details))
diagnostics.AppendLine($"Details: {dep.Details}");

if (!string.IsNullOrEmpty(dep.ErrorMessage))
diagnostics.AppendLine($"Error: {dep.ErrorMessage}");

diagnostics.AppendLine();
}

if (result.RecommendedActions.Count > 0)
{
diagnostics.AppendLine("=== Recommended Actions ===");
foreach (var action in result.RecommendedActions)
{
diagnostics.AppendLine($"- {action}");
}
}

return diagnostics.ToString();
}
catch (Exception ex)
{
return $"Error generating diagnostics: {ex.Message}";
}
}

private static void GenerateRecommendations(DependencyCheckResult result, IPlatformDetector detector)
{
var missing = result.GetMissingDependencies();
Expand Down
Loading