Skip to content

Commit 66e0e55

Browse files
committed
feat(editor): improve configuration UI and dependency management
- Consolidate settings into Setup and Client Config sections - Remove redundant 'Advanced Settings' from main settings view - Implement robust Node.js path fallback logic - Restore official repository URL as default source - Enhance dependency status checks and error handling --- feat(editor): 설정 UI 및 의존성 관리 개선 - 설정을 Setup 및 Client Config 섹션으로 통합 - 메인 설정 뷰에서 중복된 'Advanced Settings' 제거 - 견고한 Node.js 경로 폴백 로직 구현 - 공식 레포지토리 URL을 기본 소스로 복구 - 의존성 상태 확인 및 에러 처리 강화
1 parent f448ea0 commit 66e0e55

34 files changed

+2664
-913
lines changed

MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs

Lines changed: 181 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
102102
string configuredUrl = null;
103103
bool configExists = false;
104104

105+
string command = null;
106+
105107
if (client.IsVsCodeLayout)
106108
{
107109
var vsConfig = JsonConvert.DeserializeObject<JToken>(configJson) as JObject;
@@ -115,6 +117,12 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
115117
{
116118
configExists = true;
117119

120+
var commandToken = unityObj["command"];
121+
if (commandToken != null && commandToken.Type == JTokenType.String)
122+
{
123+
command = commandToken.ToString();
124+
}
125+
118126
var argsToken = unityObj["args"];
119127
if (argsToken is JArray)
120128
{
@@ -131,10 +139,31 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
131139
}
132140
else
133141
{
134-
McpConfig standardConfig = JsonConvert.DeserializeObject<McpConfig>(configJson);
135-
if (standardConfig?.mcpServers?.unityMCP != null)
142+
// Use JObject parsing for flexibility (handling both url and serverUrl without strict model dependency)
143+
var rootObj = JsonConvert.DeserializeObject<JToken>(configJson) as JObject;
144+
var unityToken = rootObj?["mcpServers"]?["unityMCP"];
145+
146+
if (unityToken is JObject unityObj)
136147
{
137-
args = standardConfig.mcpServers.unityMCP.args;
148+
var commandToken = unityObj["command"];
149+
if (commandToken != null && commandToken.Type == JTokenType.String)
150+
{
151+
command = commandToken.ToString();
152+
}
153+
154+
var argsToken = unityObj["args"];
155+
if (argsToken is JArray)
156+
{
157+
args = argsToken.ToObject<string[]>();
158+
}
159+
160+
// Check for both 'url' (standard) and 'serverUrl' (Antigravity)
161+
var urlToken = unityObj["url"] ?? unityObj["serverUrl"];
162+
if (urlToken != null && urlToken.Type != JTokenType.Null)
163+
{
164+
configuredUrl = urlToken.ToString();
165+
}
166+
138167
configExists = true;
139168
}
140169
}
@@ -146,17 +175,39 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
146175
}
147176

148177
bool matches = false;
149-
if (args != null && args.Length > 0)
178+
bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
179+
180+
if (useHttpTransport)
150181
{
151-
string expectedUvxUrl = AssetPathUtility.GetMcpServerGitUrl();
152-
string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args);
153-
matches = !string.IsNullOrEmpty(configuredUvxUrl) &&
154-
McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl);
182+
// Check 3: HTTP (Only check this if we are in HTTP mode)
183+
if (!string.IsNullOrEmpty(configuredUrl))
184+
{
185+
string expectedUrl = HttpEndpointUtility.GetMcpRpcUrl();
186+
matches = UrlsEqual(configuredUrl, expectedUrl);
187+
}
155188
}
156-
else if (!string.IsNullOrEmpty(configuredUrl))
189+
else
157190
{
158-
string expectedUrl = HttpEndpointUtility.GetMcpRpcUrl();
159-
matches = UrlsEqual(configuredUrl, expectedUrl);
191+
// Check 1: Node.js Wrapper (Stdio) (Only check this if we are in Stdio mode)
192+
string expectedWrapper = AssetPathUtility.GetWrapperJsPath();
193+
if (!string.IsNullOrEmpty(command) &&
194+
Path.GetFileNameWithoutExtension(command).Equals("node", StringComparison.OrdinalIgnoreCase) &&
195+
args != null && args.Length > 0 &&
196+
!string.IsNullOrEmpty(expectedWrapper))
197+
{
198+
if (McpConfigurationHelper.PathsEqual(args[0], expectedWrapper))
199+
{
200+
matches = true;
201+
}
202+
}
203+
// Check 2: UVX (Stdio - Legacy/Fallback)
204+
else if (args != null && args.Length > 0)
205+
{
206+
string expectedUvxUrl = AssetPathUtility.GetMcpServerGitUrl();
207+
string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args);
208+
matches = !string.IsNullOrEmpty(configuredUvxUrl) &&
209+
McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl);
210+
}
160211
}
161212

162213
if (matches)
@@ -327,6 +378,9 @@ public override string GetManualSnippet()
327378
/// <summary>CLI-based configurator (Claude Code).</summary>
328379
public abstract class ClaudeCliMcpConfigurator : McpClientConfiguratorBase
329380
{
381+
private static readonly object _claudeCliLock = new object();
382+
private static bool _isClaudeCliRunning = false;
383+
330384
public ClaudeCliMcpConfigurator(McpClient client) : base(client) { }
331385

332386
public override bool SupportsAutoConfigure => true;
@@ -405,114 +459,152 @@ public override void Configure()
405459

406460
private void Register()
407461
{
408-
var pathService = MCPServiceLocator.Paths;
409-
string claudePath = pathService.GetClaudeCliPath();
410-
if (string.IsNullOrEmpty(claudePath))
462+
lock (_claudeCliLock)
411463
{
412-
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
464+
if (_isClaudeCliRunning)
465+
{
466+
throw new InvalidOperationException("Claude CLI operation already in progress. Please wait.");
467+
}
468+
_isClaudeCliRunning = true;
413469
}
470+
471+
try
472+
{
473+
var pathService = MCPServiceLocator.Paths;
474+
string claudePath = pathService.GetClaudeCliPath();
475+
if (string.IsNullOrEmpty(claudePath))
476+
{
477+
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
478+
}
414479

415-
bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
480+
bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
416481

417-
string args;
418-
if (useHttpTransport)
419-
{
420-
string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
421-
args = $"mcp add --transport http UnityMCP {httpUrl}";
422-
}
423-
else
424-
{
425-
var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
426-
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" {packageName}";
427-
}
482+
string args;
483+
if (useHttpTransport)
484+
{
485+
string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
486+
args = $"mcp add --transport http UnityMCP {httpUrl}";
487+
}
488+
else
489+
{
490+
var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
491+
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" {packageName}";
492+
}
428493

429-
string projectDir = Path.GetDirectoryName(Application.dataPath);
494+
string projectDir = Path.GetDirectoryName(Application.dataPath);
430495

431-
string pathPrepend = null;
432-
if (Application.platform == RuntimePlatform.OSXEditor)
433-
{
434-
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
435-
}
436-
else if (Application.platform == RuntimePlatform.LinuxEditor)
437-
{
438-
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
439-
}
496+
string pathPrepend = null;
497+
if (Application.platform == RuntimePlatform.OSXEditor)
498+
{
499+
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
500+
}
501+
else if (Application.platform == RuntimePlatform.LinuxEditor)
502+
{
503+
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
504+
}
440505

441-
try
442-
{
443-
string claudeDir = Path.GetDirectoryName(claudePath);
444-
if (!string.IsNullOrEmpty(claudeDir))
506+
try
445507
{
446-
pathPrepend = string.IsNullOrEmpty(pathPrepend)
447-
? claudeDir
448-
: $"{claudeDir}:{pathPrepend}";
508+
string claudeDir = Path.GetDirectoryName(claudePath);
509+
if (!string.IsNullOrEmpty(claudeDir))
510+
{
511+
pathPrepend = string.IsNullOrEmpty(pathPrepend)
512+
? claudeDir
513+
: $"{claudeDir}:{pathPrepend}";
514+
}
449515
}
450-
}
451-
catch { }
516+
catch { }
452517

453-
bool already = false;
454-
if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
455-
{
456-
string combined = ($"{stdout}\n{stderr}") ?? string.Empty;
457-
if (combined.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0)
518+
bool already = false;
519+
if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
458520
{
459-
already = true;
521+
string combined = ($"{stdout}\n{stderr}") ?? string.Empty;
522+
if (combined.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0)
523+
{
524+
already = true;
525+
}
526+
else
527+
{
528+
throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}");
529+
}
460530
}
461-
else
531+
532+
if (!already)
462533
{
463-
throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}");
534+
McpLog.Info("Successfully registered with Claude Code.");
464535
}
465-
}
466536

467-
if (!already)
537+
CheckStatus();
538+
}
539+
finally
468540
{
469-
McpLog.Info("Successfully registered with Claude Code.");
541+
lock (_claudeCliLock)
542+
{
543+
_isClaudeCliRunning = false;
544+
}
470545
}
471-
472-
CheckStatus();
473546
}
474547

475548
private void Unregister()
476549
{
477-
var pathService = MCPServiceLocator.Paths;
478-
string claudePath = pathService.GetClaudeCliPath();
479-
480-
if (string.IsNullOrEmpty(claudePath))
481-
{
482-
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
483-
}
484-
485-
string projectDir = Path.GetDirectoryName(Application.dataPath);
486-
string pathPrepend = null;
487-
if (Application.platform == RuntimePlatform.OSXEditor)
550+
lock (_claudeCliLock)
488551
{
489-
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
552+
if (_isClaudeCliRunning)
553+
{
554+
throw new InvalidOperationException("Claude CLI operation already in progress. Please wait.");
555+
}
556+
_isClaudeCliRunning = true;
490557
}
491-
else if (Application.platform == RuntimePlatform.LinuxEditor)
558+
559+
try
492560
{
493-
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
494-
}
561+
var pathService = MCPServiceLocator.Paths;
562+
string claudePath = pathService.GetClaudeCliPath();
495563

496-
bool serverExists = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
564+
if (string.IsNullOrEmpty(claudePath))
565+
{
566+
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
567+
}
497568

498-
if (!serverExists)
499-
{
500-
client.SetStatus(McpStatus.NotConfigured);
501-
McpLog.Info("No MCP for Unity server found - already unregistered.");
502-
return;
503-
}
569+
string projectDir = Path.GetDirectoryName(Application.dataPath);
570+
string pathPrepend = null;
571+
if (Application.platform == RuntimePlatform.OSXEditor)
572+
{
573+
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
574+
}
575+
else if (Application.platform == RuntimePlatform.LinuxEditor)
576+
{
577+
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
578+
}
504579

505-
if (ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out var stdout, out var stderr, 10000, pathPrepend))
506-
{
507-
McpLog.Info("MCP server successfully unregistered from Claude Code.");
580+
bool serverExists = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
581+
582+
if (!serverExists)
583+
{
584+
client.SetStatus(McpStatus.NotConfigured);
585+
McpLog.Info("No MCP for Unity server found - already unregistered.");
586+
return;
587+
}
588+
589+
if (ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out var stdout, out var stderr, 10000, pathPrepend))
590+
{
591+
McpLog.Info("MCP server successfully unregistered from Claude Code.");
592+
}
593+
else
594+
{
595+
throw new InvalidOperationException($"Failed to unregister: {stderr}");
596+
}
597+
598+
client.SetStatus(McpStatus.NotConfigured);
599+
CheckStatus();
508600
}
509-
else
601+
finally
510602
{
511-
throw new InvalidOperationException($"Failed to unregister: {stderr}");
603+
lock (_claudeCliLock)
604+
{
605+
_isClaudeCliRunning = false;
606+
}
512607
}
513-
514-
client.SetStatus(McpStatus.NotConfigured);
515-
CheckStatus();
516608
}
517609

518610
public override string GetManualSnippet()

MCPForUnity/Editor/Constants/EditorPrefKeys.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,15 @@ internal static class EditorPrefKeys
2121
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
2222
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
2323

24-
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";
25-
internal const string PackageDeployLastBackupPath = "MCPForUnity.PackageDeploy.LastBackupPath";
26-
internal const string PackageDeployLastTargetPath = "MCPForUnity.PackageDeploy.LastTargetPath";
27-
internal const string PackageDeployLastSourcePath = "MCPForUnity.PackageDeploy.LastSourcePath";
28-
2924
internal const string ServerSrc = "MCPForUnity.ServerSrc";
3025
internal const string UseEmbeddedServer = "MCPForUnity.UseEmbeddedServer";
3126
internal const string LockCursorConfig = "MCPForUnity.LockCursorConfig";
3227
internal const string AutoRegisterEnabled = "MCPForUnity.AutoRegisterEnabled";
33-
internal const string ToolEnabledPrefix = "MCPForUnity.ToolEnabled.";
34-
internal const string ToolFoldoutStatePrefix = "MCPForUnity.ToolFoldout.";
35-
internal const string EditorWindowActivePanel = "MCPForUnity.EditorWindow.ActivePanel";
3628

3729
internal const string SetupCompleted = "MCPForUnity.SetupCompleted";
3830
internal const string SetupDismissed = "MCPForUnity.SetupDismissed";
31+
32+
internal const string EditorWindowActivePanel = "MCPForUnityEditorWindow.ActivePanel";
3933

4034
internal const string CustomToolRegistrationEnabled = "MCPForUnity.CustomToolRegistrationEnabled";
4135

@@ -45,5 +39,18 @@ internal static class EditorPrefKeys
4539

4640
internal const string TelemetryDisabled = "MCPForUnity.TelemetryDisabled";
4741
internal const string CustomerUuid = "MCPForUnity.CustomerUUID";
42+
43+
// Path Overrides
44+
internal const string PythonPathOverride = "MCPForUnity.PythonPathOverride";
45+
internal const string NodePathOverride = "MCPForUnity.NodePathOverride";
46+
internal const string UvPathOverride = "MCPForUnity.UvPathOverride";
47+
48+
// Deployment & Tool Config
49+
internal const string PackageDeploySourcePath = "MCPForUnity.Deploy.SourcePath";
50+
internal const string PackageDeployLastBackupPath = "MCPForUnity.Deploy.LastBackupPath";
51+
internal const string PackageDeployLastTargetPath = "MCPForUnity.Deploy.LastTargetPath";
52+
internal const string PackageDeployLastSourcePath = "MCPForUnity.Deploy.LastSourcePath";
53+
internal const string ToolFoldoutStatePrefix = "MCPForUnity.Tool.Foldout.";
54+
internal const string ToolEnabledPrefix = "MCPForUnity.Tool.Enabled.";
4855
}
4956
}

MCPForUnity/Editor/Data/.keep

Whitespace-only changes.

0 commit comments

Comments
 (0)