2025-10-04 08:23:28 +08:00
using System ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices ;
using UnityEditor ;
2025-10-24 12:50:29 +08:00
using UnityEditor.UIElements ; // For Unity 2021 compatibility
2025-10-04 08:23:28 +08:00
using UnityEngine ;
2025-10-24 12:50:29 +08:00
using UnityEngine.UIElements ;
2025-10-04 08:23:28 +08:00
using MCPForUnity.Editor.Data ;
using MCPForUnity.Editor.Helpers ;
using MCPForUnity.Editor.Models ;
2025-10-24 12:50:29 +08:00
using MCPForUnity.Editor.Services ;
2025-10-04 08:23:28 +08:00
namespace MCPForUnity.Editor.Windows
{
public class MCPForUnityEditorWindow : EditorWindow
{
2025-10-24 12:50:29 +08:00
// Protocol enum for future HTTP support
private enum ConnectionProtocol
{
Stdio ,
// HTTPStreaming // Future
}
// Settings UI Elements
private Label versionLabel ;
private Toggle debugLogsToggle ;
private EnumField validationLevelField ;
private Label validationDescription ;
private Foldout advancedSettingsFoldout ;
private TextField mcpServerPathOverride ;
private TextField uvPathOverride ;
private Button browsePythonButton ;
private Button clearPythonButton ;
private Button browseUvButton ;
private Button clearUvButton ;
private VisualElement mcpServerPathStatus ;
private VisualElement uvPathStatus ;
// Connection UI Elements
private EnumField protocolDropdown ;
private TextField unityPortField ;
private TextField serverPortField ;
private VisualElement statusIndicator ;
private Label connectionStatusLabel ;
private Button connectionToggleButton ;
private VisualElement healthIndicator ;
private Label healthStatusLabel ;
private Button testConnectionButton ;
private VisualElement serverStatusBanner ;
private Label serverStatusMessage ;
private Button downloadServerButton ;
private Button rebuildServerButton ;
// Client UI Elements
private DropdownField clientDropdown ;
private Button configureAllButton ;
private VisualElement clientStatusIndicator ;
private Label clientStatusLabel ;
private Button configureButton ;
private VisualElement claudeCliPathRow ;
private TextField claudeCliPath ;
private Button browseClaudeButton ;
private Foldout manualConfigFoldout ;
private TextField configPathField ;
private Button copyPathButton ;
private Button openFileButton ;
private TextField configJsonField ;
private Button copyJsonButton ;
private Label installationStepsLabel ;
// Data
2025-10-04 08:23:28 +08:00
private readonly McpClients mcpClients = new ( ) ;
private int selectedClientIndex = 0 ;
2025-10-24 12:50:29 +08:00
private ValidationLevel currentValidationLevel = ValidationLevel . Standard ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Validation levels matching the existing enum
private enum ValidationLevel
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
Basic ,
Standard ,
Comprehensive ,
Strict
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
public static void ShowWindow ( )
{
var window = GetWindow < MCPForUnityEditorWindow > ( "MCP For Unity" ) ;
window . minSize = new Vector2 ( 500 , 600 ) ;
}
public void CreateGUI ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
// Determine base path (Package Manager vs Asset Store install)
string basePath = AssetPathUtility . GetMcpPackageRootPath ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Load UXML
var visualTree = AssetDatabase . LoadAssetAtPath < VisualTreeAsset > (
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
) ;
if ( visualTree = = null )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
McpLog . Error ( $"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml" ) ;
return ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
visualTree . CloneTree ( rootVisualElement ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Load USS
var styleSheet = AssetDatabase . LoadAssetAtPath < StyleSheet > (
$"{basePath}/Editor/Windows/MCPForUnityEditorWindow.uss"
) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
if ( styleSheet ! = null )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
rootVisualElement . styleSheets . Add ( styleSheet ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Cache UI elements
CacheUIElements ( ) ;
// Initialize UI
InitializeUI ( ) ;
// Register callbacks
RegisterCallbacks ( ) ;
// Initial update
UpdateConnectionStatus ( ) ;
UpdateServerStatusBanner ( ) ;
UpdateClientStatus ( ) ;
UpdatePathOverrides ( ) ;
// Technically not required to connect, but if we don't do this, the UI will be blank
UpdateManualConfiguration ( ) ;
UpdateClaudeCliPathVisibility ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnEnable ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorApplication . update + = OnEditorUpdate ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnDisable ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorApplication . update - = OnEditorUpdate ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnFocus ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
// Only refresh data if UI is built
if ( rootVisualElement = = null | | rootVisualElement . childCount = = 0 )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
RefreshAllData ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnEditorUpdate ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
// Only update UI if it's built
if ( rootVisualElement = = null | | rootVisualElement . childCount = = 0 )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
UpdateConnectionStatus ( ) ;
}
private void RefreshAllData ( )
{
// Update connection status
UpdateConnectionStatus ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Auto-verify bridge health if connected
if ( MCPServiceLocator . Bridge . IsRunning )
{
VerifyBridgeConnection ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Update path overrides
UpdatePathOverrides ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Refresh selected client (may have been configured externally)
if ( selectedClientIndex > = 0 & & selectedClientIndex < mcpClients . clients . Count )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
var client = mcpClients . clients [ selectedClientIndex ] ;
MCPServiceLocator . Client . CheckClientStatus ( client ) ;
UpdateClientStatus ( ) ;
UpdateManualConfiguration ( ) ;
UpdateClaudeCliPathVisibility ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void CacheUIElements ( )
{
// Settings
versionLabel = rootVisualElement . Q < Label > ( "version-label" ) ;
debugLogsToggle = rootVisualElement . Q < Toggle > ( "debug-logs-toggle" ) ;
validationLevelField = rootVisualElement . Q < EnumField > ( "validation-level" ) ;
validationDescription = rootVisualElement . Q < Label > ( "validation-description" ) ;
advancedSettingsFoldout = rootVisualElement . Q < Foldout > ( "advanced-settings-foldout" ) ;
mcpServerPathOverride = rootVisualElement . Q < TextField > ( "python-path-override" ) ;
uvPathOverride = rootVisualElement . Q < TextField > ( "uv-path-override" ) ;
browsePythonButton = rootVisualElement . Q < Button > ( "browse-python-button" ) ;
clearPythonButton = rootVisualElement . Q < Button > ( "clear-python-button" ) ;
browseUvButton = rootVisualElement . Q < Button > ( "browse-uv-button" ) ;
clearUvButton = rootVisualElement . Q < Button > ( "clear-uv-button" ) ;
mcpServerPathStatus = rootVisualElement . Q < VisualElement > ( "mcp-server-path-status" ) ;
uvPathStatus = rootVisualElement . Q < VisualElement > ( "uv-path-status" ) ;
// Connection
protocolDropdown = rootVisualElement . Q < EnumField > ( "protocol-dropdown" ) ;
unityPortField = rootVisualElement . Q < TextField > ( "unity-port" ) ;
serverPortField = rootVisualElement . Q < TextField > ( "server-port" ) ;
statusIndicator = rootVisualElement . Q < VisualElement > ( "status-indicator" ) ;
connectionStatusLabel = rootVisualElement . Q < Label > ( "connection-status" ) ;
connectionToggleButton = rootVisualElement . Q < Button > ( "connection-toggle" ) ;
healthIndicator = rootVisualElement . Q < VisualElement > ( "health-indicator" ) ;
healthStatusLabel = rootVisualElement . Q < Label > ( "health-status" ) ;
testConnectionButton = rootVisualElement . Q < Button > ( "test-connection-button" ) ;
serverStatusBanner = rootVisualElement . Q < VisualElement > ( "server-status-banner" ) ;
serverStatusMessage = rootVisualElement . Q < Label > ( "server-status-message" ) ;
downloadServerButton = rootVisualElement . Q < Button > ( "download-server-button" ) ;
rebuildServerButton = rootVisualElement . Q < Button > ( "rebuild-server-button" ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Client
clientDropdown = rootVisualElement . Q < DropdownField > ( "client-dropdown" ) ;
configureAllButton = rootVisualElement . Q < Button > ( "configure-all-button" ) ;
clientStatusIndicator = rootVisualElement . Q < VisualElement > ( "client-status-indicator" ) ;
clientStatusLabel = rootVisualElement . Q < Label > ( "client-status" ) ;
configureButton = rootVisualElement . Q < Button > ( "configure-button" ) ;
claudeCliPathRow = rootVisualElement . Q < VisualElement > ( "claude-cli-path-row" ) ;
claudeCliPath = rootVisualElement . Q < TextField > ( "claude-cli-path" ) ;
browseClaudeButton = rootVisualElement . Q < Button > ( "browse-claude-button" ) ;
manualConfigFoldout = rootVisualElement . Q < Foldout > ( "manual-config-foldout" ) ;
configPathField = rootVisualElement . Q < TextField > ( "config-path" ) ;
copyPathButton = rootVisualElement . Q < Button > ( "copy-path-button" ) ;
openFileButton = rootVisualElement . Q < Button > ( "open-file-button" ) ;
configJsonField = rootVisualElement . Q < TextField > ( "config-json" ) ;
copyJsonButton = rootVisualElement . Q < Button > ( "copy-json-button" ) ;
installationStepsLabel = rootVisualElement . Q < Label > ( "installation-steps" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void InitializeUI ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
// Settings Section
UpdateVersionLabel ( ) ;
debugLogsToggle . value = EditorPrefs . GetBool ( "MCPForUnity.DebugLogs" , false ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
validationLevelField . Init ( ValidationLevel . Standard ) ;
int savedLevel = EditorPrefs . GetInt ( "MCPForUnity.ValidationLevel" , 1 ) ;
currentValidationLevel = ( ValidationLevel ) Mathf . Clamp ( savedLevel , 0 , 3 ) ;
validationLevelField . value = currentValidationLevel ;
UpdateValidationDescription ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Advanced settings starts collapsed
advancedSettingsFoldout . value = false ;
// Connection Section
protocolDropdown . Init ( ConnectionProtocol . Stdio ) ;
protocolDropdown . SetEnabled ( false ) ; // Disabled for now, only stdio supported
unityPortField . value = MCPServiceLocator . Bridge . CurrentPort . ToString ( ) ;
serverPortField . value = "6500" ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Client Configuration
var clientNames = mcpClients . clients . Select ( c = > c . name ) . ToList ( ) ;
clientDropdown . choices = clientNames ;
if ( clientNames . Count > 0 )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
clientDropdown . index = 0 ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Manual config starts collapsed
manualConfigFoldout . value = false ;
// Claude CLI path row hidden by default
claudeCliPathRow . style . display = DisplayStyle . None ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void RegisterCallbacks ( )
{
// Settings callbacks
debugLogsToggle . RegisterValueChangedCallback ( evt = >
{
EditorPrefs . SetBool ( "MCPForUnity.DebugLogs" , evt . newValue ) ;
} ) ;
validationLevelField . RegisterValueChangedCallback ( evt = >
{
currentValidationLevel = ( ValidationLevel ) evt . newValue ;
EditorPrefs . SetInt ( "MCPForUnity.ValidationLevel" , ( int ) currentValidationLevel ) ;
UpdateValidationDescription ( ) ;
} ) ;
// Advanced settings callbacks
browsePythonButton . clicked + = OnBrowsePythonClicked ;
clearPythonButton . clicked + = OnClearPythonClicked ;
browseUvButton . clicked + = OnBrowseUvClicked ;
clearUvButton . clicked + = OnClearUvClicked ;
// Connection callbacks
connectionToggleButton . clicked + = OnConnectionToggleClicked ;
testConnectionButton . clicked + = OnTestConnectionClicked ;
downloadServerButton . clicked + = OnDownloadServerClicked ;
rebuildServerButton . clicked + = OnRebuildServerClicked ;
// Client callbacks
clientDropdown . RegisterValueChangedCallback ( evt = >
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
selectedClientIndex = clientDropdown . index ;
UpdateClientStatus ( ) ;
UpdateManualConfiguration ( ) ;
UpdateClaudeCliPathVisibility ( ) ;
} ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
configureAllButton . clicked + = OnConfigureAllClientsClicked ;
configureButton . clicked + = OnConfigureClicked ;
browseClaudeButton . clicked + = OnBrowseClaudeClicked ;
copyPathButton . clicked + = OnCopyPathClicked ;
openFileButton . clicked + = OnOpenFileClicked ;
copyJsonButton . clicked + = OnCopyJsonClicked ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void UpdateValidationDescription ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
validationDescription . text = GetValidationLevelDescription ( ( int ) currentValidationLevel ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private string GetValidationLevelDescription ( int index )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
return index switch
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
0 = > "Only basic syntax checks (braces, quotes, comments)" ,
1 = > "Syntax checks + Unity best practices and warnings" ,
2 = > "All checks + semantic analysis and performance warnings" ,
3 = > "Full semantic validation with namespace/type resolution (requires Roslyn)" ,
_ = > "Standard validation"
} ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void UpdateConnectionStatus ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
var bridgeService = MCPServiceLocator . Bridge ;
bool isRunning = bridgeService . IsRunning ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
if ( isRunning )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
connectionStatusLabel . text = "Connected" ;
statusIndicator . RemoveFromClassList ( "disconnected" ) ;
statusIndicator . AddToClassList ( "connected" ) ;
connectionToggleButton . text = "Stop" ;
}
else
{
connectionStatusLabel . text = "Disconnected" ;
statusIndicator . RemoveFromClassList ( "connected" ) ;
statusIndicator . AddToClassList ( "disconnected" ) ;
connectionToggleButton . text = "Start" ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Reset health status when disconnected
healthStatusLabel . text = "Unknown" ;
healthIndicator . RemoveFromClassList ( "healthy" ) ;
healthIndicator . RemoveFromClassList ( "warning" ) ;
healthIndicator . AddToClassList ( "unknown" ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Update ports
unityPortField . value = bridgeService . CurrentPort . ToString ( ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void UpdateClientStatus ( )
{
if ( selectedClientIndex < 0 | | selectedClientIndex > = mcpClients . clients . Count )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
var client = mcpClients . clients [ selectedClientIndex ] ;
MCPServiceLocator . Client . CheckClientStatus ( client ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
clientStatusLabel . text = client . GetStatusDisplayString ( ) ;
// Reset inline color style (clear error state from OnConfigureClicked)
clientStatusLabel . style . color = StyleKeyword . Null ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Update status indicator color
clientStatusIndicator . RemoveFromClassList ( "configured" ) ;
clientStatusIndicator . RemoveFromClassList ( "not-configured" ) ;
clientStatusIndicator . RemoveFromClassList ( "warning" ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
switch ( client . status )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
case McpStatus . Configured :
case McpStatus . Running :
case McpStatus . Connected :
clientStatusIndicator . AddToClassList ( "configured" ) ;
break ;
case McpStatus . IncorrectPath :
case McpStatus . CommunicationError :
case McpStatus . NoResponse :
clientStatusIndicator . AddToClassList ( "warning" ) ;
break ;
default :
clientStatusIndicator . AddToClassList ( "not-configured" ) ;
break ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Update configure button text for Claude Code
if ( client . mcpType = = McpTypes . ClaudeCode )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
bool isConfigured = client . status = = McpStatus . Configured ;
configureButton . text = isConfigured ? "Unregister" : "Register" ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
configureButton . text = "Configure" ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void UpdateManualConfiguration ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
if ( selectedClientIndex < 0 | | selectedClientIndex > = mcpClients . clients . Count )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
var client = mcpClients . clients [ selectedClientIndex ] ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Get config path
string configPath = MCPServiceLocator . Client . GetConfigPath ( client ) ;
configPathField . value = configPath ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Get config JSON
string configJson = MCPServiceLocator . Client . GenerateConfigJson ( client ) ;
configJsonField . value = configJson ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Get installation steps
string steps = MCPServiceLocator . Client . GetInstallationSteps ( client ) ;
installationStepsLabel . text = steps ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void UpdateClaudeCliPathVisibility ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
if ( selectedClientIndex < 0 | | selectedClientIndex > = mcpClients . clients . Count )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
var client = mcpClients . clients [ selectedClientIndex ] ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Show Claude CLI path only for Claude Code client
if ( client . mcpType = = McpTypes . ClaudeCode )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
string claudePath = MCPServiceLocator . Paths . GetClaudeCliPath ( ) ;
if ( string . IsNullOrEmpty ( claudePath ) )
{
// Show path selector if not found
claudeCliPathRow . style . display = DisplayStyle . Flex ;
claudeCliPath . value = "Not found - click Browse to select" ;
}
else
{
// Show detected path
claudeCliPathRow . style . display = DisplayStyle . Flex ;
claudeCliPath . value = claudePath ;
}
}
else
{
claudeCliPathRow . style . display = DisplayStyle . None ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void UpdatePathOverrides ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
var pathService = MCPServiceLocator . Paths ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// MCP Server Path
string mcpServerPath = pathService . GetMcpServerPath ( ) ;
if ( pathService . HasMcpServerOverride )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
mcpServerPathOverride . value = mcpServerPath ? ? "(override set but invalid)" ;
}
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
mcpServerPathOverride . value = mcpServerPath ? ? "(auto-detected)" ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Update status indicator
mcpServerPathStatus . RemoveFromClassList ( "valid" ) ;
mcpServerPathStatus . RemoveFromClassList ( "invalid" ) ;
if ( ! string . IsNullOrEmpty ( mcpServerPath ) & & File . Exists ( Path . Combine ( mcpServerPath , "server.py" ) ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
mcpServerPathStatus . AddToClassList ( "valid" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
mcpServerPathStatus . AddToClassList ( "invalid" ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// UV Path
string uvPath = pathService . GetUvPath ( ) ;
if ( pathService . HasUvPathOverride )
{
uvPathOverride . value = uvPath ? ? "(override set but invalid)" ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
uvPathOverride . value = uvPath ? ? "(auto-detected)" ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Update status indicator
uvPathStatus . RemoveFromClassList ( "valid" ) ;
uvPathStatus . RemoveFromClassList ( "invalid" ) ;
if ( ! string . IsNullOrEmpty ( uvPath ) & & File . Exists ( uvPath ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
uvPathStatus . AddToClassList ( "valid" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
uvPathStatus . AddToClassList ( "invalid" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
// Button callbacks
private void OnConnectionToggleClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
var bridgeService = MCPServiceLocator . Bridge ;
if ( bridgeService . IsRunning )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
bridgeService . Stop ( ) ;
}
else
{
bridgeService . Start ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Verify connection after starting (Option C: verify on connect)
EditorApplication . delayCall + = ( ) = >
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
if ( bridgeService . IsRunning )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
VerifyBridgeConnection ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
} ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
UpdateConnectionStatus ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnTestConnectionClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
VerifyBridgeConnection ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void VerifyBridgeConnection ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
var bridgeService = MCPServiceLocator . Bridge ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
if ( ! bridgeService . IsRunning )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
healthStatusLabel . text = "Disconnected" ;
healthIndicator . RemoveFromClassList ( "healthy" ) ;
healthIndicator . RemoveFromClassList ( "warning" ) ;
healthIndicator . AddToClassList ( "unknown" ) ;
McpLog . Warn ( "Cannot verify connection: Bridge is not running" ) ;
return ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
var result = bridgeService . Verify ( bridgeService . CurrentPort ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
healthIndicator . RemoveFromClassList ( "healthy" ) ;
healthIndicator . RemoveFromClassList ( "warning" ) ;
healthIndicator . RemoveFromClassList ( "unknown" ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
if ( result . Success & & result . PingSucceeded )
{
healthStatusLabel . text = "Healthy" ;
healthIndicator . AddToClassList ( "healthy" ) ;
McpLog . Info ( "Bridge verification successful" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else if ( result . HandshakeValid )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
healthStatusLabel . text = "Ping Failed" ;
healthIndicator . AddToClassList ( "warning" ) ;
McpLog . Warn ( $"Bridge verification warning: {result.Message}" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
healthStatusLabel . text = "Unhealthy" ;
healthIndicator . AddToClassList ( "warning" ) ;
McpLog . Error ( $"Bridge verification failed: {result.Message}" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnDownloadServerClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
if ( ServerInstaller . DownloadAndInstallServer ( ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
UpdateServerStatusBanner ( ) ;
UpdatePathOverrides ( ) ;
EditorUtility . DisplayDialog (
"Download Complete" ,
"Server installed successfully! Start your connection and configure your MCP clients to begin." ,
"OK"
) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnRebuildServerClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
try
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
bool success = ServerInstaller . RebuildMcpServer ( ) ;
if ( success )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "MCP For Unity" , "Server rebuilt successfully." , "OK" ) ;
UpdateServerStatusBanner ( ) ;
UpdatePathOverrides ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "MCP For Unity" , "Rebuild failed. Please check Console for details." , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
McpLog . Error ( $"Failed to rebuild server: {ex.Message}" ) ;
EditorUtility . DisplayDialog ( "MCP For Unity" , $"Rebuild failed: {ex.Message}" , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void UpdateServerStatusBanner ( )
{
bool hasEmbedded = ServerInstaller . HasEmbeddedServer ( ) ;
string installedVer = ServerInstaller . GetInstalledServerVersion ( ) ;
string packageVer = AssetPathUtility . GetPackageVersion ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Show/hide download vs rebuild buttons
if ( hasEmbedded )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
downloadServerButton . style . display = DisplayStyle . None ;
rebuildServerButton . style . display = DisplayStyle . Flex ;
2025-10-04 08:23:28 +08:00
}
else
{
2025-10-24 12:50:29 +08:00
downloadServerButton . style . display = DisplayStyle . Flex ;
rebuildServerButton . style . display = DisplayStyle . None ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Check for installation errors first
string installError = PackageLifecycleManager . GetLastInstallError ( ) ;
if ( ! string . IsNullOrEmpty ( installError ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
serverStatusMessage . text = $"\u274C Server installation failed: {installError}. Click 'Rebuild Server' to retry." ;
serverStatusBanner . style . display = DisplayStyle . Flex ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
// Update banner
else if ( ! hasEmbedded & & string . IsNullOrEmpty ( installedVer ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
serverStatusMessage . text = "\u26A0 Server not installed. Click 'Download & Install Server' to get started." ;
serverStatusBanner . style . display = DisplayStyle . Flex ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
else if ( ! hasEmbedded & & ! string . IsNullOrEmpty ( installedVer ) & & installedVer ! = packageVer )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
serverStatusMessage . text = $"\u26A0 Server update available (v{installedVer} \u2192 v{packageVer}). Update recommended." ;
serverStatusBanner . style . display = DisplayStyle . Flex ;
2025-10-04 08:23:28 +08:00
}
else
{
2025-10-24 12:50:29 +08:00
serverStatusBanner . style . display = DisplayStyle . None ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnConfigureAllClientsClicked ( )
2025-10-04 08:23:28 +08:00
{
try
{
2025-10-24 12:50:29 +08:00
var summary = MCPServiceLocator . Client . ConfigureAllDetectedClients ( ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Build detailed message
string message = summary . GetSummaryMessage ( ) + "\n\n" ;
foreach ( var msg in summary . Messages )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
message + = msg + "\n" ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Configure All Clients" , message , "OK" ) ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Refresh current client status
if ( selectedClientIndex > = 0 & & selectedClientIndex < mcpClients . clients . Count )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
UpdateClientStatus ( ) ;
UpdateManualConfiguration ( ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Configuration Failed" , ex . Message , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnConfigureClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
if ( selectedClientIndex < 0 | | selectedClientIndex > = mcpClients . clients . Count )
return ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
var client = mcpClients . clients [ selectedClientIndex ] ;
2025-10-04 08:23:28 +08:00
try
{
2025-10-24 12:50:29 +08:00
if ( client . mcpType = = McpTypes . ClaudeCode )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
bool isConfigured = client . status = = McpStatus . Configured ;
if ( isConfigured )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Client . UnregisterClaudeCode ( ) ;
2025-10-04 08:23:28 +08:00
}
else
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Client . RegisterClaudeCode ( ) ;
2025-10-04 08:23:28 +08:00
}
}
else
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Client . ConfigureClient ( client ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
UpdateClientStatus ( ) ;
UpdateManualConfiguration ( ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
clientStatusLabel . text = "Error" ;
clientStatusLabel . style . color = Color . red ;
McpLog . Error ( $"Configuration failed: {ex.Message}" ) ;
EditorUtility . DisplayDialog ( "Configuration Failed" , ex . Message , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnBrowsePythonClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
string picked = EditorUtility . OpenFolderPanel ( "Select MCP Server Directory" , Application . dataPath , "" ) ;
if ( ! string . IsNullOrEmpty ( picked ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
try
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Paths . SetMcpServerOverride ( picked ) ;
UpdatePathOverrides ( ) ;
McpLog . Info ( $"MCP server path override set to: {picked}" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Invalid Path" , ex . Message , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
}
2025-10-24 12:50:29 +08:00
private void OnClearPythonClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Paths . ClearMcpServerOverride ( ) ;
UpdatePathOverrides ( ) ;
McpLog . Info ( "MCP server path override cleared" ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void OnBrowseUvClicked ( )
{
string suggested = RuntimeInformation . IsOSPlatform ( OSPlatform . OSX )
? "/opt/homebrew/bin"
: Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) ;
string picked = EditorUtility . OpenFilePanel ( "Select UV Executable" , suggested , "" ) ;
if ( ! string . IsNullOrEmpty ( picked ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
try
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Paths . SetUvPathOverride ( picked ) ;
UpdatePathOverrides ( ) ;
McpLog . Info ( $"UV path override set to: {picked}" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Invalid Path" , ex . Message , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void OnClearUvClicked ( )
{
MCPServiceLocator . Paths . ClearUvPathOverride ( ) ;
UpdatePathOverrides ( ) ;
McpLog . Info ( "UV path override cleared" ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void OnBrowseClaudeClicked ( )
{
string suggested = RuntimeInformation . IsOSPlatform ( OSPlatform . OSX )
? "/opt/homebrew/bin"
: Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) ;
string picked = EditorUtility . OpenFilePanel ( "Select Claude CLI" , suggested , "" ) ;
if ( ! string . IsNullOrEmpty ( picked ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
try
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
MCPServiceLocator . Paths . SetClaudeCliPathOverride ( picked ) ;
UpdateClaudeCliPathVisibility ( ) ;
UpdateClientStatus ( ) ;
McpLog . Info ( $"Claude CLI path override set to: {picked}" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Invalid Path" , ex . Message , "OK" ) ;
2025-10-04 08:23:28 +08:00
}
}
}
2025-10-24 12:50:29 +08:00
private void OnCopyPathClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorGUIUtility . systemCopyBuffer = configPathField . value ;
McpLog . Info ( "Config path copied to clipboard" ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
private void OnOpenFileClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
string path = configPathField . value ;
2025-10-04 08:23:28 +08:00
try
{
2025-10-24 12:50:29 +08:00
if ( ! File . Exists ( path ) )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorUtility . DisplayDialog ( "Open File" , "The configuration file path does not exist." , "OK" ) ;
2025-10-04 08:23:28 +08:00
return ;
}
2025-10-24 12:50:29 +08:00
Process . Start ( new ProcessStartInfo
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
FileName = path ,
UseShellExecute = true
} ) ;
2025-10-04 08:23:28 +08:00
}
2025-10-24 12:50:29 +08:00
catch ( Exception ex )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
McpLog . Error ( $"Failed to open file: {ex.Message}" ) ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
private void OnCopyJsonClicked ( )
2025-10-04 08:23:28 +08:00
{
2025-10-24 12:50:29 +08:00
EditorGUIUtility . systemCopyBuffer = configJsonField . value ;
McpLog . Info ( "Configuration copied to clipboard" ) ;
}
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
private void UpdateVersionLabel ( )
{
string currentVersion = AssetPathUtility . GetPackageVersion ( ) ;
versionLabel . text = $"v{currentVersion}" ;
2025-10-04 08:23:28 +08:00
2025-10-24 12:50:29 +08:00
// Check for updates using the service
var updateCheck = MCPServiceLocator . Updates . CheckForUpdate ( currentVersion ) ;
if ( updateCheck . UpdateAvailable & & ! string . IsNullOrEmpty ( updateCheck . LatestVersion ) )
{
// Update available - enhance the label
versionLabel . text = $"\u2191 v{currentVersion} (Update available: v{updateCheck.LatestVersion})" ;
versionLabel . style . color = new Color ( 1f , 0.7f , 0f ) ; // Orange
versionLabel . tooltip = $"Version {updateCheck.LatestVersion} is available. Update via Package Manager.\n\nGit URL: https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity" ;
}
else
{
versionLabel . style . color = StyleKeyword . Null ; // Default color
versionLabel . tooltip = $"Current version: {currentVersion}" ;
2025-10-04 08:23:28 +08:00
}
}
2025-10-24 12:50:29 +08:00
2025-10-04 08:23:28 +08:00
}
}