Merge pull request #175 from dsarno/claude-code-button
feat: Add Claude Code support with register/unregister toggle button in Unity MCP windowmain
commit
01a3d472af
|
|
@ -28,6 +28,20 @@ namespace UnityMcpBridge.Editor.Data
|
||||||
configStatus = "Not Configured",
|
configStatus = "Not Configured",
|
||||||
},
|
},
|
||||||
new()
|
new()
|
||||||
|
{
|
||||||
|
name = "Claude Code",
|
||||||
|
windowsConfigPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
".claude.json"
|
||||||
|
),
|
||||||
|
linuxConfigPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
".claude.json"
|
||||||
|
),
|
||||||
|
mcpType = McpTypes.ClaudeCode,
|
||||||
|
configStatus = "Not Configured",
|
||||||
|
},
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
name = "Cursor",
|
name = "Cursor",
|
||||||
windowsConfigPath = Path.Combine(
|
windowsConfigPath = Path.Combine(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace UnityMcpBridge.Editor.Models
|
||||||
ClaudeDesktop,
|
ClaudeDesktop,
|
||||||
Cursor,
|
Cursor,
|
||||||
VSCode,
|
VSCode,
|
||||||
|
ClaudeCode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
);
|
);
|
||||||
GUI.Label(
|
GUI.Label(
|
||||||
new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height),
|
new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height),
|
||||||
mcpClient.name + " Manual Configuration",
|
(mcpClient?.name ?? "Unknown") + " Manual Configuration",
|
||||||
EditorStyles.boldLabel
|
EditorStyles.boldLabel
|
||||||
);
|
);
|
||||||
EditorGUILayout.Space(10);
|
EditorGUILayout.Space(10);
|
||||||
|
|
@ -70,17 +70,17 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
};
|
};
|
||||||
|
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"1. Open " + mcpClient.name + " config file by either:",
|
"1. Open " + (mcpClient?.name ?? "Unknown") + " config file by either:",
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
if (mcpClient.mcpType == McpTypes.ClaudeDesktop)
|
if (mcpClient?.mcpType == McpTypes.ClaudeDesktop)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
" a) Going to Settings > Developer > Edit Config",
|
" a) Going to Settings > Developer > Edit Config",
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (mcpClient.mcpType == McpTypes.Cursor)
|
else if (mcpClient?.mcpType == McpTypes.Cursor)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
" a) Going to File > Preferences > Cursor Settings > MCP > Add new global MCP server",
|
" a) Going to File > Preferences > Cursor Settings > MCP > Add new global MCP server",
|
||||||
|
|
@ -96,6 +96,8 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
// Path section with improved styling
|
// Path section with improved styling
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
string displayPath;
|
string displayPath;
|
||||||
|
if (mcpClient != null)
|
||||||
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
displayPath = mcpClient.windowsConfigPath;
|
displayPath = mcpClient.windowsConfigPath;
|
||||||
|
|
@ -111,6 +113,11 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
{
|
{
|
||||||
displayPath = configPath;
|
displayPath = configPath;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
displayPath = configPath;
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent text overflow by allowing the text field to wrap
|
// Prevent text overflow by allowing the text field to wrap
|
||||||
GUIStyle pathStyle = new(EditorStyles.textField) { wordWrap = true };
|
GUIStyle pathStyle = new(EditorStyles.textField) { wordWrap = true };
|
||||||
|
|
@ -224,7 +231,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
|
|
||||||
EditorGUILayout.Space(10);
|
EditorGUILayout.Space(10);
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(
|
||||||
"3. Save the file and restart " + mcpClient.name,
|
"3. Save the file and restart " + (mcpClient?.name ?? "Unknown"),
|
||||||
instructionStyle
|
instructionStyle
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
@ -54,6 +55,16 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
LoadValidationLevelSetting();
|
LoadValidationLevelSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnFocus()
|
||||||
|
{
|
||||||
|
// Refresh configuration status when window gains focus
|
||||||
|
foreach (McpClient mcpClient in mcpClients.clients)
|
||||||
|
{
|
||||||
|
CheckMcpConfiguration(mcpClient);
|
||||||
|
}
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
private Color GetStatusColor(McpStatus status)
|
private Color GetStatusColor(McpStatus status)
|
||||||
{
|
{
|
||||||
// Return appropriate color based on the status enum
|
// Return appropriate color based on the status enum
|
||||||
|
|
@ -326,6 +337,23 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
ConfigureMcpClient(mcpClient);
|
ConfigureMcpClient(mcpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (mcpClient.mcpType == McpTypes.ClaudeCode)
|
||||||
|
{
|
||||||
|
bool isConfigured = mcpClient.status == McpStatus.Configured;
|
||||||
|
string buttonText = isConfigured ? "Unregister UnityMCP with Claude Code" : "Register with Claude Code";
|
||||||
|
if (GUILayout.Button(buttonText, GUILayout.Height(32)))
|
||||||
|
{
|
||||||
|
if (isConfigured)
|
||||||
|
{
|
||||||
|
UnregisterWithClaudeCode();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string pythonDir = FindPackagePythonDirectory();
|
||||||
|
RegisterWithClaudeCode(pythonDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (GUILayout.Button($"Auto Configure", GUILayout.Height(32)))
|
if (GUILayout.Button($"Auto Configure", GUILayout.Height(32)))
|
||||||
|
|
@ -334,6 +362,8 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mcpClient.mcpType != McpTypes.ClaudeCode)
|
||||||
|
{
|
||||||
if (GUILayout.Button("Manual Setup", GUILayout.Height(32)))
|
if (GUILayout.Button("Manual Setup", GUILayout.Height(32)))
|
||||||
{
|
{
|
||||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
|
|
@ -343,6 +373,13 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
if (mcpClient.mcpType == McpTypes.VSCode)
|
if (mcpClient.mcpType == McpTypes.VSCode)
|
||||||
{
|
{
|
||||||
string pythonDir = FindPackagePythonDirectory();
|
string pythonDir = FindPackagePythonDirectory();
|
||||||
|
string uvPath = FindUvPath();
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var vscodeConfig = new
|
var vscodeConfig = new
|
||||||
{
|
{
|
||||||
mcp = new
|
mcp = new
|
||||||
|
|
@ -351,7 +388,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
{
|
{
|
||||||
unityMCP = new
|
unityMCP = new
|
||||||
{
|
{
|
||||||
command = "uv",
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -366,6 +403,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
|
@ -394,10 +432,16 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
|
|
||||||
private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
|
private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
|
||||||
{
|
{
|
||||||
|
string uvPath = FindUvPath();
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
return "UV package manager not found. Please install UV first.";
|
||||||
|
}
|
||||||
|
|
||||||
// Create configuration object for unityMCP
|
// Create configuration object for unityMCP
|
||||||
McpConfigServer unityMCPConfig = new()
|
McpConfigServer unityMCPConfig = new()
|
||||||
{
|
{
|
||||||
command = "uv",
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -413,7 +457,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"Error reading existing config: {e.Message}.");
|
UnityEngine.Debug.LogWarning($"Error reading existing config: {e.Message}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,13 +554,20 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Create standard MCP configuration for other clients
|
// Create standard MCP configuration for other clients
|
||||||
|
string uvPath = FindUvPath();
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure manual setup.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
McpConfig jsonConfig = new()
|
McpConfig jsonConfig = new()
|
||||||
{
|
{
|
||||||
mcpServers = new McpConfigServers
|
mcpServers = new McpConfigServers
|
||||||
{
|
{
|
||||||
unityMCP = new McpConfigServer
|
unityMCP = new McpConfigServer
|
||||||
{
|
{
|
||||||
command = "uv",
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -546,12 +597,17 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
if (package.name == "com.justinpbarnett.unity-mcp")
|
if (package.name == "com.justinpbarnett.unity-mcp")
|
||||||
{
|
{
|
||||||
string packagePath = package.resolvedPath;
|
string packagePath = package.resolvedPath;
|
||||||
string potentialPythonDir = Path.Combine(packagePath, "Python");
|
|
||||||
|
|
||||||
if (
|
// Check for local package structure (UnityMcpServer/src)
|
||||||
Directory.Exists(potentialPythonDir)
|
string localPythonDir = Path.Combine(Path.GetDirectoryName(packagePath), "UnityMcpServer", "src");
|
||||||
&& File.Exists(Path.Combine(potentialPythonDir, "server.py"))
|
if (Directory.Exists(localPythonDir) && File.Exists(Path.Combine(localPythonDir, "server.py")))
|
||||||
)
|
{
|
||||||
|
return localPythonDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for old structure (Python subdirectory)
|
||||||
|
string potentialPythonDir = Path.Combine(packagePath, "Python");
|
||||||
|
if (Directory.Exists(potentialPythonDir) && File.Exists(Path.Combine(potentialPythonDir, "server.py")))
|
||||||
{
|
{
|
||||||
return potentialPythonDir;
|
return potentialPythonDir;
|
||||||
}
|
}
|
||||||
|
|
@ -560,13 +616,22 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
else if (request.Error != null)
|
else if (request.Error != null)
|
||||||
{
|
{
|
||||||
Debug.LogError("Failed to list packages: " + request.Error.message);
|
UnityEngine.Debug.LogError("Failed to list packages: " + request.Error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found via Package Manager, try manual approaches
|
// If not found via Package Manager, try manual approaches
|
||||||
// First check for local installation
|
// Check for local development structure
|
||||||
string[] possibleDirs =
|
string[] possibleDirs =
|
||||||
{
|
{
|
||||||
|
// Check in the Unity project's Packages folder (for local package development)
|
||||||
|
Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Packages", "unity-mcp", "UnityMcpServer", "src")),
|
||||||
|
// Check relative to the Unity project (for development)
|
||||||
|
Path.GetFullPath(Path.Combine(Application.dataPath, "..", "unity-mcp", "UnityMcpServer", "src")),
|
||||||
|
// Check in user's home directory (common installation location)
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "unity-mcp", "UnityMcpServer", "src"),
|
||||||
|
// Check in Applications folder (macOS/Linux common location)
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Applications", "UnityMCP", "UnityMcpServer", "src"),
|
||||||
|
// Legacy Python folder structure
|
||||||
Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python")),
|
Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python")),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -579,11 +644,11 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still not found, return the placeholder path
|
// If still not found, return the placeholder path
|
||||||
Debug.LogWarning("Could not find Python directory, using placeholder path");
|
UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogError($"Error finding package path: {e.Message}");
|
UnityEngine.Debug.LogError($"Error finding package path: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return pythonDir;
|
return pythonDir;
|
||||||
|
|
@ -651,7 +716,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||||
Debug.LogError(
|
UnityEngine.Debug.LogError(
|
||||||
$"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}"
|
$"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}"
|
||||||
);
|
);
|
||||||
return $"Failed to configure {mcpClient.name}";
|
return $"Failed to configure {mcpClient.name}";
|
||||||
|
|
@ -669,13 +734,20 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
string pythonDir = FindPackagePythonDirectory();
|
string pythonDir = FindPackagePythonDirectory();
|
||||||
|
|
||||||
// Create the manual configuration message
|
// Create the manual configuration message
|
||||||
|
string uvPath = FindUvPath();
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("UV package manager not found. Cannot configure manual setup.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
McpConfig jsonConfig = new()
|
McpConfig jsonConfig = new()
|
||||||
{
|
{
|
||||||
mcpServers = new McpConfigServers
|
mcpServers = new McpConfigServers
|
||||||
{
|
{
|
||||||
unityMCP = new McpConfigServer
|
unityMCP = new McpConfigServer
|
||||||
{
|
{
|
||||||
command = "uv",
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -735,6 +807,13 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Special handling for Claude Code
|
||||||
|
if (mcpClient.mcpType == McpTypes.ClaudeCode)
|
||||||
|
{
|
||||||
|
CheckClaudeCodeConfiguration(mcpClient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string configPath;
|
string configPath;
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
|
|
@ -814,5 +893,399 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
mcpClient.SetStatus(McpStatus.Error, e.Message);
|
mcpClient.SetStatus(McpStatus.Error, e.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RegisterWithClaudeCode(string pythonDir)
|
||||||
|
{
|
||||||
|
string command;
|
||||||
|
string args;
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
command = FindClaudeCommand();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(command))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("Claude CLI not found. Please ensure Claude Code is installed and accessible.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find uv.exe in common locations
|
||||||
|
string uvPath = FindWindowsUvPath();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(uvPath))
|
||||||
|
{
|
||||||
|
// Fallback to expecting uv in PATH
|
||||||
|
args = $"mcp add UnityMCP -- uv --directory \"{pythonDir}\" run server.py";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args = $"mcp add UnityMCP -- \"{uvPath}\" --directory \"{pythonDir}\" run server.py";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use full path to claude command
|
||||||
|
command = "/usr/local/bin/claude";
|
||||||
|
args = $"mcp add UnityMCP -- uv --directory \"{pythonDir}\" run server.py";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the Unity project directory (where the Assets folder is)
|
||||||
|
string unityProjectDir = Application.dataPath;
|
||||||
|
string projectDir = Path.GetDirectoryName(unityProjectDir);
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = command,
|
||||||
|
Arguments = args,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = projectDir // Set working directory to Unity project directory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set PATH to include common binary locations
|
||||||
|
string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
|
||||||
|
string additionalPaths = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin";
|
||||||
|
psi.EnvironmentVariables["PATH"] = $"{additionalPaths}:{currentPath}";
|
||||||
|
|
||||||
|
using var process = Process.Start(psi);
|
||||||
|
string output = process.StandardOutput.ReadToEnd();
|
||||||
|
string errors = process.StandardError.ReadToEnd();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check for success or already exists
|
||||||
|
if (output.Contains("Added stdio MCP server") || errors.Contains("already exists"))
|
||||||
|
{
|
||||||
|
// Force refresh the configuration status
|
||||||
|
var claudeClient = mcpClients.clients.FirstOrDefault(c => c.mcpType == McpTypes.ClaudeCode);
|
||||||
|
if (claudeClient != null)
|
||||||
|
{
|
||||||
|
CheckMcpConfiguration(claudeClient);
|
||||||
|
}
|
||||||
|
Repaint();
|
||||||
|
UnityEngine.Debug.Log("UnityMCP server successfully registered from Claude Code.");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(errors))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Claude CLI registration failed: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterWithClaudeCode()
|
||||||
|
{
|
||||||
|
string command;
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
command = FindClaudeCommand();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(command))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("Claude CLI not found. Please ensure Claude Code is installed and accessible.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use full path to claude command
|
||||||
|
command = "/usr/local/bin/claude";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the Unity project directory (where the Assets folder is)
|
||||||
|
string unityProjectDir = Application.dataPath;
|
||||||
|
string projectDir = Path.GetDirectoryName(unityProjectDir);
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = command,
|
||||||
|
Arguments = "mcp remove UnityMCP",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = projectDir // Set working directory to Unity project directory
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set PATH to include common binary locations
|
||||||
|
string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
|
||||||
|
string additionalPaths = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin";
|
||||||
|
psi.EnvironmentVariables["PATH"] = $"{additionalPaths}:{currentPath}";
|
||||||
|
|
||||||
|
using var process = Process.Start(psi);
|
||||||
|
string output = process.StandardOutput.ReadToEnd();
|
||||||
|
string errors = process.StandardError.ReadToEnd();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
// Check for success
|
||||||
|
if (output.Contains("Removed MCP server") || process.ExitCode == 0)
|
||||||
|
{
|
||||||
|
// Force refresh the configuration status
|
||||||
|
var claudeClient = mcpClients.clients.FirstOrDefault(c => c.mcpType == McpTypes.ClaudeCode);
|
||||||
|
if (claudeClient != null)
|
||||||
|
{
|
||||||
|
CheckMcpConfiguration(claudeClient);
|
||||||
|
}
|
||||||
|
Repaint();
|
||||||
|
|
||||||
|
UnityEngine.Debug.Log("UnityMCP server successfully unregistered from Claude Code.");
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(errors))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning($"Claude MCP removal errors: {errors}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Claude CLI unregistration failed: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FindUvPath()
|
||||||
|
{
|
||||||
|
string uvPath = null;
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
uvPath = FindWindowsUvPath();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// macOS/Linux paths
|
||||||
|
string[] possiblePaths = {
|
||||||
|
"/Library/Frameworks/Python.framework/Versions/3.13/bin/uv",
|
||||||
|
"/usr/local/bin/uv",
|
||||||
|
"/opt/homebrew/bin/uv",
|
||||||
|
"/usr/bin/uv"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string path in possiblePaths)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
uvPath = path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found in common locations, try to find via which command
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "which",
|
||||||
|
Arguments = "uv",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = Process.Start(psi);
|
||||||
|
string output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(output) && File.Exists(output))
|
||||||
|
{
|
||||||
|
uvPath = output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uvPath == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError("UV package manager not found! Please install UV first:\n" +
|
||||||
|
"• macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh\n" +
|
||||||
|
"• Windows: pip install uv\n" +
|
||||||
|
"• Or visit: https://docs.astral.sh/uv/getting-started/installation");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uvPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FindWindowsUvPath()
|
||||||
|
{
|
||||||
|
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
|
||||||
|
// Check for different Python versions
|
||||||
|
string[] pythonVersions = { "Python313", "Python312", "Python311", "Python310", "Python39", "Python38" };
|
||||||
|
|
||||||
|
foreach (string version in pythonVersions)
|
||||||
|
{
|
||||||
|
string uvPath = Path.Combine(appData, version, "Scripts", "uv.exe");
|
||||||
|
if (File.Exists(uvPath))
|
||||||
|
{
|
||||||
|
return uvPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Program Files locations
|
||||||
|
string[] programFilesPaths = {
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Python"),
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Python"),
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "Python")
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string basePath in programFilesPaths)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(basePath))
|
||||||
|
{
|
||||||
|
foreach (string dir in Directory.GetDirectories(basePath, "Python*"))
|
||||||
|
{
|
||||||
|
string uvPath = Path.Combine(dir, "Scripts", "uv.exe");
|
||||||
|
if (File.Exists(uvPath))
|
||||||
|
{
|
||||||
|
return uvPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Will fallback to using 'uv' from PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FindClaudeCommand()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Common locations for Claude CLI on Windows
|
||||||
|
string[] possiblePaths = {
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm", "claude.cmd"),
|
||||||
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "npm", "claude.cmd"),
|
||||||
|
"claude.cmd", // Fallback to PATH
|
||||||
|
"claude" // Final fallback
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (string path in possiblePaths)
|
||||||
|
{
|
||||||
|
if (path.Contains("\\") && File.Exists(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find via where command
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "where",
|
||||||
|
Arguments = "claude",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = Process.Start(psi);
|
||||||
|
string output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(output))
|
||||||
|
{
|
||||||
|
string[] lines = output.Split('\n');
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
string cleanPath = line.Trim();
|
||||||
|
if (File.Exists(cleanPath))
|
||||||
|
{
|
||||||
|
return cleanPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors and fall back
|
||||||
|
}
|
||||||
|
|
||||||
|
return "claude"; // Final fallback to PATH
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "/usr/local/bin/claude";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckClaudeCodeConfiguration(McpClient mcpClient)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get the Unity project directory to check project-specific config
|
||||||
|
string unityProjectDir = Application.dataPath;
|
||||||
|
string projectDir = Path.GetDirectoryName(unityProjectDir);
|
||||||
|
|
||||||
|
// Read the global Claude config file
|
||||||
|
string configPath = mcpClient.linuxConfigPath; // ~/.claude.json
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
{
|
||||||
|
mcpClient.SetStatus(McpStatus.NotConfigured);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string configJson = File.ReadAllText(configPath);
|
||||||
|
dynamic claudeConfig = JsonConvert.DeserializeObject(configJson);
|
||||||
|
|
||||||
|
// Check for UnityMCP server in the mcpServers section (current format)
|
||||||
|
if (claudeConfig?.mcpServers != null)
|
||||||
|
{
|
||||||
|
var servers = claudeConfig.mcpServers;
|
||||||
|
if (servers.UnityMCP != null || servers.unityMCP != null)
|
||||||
|
{
|
||||||
|
// Found UnityMCP configured
|
||||||
|
mcpClient.SetStatus(McpStatus.Configured);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if there's a project-specific configuration for this Unity project (legacy format)
|
||||||
|
if (claudeConfig?.projects != null)
|
||||||
|
{
|
||||||
|
// Look for the project path in the config
|
||||||
|
foreach (var project in claudeConfig.projects)
|
||||||
|
{
|
||||||
|
string projectPath = project.Name;
|
||||||
|
if (projectPath == projectDir && project.Value?.mcpServers != null)
|
||||||
|
{
|
||||||
|
// Check for UnityMCP (case variations)
|
||||||
|
var servers = project.Value.mcpServers;
|
||||||
|
if (servers.UnityMCP != null || servers.unityMCP != null)
|
||||||
|
{
|
||||||
|
// Found UnityMCP configured for this project
|
||||||
|
mcpClient.SetStatus(McpStatus.Configured);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No configuration found for this project
|
||||||
|
mcpClient.SetStatus(McpStatus.NotConfigured);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning($"Error checking Claude Code config: {e.Message}");
|
||||||
|
mcpClient.SetStatus(McpStatus.Error, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue