Code Validation Update
1. Update the code validation feature. With Roslyn installed(see guide), MCP clients will receive detailed error messages and making the code generation process more error-proof 2. Minor update to the EditorWindow, making more space for upcoming features 3. Readme update to include Code validation guides 4. Minor bug fixes including installation bugs from previous VSC PRmain
parent
6906c24290
commit
9101105212
26
README.md
26
README.md
|
|
@ -73,6 +73,27 @@ Unity MCP connects your tools using two components:
|
|||
* [Cursor](https://www.cursor.com/en/downloads)
|
||||
* [Visual Studio Code Copilot](https://code.visualstudio.com/docs/copilot/overview)
|
||||
* *(Others may work with manual config)*
|
||||
* <details> <summary><strong>[Optional] Roslyn for Advanced Script Validation</strong></summary>
|
||||
|
||||
For **Strict** validation level that catches undefined namespaces, types, and methods:
|
||||
|
||||
**Method 1: NuGet for Unity (Recommended)**
|
||||
1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity)
|
||||
2. Go to `Window > NuGet Package Manager`
|
||||
3. Search for `Microsoft.CodeAnalysis.CSharp` and install the package
|
||||
5. Go to `Player Settings > Scripting Define Symbols`
|
||||
6. Add `USE_ROSLYN`
|
||||
7. Restart Unity
|
||||
|
||||
**Method 2: Manual DLL Installation**
|
||||
1. Download Microsoft.CodeAnalysis.CSharp.dll and dependencies from [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/)
|
||||
2. Place DLLs in `Assets/Plugins/` folder
|
||||
3. Ensure .NET compatibility settings are correct
|
||||
4. Add `USE_ROSLYN` to Scripting Define Symbols
|
||||
5. Restart Unity
|
||||
|
||||
**Note:** Without Roslyn, script validation falls back to basic structural checks. Roslyn enables full C# compiler diagnostics with precise error reporting.</details>
|
||||
|
||||
|
||||
### Step 1: Install the Unity Package (Bridge)
|
||||
|
||||
|
|
@ -192,7 +213,7 @@ If Auto-Configure fails or you use a different client:
|
|||
|
||||
### 🔴 High Priority
|
||||
- [ ] **Asset Generation Improvements** - Enhanced server request handling and asset pipeline optimization
|
||||
- [ ] **Code Generation Enhancements** - Improved generated code quality, validation, and error handling
|
||||
- [ ] **Code Generation Enhancements** - Improved generated code quality and error handling
|
||||
- [ ] **Robust Error Handling** - Comprehensive error messages, recovery mechanisms, and graceful degradation
|
||||
- [ ] **Remote Connection Support** - Enable seamless remote connection between Unity host and MCP server
|
||||
- [ ] **Documentation Expansion** - Complete tutorials for custom tool creation and API reference
|
||||
|
|
@ -210,6 +231,7 @@ If Auto-Configure fails or you use a different client:
|
|||
<summary><strong>✅ Completed Features<strong></summary>
|
||||
|
||||
- [x] **Shader Generation** - Generate shaders using CGProgram template
|
||||
- [x] **Advanced Script Validation** - Multi-level validation with semantic analysis, namespace/type checking, and Unity best practices (Will need Roslyn Installed, see [Prerequisite](#prerequisites)).
|
||||
</details>
|
||||
|
||||
### 🔬 Research & Exploration
|
||||
|
|
@ -295,4 +317,4 @@ Thanks to the contributors and the Unity team.
|
|||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#unity-mcp/unity-mcp&justinpbarnett/unity-mcp&Date)
|
||||
[](https://www.star-history.com/#justinpbarnett/unity-mcp&Date)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,43 @@ using UnityEditor;
|
|||
using UnityEngine;
|
||||
using UnityMcpBridge.Editor.Helpers;
|
||||
|
||||
#if USE_ROSLYN
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Compilation;
|
||||
#endif
|
||||
|
||||
|
||||
namespace UnityMcpBridge.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles CRUD operations for C# scripts within the Unity project.
|
||||
///
|
||||
/// ROSLYN INSTALLATION GUIDE:
|
||||
/// To enable advanced syntax validation with Roslyn compiler services:
|
||||
///
|
||||
/// 1. Install Microsoft.CodeAnalysis.CSharp NuGet package:
|
||||
/// - Open Package Manager in Unity
|
||||
/// - Follow the instruction on https://github.com/GlitchEnzo/NuGetForUnity
|
||||
///
|
||||
/// 2. Open NuGet Package Manager and Install Microsoft.CodeAnalysis.CSharp:
|
||||
///
|
||||
/// 3. Alternative: Manual DLL installation:
|
||||
/// - Download Microsoft.CodeAnalysis.CSharp.dll and dependencies
|
||||
/// - Place in Assets/Plugins/ folder
|
||||
/// - Ensure .NET compatibility settings are correct
|
||||
///
|
||||
/// 4. Define USE_ROSLYN symbol:
|
||||
/// - Go to Player Settings > Scripting Define Symbols
|
||||
/// - Add "USE_ROSLYN" to enable Roslyn-based validation
|
||||
///
|
||||
/// 5. Restart Unity after installation
|
||||
///
|
||||
/// Note: Without Roslyn, the system falls back to basic structural validation.
|
||||
/// Roslyn provides full C# compiler diagnostics with line numbers and detailed error messages.
|
||||
/// </summary>
|
||||
public static class ManageScript
|
||||
{
|
||||
|
|
@ -168,12 +201,18 @@ namespace UnityMcpBridge.Editor.Tools
|
|||
contents = GenerateDefaultScriptContent(name, scriptType, namespaceName);
|
||||
}
|
||||
|
||||
// Validate syntax (basic check)
|
||||
if (!ValidateScriptSyntax(contents))
|
||||
// Validate syntax with detailed error reporting using GUI setting
|
||||
ValidationLevel validationLevel = GetValidationLevelFromGUI();
|
||||
bool isValid = ValidateScriptSyntax(contents, validationLevel, out string[] validationErrors);
|
||||
if (!isValid)
|
||||
{
|
||||
// Optionally return a specific error or warning about syntax
|
||||
// return Response.Error("Provided script content has potential syntax errors.");
|
||||
Debug.LogWarning($"Potential syntax error in script being created: {name}");
|
||||
string errorMessage = "Script validation failed:\n" + string.Join("\n", validationErrors);
|
||||
return Response.Error(errorMessage);
|
||||
}
|
||||
else if (validationErrors != null && validationErrors.Length > 0)
|
||||
{
|
||||
// Log warnings but don't block creation
|
||||
Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -243,11 +282,18 @@ namespace UnityMcpBridge.Editor.Tools
|
|||
return Response.Error("Content is required for the 'update' action.");
|
||||
}
|
||||
|
||||
// Validate syntax (basic check)
|
||||
if (!ValidateScriptSyntax(contents))
|
||||
// Validate syntax with detailed error reporting using GUI setting
|
||||
ValidationLevel validationLevel = GetValidationLevelFromGUI();
|
||||
bool isValid = ValidateScriptSyntax(contents, validationLevel, out string[] validationErrors);
|
||||
if (!isValid)
|
||||
{
|
||||
Debug.LogWarning($"Potential syntax error in script being updated: {name}");
|
||||
// Consider if this should be a hard error or just a warning
|
||||
string errorMessage = "Script validation failed:\n" + string.Join("\n", validationErrors);
|
||||
return Response.Error(errorMessage);
|
||||
}
|
||||
else if (validationErrors != null && validationErrors.Length > 0)
|
||||
{
|
||||
// Log warnings but don't block update
|
||||
Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -361,27 +407,624 @@ namespace UnityMcpBridge.Editor.Tools
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a very basic syntax validation (checks for balanced braces).
|
||||
/// TODO: Implement more robust syntax checking if possible.
|
||||
/// Gets the validation level from the GUI settings
|
||||
/// </summary>
|
||||
private static ValidationLevel GetValidationLevelFromGUI()
|
||||
{
|
||||
string savedLevel = EditorPrefs.GetString("UnityMCP_ScriptValidationLevel", "standard");
|
||||
return savedLevel.ToLower() switch
|
||||
{
|
||||
"basic" => ValidationLevel.Basic,
|
||||
"standard" => ValidationLevel.Standard,
|
||||
"comprehensive" => ValidationLevel.Comprehensive,
|
||||
"strict" => ValidationLevel.Strict,
|
||||
_ => ValidationLevel.Standard // Default fallback
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates C# script syntax using multiple validation layers.
|
||||
/// </summary>
|
||||
private static bool ValidateScriptSyntax(string contents)
|
||||
{
|
||||
if (string.IsNullOrEmpty(contents))
|
||||
return true; // Empty is technically valid?
|
||||
return ValidateScriptSyntax(contents, ValidationLevel.Standard, out _);
|
||||
}
|
||||
|
||||
int braceBalance = 0;
|
||||
foreach (char c in contents)
|
||||
/// <summary>
|
||||
/// Advanced syntax validation with detailed diagnostics and configurable strictness.
|
||||
/// </summary>
|
||||
private static bool ValidateScriptSyntax(string contents, ValidationLevel level, out string[] errors)
|
||||
{
|
||||
var errorList = new System.Collections.Generic.List<string>();
|
||||
errors = null;
|
||||
|
||||
if (string.IsNullOrEmpty(contents))
|
||||
{
|
||||
if (c == '{')
|
||||
braceBalance++;
|
||||
else if (c == '}')
|
||||
braceBalance--;
|
||||
return true; // Empty content is valid
|
||||
}
|
||||
|
||||
return braceBalance == 0;
|
||||
// This is extremely basic. A real C# parser/compiler check would be ideal
|
||||
// but is complex to implement directly here.
|
||||
// Basic structural validation
|
||||
if (!ValidateBasicStructure(contents, errorList))
|
||||
{
|
||||
errors = errorList.ToArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_ROSLYN
|
||||
// Advanced Roslyn-based validation
|
||||
if (!ValidateScriptSyntaxRoslyn(contents, level, errorList))
|
||||
{
|
||||
errors = errorList.ToArray();
|
||||
return level != ValidationLevel.Standard; //TODO: Allow standard to run roslyn right now, might formalize it in the future
|
||||
}
|
||||
#endif
|
||||
|
||||
// Unity-specific validation
|
||||
if (level >= ValidationLevel.Standard)
|
||||
{
|
||||
ValidateScriptSyntaxUnity(contents, errorList);
|
||||
}
|
||||
|
||||
// Semantic analysis for common issues
|
||||
if (level >= ValidationLevel.Comprehensive)
|
||||
{
|
||||
ValidateSemanticRules(contents, errorList);
|
||||
}
|
||||
|
||||
#if USE_ROSLYN
|
||||
// Full semantic compilation validation for Strict level
|
||||
if (level == ValidationLevel.Strict)
|
||||
{
|
||||
if (!ValidateScriptSemantics(contents, errorList))
|
||||
{
|
||||
errors = errorList.ToArray();
|
||||
return false; // Strict level fails on any semantic errors
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
errors = errorList.ToArray();
|
||||
return errorList.Count == 0 || (level != ValidationLevel.Strict && !errorList.Any(e => e.StartsWith("ERROR:")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validation strictness levels
|
||||
/// </summary>
|
||||
private enum ValidationLevel
|
||||
{
|
||||
Basic, // Only syntax errors
|
||||
Standard, // Syntax + Unity best practices
|
||||
Comprehensive, // All checks + semantic analysis
|
||||
Strict // Treat all issues as errors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates basic code structure (braces, quotes, comments)
|
||||
/// </summary>
|
||||
private static bool ValidateBasicStructure(string contents, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
bool isValid = true;
|
||||
int braceBalance = 0;
|
||||
int parenBalance = 0;
|
||||
int bracketBalance = 0;
|
||||
bool inStringLiteral = false;
|
||||
bool inCharLiteral = false;
|
||||
bool inSingleLineComment = false;
|
||||
bool inMultiLineComment = false;
|
||||
bool escaped = false;
|
||||
|
||||
for (int i = 0; i < contents.Length; i++)
|
||||
{
|
||||
char c = contents[i];
|
||||
char next = i + 1 < contents.Length ? contents[i + 1] : '\0';
|
||||
|
||||
// Handle escape sequences
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\' && (inStringLiteral || inCharLiteral))
|
||||
{
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle comments
|
||||
if (!inStringLiteral && !inCharLiteral)
|
||||
{
|
||||
if (c == '/' && next == '/' && !inMultiLineComment)
|
||||
{
|
||||
inSingleLineComment = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && next == '*' && !inSingleLineComment)
|
||||
{
|
||||
inMultiLineComment = true;
|
||||
i++; // Skip next character
|
||||
continue;
|
||||
}
|
||||
if (c == '*' && next == '/' && inMultiLineComment)
|
||||
{
|
||||
inMultiLineComment = false;
|
||||
i++; // Skip next character
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
inSingleLineComment = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inSingleLineComment || inMultiLineComment)
|
||||
continue;
|
||||
|
||||
// Handle string and character literals
|
||||
if (c == '"' && !inCharLiteral)
|
||||
{
|
||||
inStringLiteral = !inStringLiteral;
|
||||
continue;
|
||||
}
|
||||
if (c == '\'' && !inStringLiteral)
|
||||
{
|
||||
inCharLiteral = !inCharLiteral;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inStringLiteral || inCharLiteral)
|
||||
continue;
|
||||
|
||||
// Count brackets and braces
|
||||
switch (c)
|
||||
{
|
||||
case '{': braceBalance++; break;
|
||||
case '}': braceBalance--; break;
|
||||
case '(': parenBalance++; break;
|
||||
case ')': parenBalance--; break;
|
||||
case '[': bracketBalance++; break;
|
||||
case ']': bracketBalance--; break;
|
||||
}
|
||||
|
||||
// Check for negative balances (closing without opening)
|
||||
if (braceBalance < 0)
|
||||
{
|
||||
errors.Add("ERROR: Unmatched closing brace '}'");
|
||||
isValid = false;
|
||||
}
|
||||
if (parenBalance < 0)
|
||||
{
|
||||
errors.Add("ERROR: Unmatched closing parenthesis ')'");
|
||||
isValid = false;
|
||||
}
|
||||
if (bracketBalance < 0)
|
||||
{
|
||||
errors.Add("ERROR: Unmatched closing bracket ']'");
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check final balances
|
||||
if (braceBalance != 0)
|
||||
{
|
||||
errors.Add($"ERROR: Unbalanced braces (difference: {braceBalance})");
|
||||
isValid = false;
|
||||
}
|
||||
if (parenBalance != 0)
|
||||
{
|
||||
errors.Add($"ERROR: Unbalanced parentheses (difference: {parenBalance})");
|
||||
isValid = false;
|
||||
}
|
||||
if (bracketBalance != 0)
|
||||
{
|
||||
errors.Add($"ERROR: Unbalanced brackets (difference: {bracketBalance})");
|
||||
isValid = false;
|
||||
}
|
||||
if (inStringLiteral)
|
||||
{
|
||||
errors.Add("ERROR: Unterminated string literal");
|
||||
isValid = false;
|
||||
}
|
||||
if (inCharLiteral)
|
||||
{
|
||||
errors.Add("ERROR: Unterminated character literal");
|
||||
isValid = false;
|
||||
}
|
||||
if (inMultiLineComment)
|
||||
{
|
||||
errors.Add("WARNING: Unterminated multi-line comment");
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
#if USE_ROSLYN
|
||||
/// <summary>
|
||||
/// Cached compilation references for performance
|
||||
/// </summary>
|
||||
private static System.Collections.Generic.List<MetadataReference> _cachedReferences = null;
|
||||
private static DateTime _cacheTime = DateTime.MinValue;
|
||||
private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Validates syntax using Roslyn compiler services
|
||||
/// </summary>
|
||||
private static bool ValidateScriptSyntaxRoslyn(string contents, ValidationLevel level, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
try
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(contents);
|
||||
var diagnostics = syntaxTree.GetDiagnostics();
|
||||
|
||||
bool hasErrors = false;
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
string severity = diagnostic.Severity.ToString().ToUpper();
|
||||
string message = $"{severity}: {diagnostic.GetMessage()}";
|
||||
|
||||
if (diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
{
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Include warnings in comprehensive mode
|
||||
if (level >= ValidationLevel.Standard || diagnostic.Severity == DiagnosticSeverity.Error) //Also use Standard for now
|
||||
{
|
||||
var location = diagnostic.Location.GetLineSpan();
|
||||
if (location.IsValid)
|
||||
{
|
||||
message += $" (Line {location.StartLinePosition.Line + 1})";
|
||||
}
|
||||
errors.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
return !hasErrors;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"ERROR: Roslyn validation failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates script semantics using full compilation context to catch namespace, type, and method resolution errors
|
||||
/// </summary>
|
||||
private static bool ValidateScriptSemantics(string contents, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get compilation references with caching
|
||||
var references = GetCompilationReferences();
|
||||
if (references == null || references.Count == 0)
|
||||
{
|
||||
errors.Add("WARNING: Could not load compilation references for semantic validation");
|
||||
return true; // Don't fail if we can't get references
|
||||
}
|
||||
|
||||
// Create syntax tree
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(contents);
|
||||
|
||||
// Create compilation with full context
|
||||
var compilation = CSharpCompilation.Create(
|
||||
"TempValidation",
|
||||
new[] { syntaxTree },
|
||||
references,
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
||||
);
|
||||
|
||||
// Get semantic diagnostics - this catches all the issues you mentioned!
|
||||
var diagnostics = compilation.GetDiagnostics();
|
||||
|
||||
bool hasErrors = false;
|
||||
foreach (var diagnostic in diagnostics)
|
||||
{
|
||||
if (diagnostic.Severity == DiagnosticSeverity.Error)
|
||||
{
|
||||
hasErrors = true;
|
||||
var location = diagnostic.Location.GetLineSpan();
|
||||
string locationInfo = location.IsValid ?
|
||||
$" (Line {location.StartLinePosition.Line + 1}, Column {location.StartLinePosition.Character + 1})" : "";
|
||||
|
||||
// Include diagnostic ID for better error identification
|
||||
string diagnosticId = !string.IsNullOrEmpty(diagnostic.Id) ? $" [{diagnostic.Id}]" : "";
|
||||
errors.Add($"ERROR: {diagnostic.GetMessage()}{diagnosticId}{locationInfo}");
|
||||
}
|
||||
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
|
||||
{
|
||||
var location = diagnostic.Location.GetLineSpan();
|
||||
string locationInfo = location.IsValid ?
|
||||
$" (Line {location.StartLinePosition.Line + 1}, Column {location.StartLinePosition.Character + 1})" : "";
|
||||
|
||||
string diagnosticId = !string.IsNullOrEmpty(diagnostic.Id) ? $" [{diagnostic.Id}]" : "";
|
||||
errors.Add($"WARNING: {diagnostic.GetMessage()}{diagnosticId}{locationInfo}");
|
||||
}
|
||||
}
|
||||
|
||||
return !hasErrors;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"ERROR: Semantic validation failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets compilation references with caching for performance
|
||||
/// </summary>
|
||||
private static System.Collections.Generic.List<MetadataReference> GetCompilationReferences()
|
||||
{
|
||||
// Check cache validity
|
||||
if (_cachedReferences != null && DateTime.Now - _cacheTime < CacheExpiry)
|
||||
{
|
||||
return _cachedReferences;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var references = new System.Collections.Generic.List<MetadataReference>();
|
||||
|
||||
// Core .NET assemblies
|
||||
references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); // mscorlib/System.Private.CoreLib
|
||||
references.Add(MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location)); // System.Linq
|
||||
references.Add(MetadataReference.CreateFromFile(typeof(System.Collections.Generic.List<>).Assembly.Location)); // System.Collections
|
||||
|
||||
// Unity assemblies
|
||||
try
|
||||
{
|
||||
references.Add(MetadataReference.CreateFromFile(typeof(UnityEngine.Debug).Assembly.Location)); // UnityEngine
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Could not load UnityEngine assembly: {ex.Message}");
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
references.Add(MetadataReference.CreateFromFile(typeof(UnityEditor.Editor).Assembly.Location)); // UnityEditor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Could not load UnityEditor assembly: {ex.Message}");
|
||||
}
|
||||
|
||||
// Get Unity project assemblies
|
||||
try
|
||||
{
|
||||
var assemblies = CompilationPipeline.GetAssemblies();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
if (File.Exists(assembly.outputPath))
|
||||
{
|
||||
references.Add(MetadataReference.CreateFromFile(assembly.outputPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Could not load Unity project assemblies: {ex.Message}");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Cache the results
|
||||
_cachedReferences = references;
|
||||
_cacheTime = DateTime.Now;
|
||||
|
||||
return references;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to get compilation references: {ex.Message}");
|
||||
return new System.Collections.Generic.List<MetadataReference>();
|
||||
}
|
||||
}
|
||||
#else
|
||||
private static bool ValidateScriptSyntaxRoslyn(string contents, ValidationLevel level, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
// Fallback when Roslyn is not available
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Validates Unity-specific coding rules and best practices
|
||||
/// //TODO: Naive Unity Checks and not really yield any results, need to be improved
|
||||
/// </summary>
|
||||
private static void ValidateScriptSyntaxUnity(string contents, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
// Check for common Unity anti-patterns
|
||||
if (contents.Contains("FindObjectOfType") && contents.Contains("Update()"))
|
||||
{
|
||||
errors.Add("WARNING: FindObjectOfType in Update() can cause performance issues");
|
||||
}
|
||||
|
||||
if (contents.Contains("GameObject.Find") && contents.Contains("Update()"))
|
||||
{
|
||||
errors.Add("WARNING: GameObject.Find in Update() can cause performance issues");
|
||||
}
|
||||
|
||||
// Check for proper MonoBehaviour usage
|
||||
if (contents.Contains(": MonoBehaviour") && !contents.Contains("using UnityEngine"))
|
||||
{
|
||||
errors.Add("WARNING: MonoBehaviour requires 'using UnityEngine;'");
|
||||
}
|
||||
|
||||
// Check for SerializeField usage
|
||||
if (contents.Contains("[SerializeField]") && !contents.Contains("using UnityEngine"))
|
||||
{
|
||||
errors.Add("WARNING: SerializeField requires 'using UnityEngine;'");
|
||||
}
|
||||
|
||||
// Check for proper coroutine usage
|
||||
if (contents.Contains("StartCoroutine") && !contents.Contains("IEnumerator"))
|
||||
{
|
||||
errors.Add("WARNING: StartCoroutine typically requires IEnumerator methods");
|
||||
}
|
||||
|
||||
// Check for Update without FixedUpdate for physics
|
||||
if (contents.Contains("Rigidbody") && contents.Contains("Update()") && !contents.Contains("FixedUpdate()"))
|
||||
{
|
||||
errors.Add("WARNING: Consider using FixedUpdate() for Rigidbody operations");
|
||||
}
|
||||
|
||||
// Check for missing null checks on Unity objects
|
||||
if (contents.Contains("GetComponent<") && !contents.Contains("!= null"))
|
||||
{
|
||||
errors.Add("WARNING: Consider null checking GetComponent results");
|
||||
}
|
||||
|
||||
// Check for proper event function signatures
|
||||
if (contents.Contains("void Start(") && !contents.Contains("void Start()"))
|
||||
{
|
||||
errors.Add("WARNING: Start() should not have parameters");
|
||||
}
|
||||
|
||||
if (contents.Contains("void Update(") && !contents.Contains("void Update()"))
|
||||
{
|
||||
errors.Add("WARNING: Update() should not have parameters");
|
||||
}
|
||||
|
||||
// Check for inefficient string operations
|
||||
if (contents.Contains("Update()") && contents.Contains("\"") && contents.Contains("+"))
|
||||
{
|
||||
errors.Add("WARNING: String concatenation in Update() can cause garbage collection issues");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates semantic rules and common coding issues
|
||||
/// </summary>
|
||||
private static void ValidateSemanticRules(string contents, System.Collections.Generic.List<string> errors)
|
||||
{
|
||||
// Check for potential memory leaks
|
||||
if (contents.Contains("new ") && contents.Contains("Update()"))
|
||||
{
|
||||
errors.Add("WARNING: Creating objects in Update() may cause memory issues");
|
||||
}
|
||||
|
||||
// Check for magic numbers
|
||||
var magicNumberPattern = new Regex(@"\b\d+\.?\d*f?\b(?!\s*[;})\]])");
|
||||
var matches = magicNumberPattern.Matches(contents);
|
||||
if (matches.Count > 5)
|
||||
{
|
||||
errors.Add("WARNING: Consider using named constants instead of magic numbers");
|
||||
}
|
||||
|
||||
// Check for long methods (simple line count check)
|
||||
var methodPattern = new Regex(@"(public|private|protected|internal)?\s*(static)?\s*\w+\s+\w+\s*\([^)]*\)\s*{");
|
||||
var methodMatches = methodPattern.Matches(contents);
|
||||
foreach (Match match in methodMatches)
|
||||
{
|
||||
int startIndex = match.Index;
|
||||
int braceCount = 0;
|
||||
int lineCount = 0;
|
||||
bool inMethod = false;
|
||||
|
||||
for (int i = startIndex; i < contents.Length; i++)
|
||||
{
|
||||
if (contents[i] == '{')
|
||||
{
|
||||
braceCount++;
|
||||
inMethod = true;
|
||||
}
|
||||
else if (contents[i] == '}')
|
||||
{
|
||||
braceCount--;
|
||||
if (braceCount == 0 && inMethod)
|
||||
break;
|
||||
}
|
||||
else if (contents[i] == '\n' && inMethod)
|
||||
{
|
||||
lineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (lineCount > 50)
|
||||
{
|
||||
errors.Add("WARNING: Method is very long, consider breaking it into smaller methods");
|
||||
break; // Only report once
|
||||
}
|
||||
}
|
||||
|
||||
// Check for proper exception handling
|
||||
if (contents.Contains("catch") && contents.Contains("catch()"))
|
||||
{
|
||||
errors.Add("WARNING: Empty catch blocks should be avoided");
|
||||
}
|
||||
|
||||
// Check for proper async/await usage
|
||||
if (contents.Contains("async ") && !contents.Contains("await"))
|
||||
{
|
||||
errors.Add("WARNING: Async method should contain await or return Task");
|
||||
}
|
||||
|
||||
// Check for hardcoded tags and layers
|
||||
if (contents.Contains("\"Player\"") || contents.Contains("\"Enemy\""))
|
||||
{
|
||||
errors.Add("WARNING: Consider using constants for tags instead of hardcoded strings");
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: A easier way for users to update incorrect scripts (now duplicated with the updateScript method and need to also update server side, put aside for now)
|
||||
/// <summary>
|
||||
/// Public method to validate script syntax with configurable validation level
|
||||
/// Returns detailed validation results including errors and warnings
|
||||
/// </summary>
|
||||
// public static object ValidateScript(JObject @params)
|
||||
// {
|
||||
// string contents = @params["contents"]?.ToString();
|
||||
// string validationLevel = @params["validationLevel"]?.ToString() ?? "standard";
|
||||
|
||||
// if (string.IsNullOrEmpty(contents))
|
||||
// {
|
||||
// return Response.Error("Contents parameter is required for validation.");
|
||||
// }
|
||||
|
||||
// // Parse validation level
|
||||
// ValidationLevel level = ValidationLevel.Standard;
|
||||
// switch (validationLevel.ToLower())
|
||||
// {
|
||||
// case "basic": level = ValidationLevel.Basic; break;
|
||||
// case "standard": level = ValidationLevel.Standard; break;
|
||||
// case "comprehensive": level = ValidationLevel.Comprehensive; break;
|
||||
// case "strict": level = ValidationLevel.Strict; break;
|
||||
// default:
|
||||
// return Response.Error($"Invalid validation level: '{validationLevel}'. Valid levels are: basic, standard, comprehensive, strict.");
|
||||
// }
|
||||
|
||||
// // Perform validation
|
||||
// bool isValid = ValidateScriptSyntax(contents, level, out string[] validationErrors);
|
||||
|
||||
// var errors = validationErrors?.Where(e => e.StartsWith("ERROR:")).ToArray() ?? new string[0];
|
||||
// var warnings = validationErrors?.Where(e => e.StartsWith("WARNING:")).ToArray() ?? new string[0];
|
||||
|
||||
// var result = new
|
||||
// {
|
||||
// isValid = isValid,
|
||||
// validationLevel = validationLevel,
|
||||
// errorCount = errors.Length,
|
||||
// warningCount = warnings.Length,
|
||||
// errors = errors,
|
||||
// warnings = warnings,
|
||||
// summary = isValid
|
||||
// ? (warnings.Length > 0 ? $"Validation passed with {warnings.Length} warnings" : "Validation passed with no issues")
|
||||
// : $"Validation failed with {errors.Length} errors and {warnings.Length} warnings"
|
||||
// };
|
||||
|
||||
// if (isValid)
|
||||
// {
|
||||
// return Response.Success("Script validation completed successfully.", result);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return Response.Error("Script validation failed.", result);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEditor;
|
||||
|
|
@ -19,6 +20,19 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
private const int unityPort = 6400; // Hardcoded Unity port
|
||||
private const int mcpPort = 6500; // Hardcoded MCP port
|
||||
private readonly McpClients mcpClients = new();
|
||||
|
||||
// Script validation settings
|
||||
private int validationLevelIndex = 1; // Default to Standard
|
||||
private readonly string[] validationLevelOptions = new string[]
|
||||
{
|
||||
"Basic - Only syntax checks",
|
||||
"Standard - Syntax + Unity practices",
|
||||
"Comprehensive - All checks + semantic analysis",
|
||||
"Strict - Full semantic validation (requires Roslyn)"
|
||||
};
|
||||
|
||||
// UI state
|
||||
private int selectedClientIndex = 0;
|
||||
|
||||
[MenuItem("Window/Unity MCP")]
|
||||
public static void ShowWindow()
|
||||
|
|
@ -35,6 +49,9 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
{
|
||||
CheckMcpConfiguration(mcpClient);
|
||||
}
|
||||
|
||||
// Load validation level setting
|
||||
LoadValidationLevelSetting();
|
||||
}
|
||||
|
||||
private Color GetStatusColor(McpStatus status)
|
||||
|
|
@ -79,164 +96,18 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
}
|
||||
}
|
||||
|
||||
private void ConfigurationSection(McpClient mcpClient)
|
||||
|
||||
private void DrawStatusDot(Rect statusRect, Color statusColor, float size = 12)
|
||||
{
|
||||
// Calculate if we should use half-width layout
|
||||
// Minimum width for half-width layout is 400 pixels
|
||||
bool useHalfWidth = position.width >= 800;
|
||||
float sectionWidth = useHalfWidth ? (position.width / 2) - 15 : position.width - 20;
|
||||
|
||||
// Begin horizontal layout if using half-width
|
||||
if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 0)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
}
|
||||
|
||||
// Begin section with fixed width
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Width(sectionWidth));
|
||||
|
||||
// Header with improved styling
|
||||
EditorGUILayout.Space(5);
|
||||
Rect headerRect = EditorGUILayout.GetControlRect(false, 24);
|
||||
GUI.Label(
|
||||
new Rect(
|
||||
headerRect.x + 8,
|
||||
headerRect.y + 4,
|
||||
headerRect.width - 16,
|
||||
headerRect.height
|
||||
),
|
||||
mcpClient.name + " Configuration",
|
||||
EditorStyles.boldLabel
|
||||
);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// Status indicator with colored dot
|
||||
Rect statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
|
||||
Color statusColor = GetStatusColor(mcpClient.status);
|
||||
|
||||
// Draw status dot
|
||||
DrawStatusDot(statusRect, statusColor);
|
||||
|
||||
// Status text with some padding
|
||||
EditorGUILayout.LabelField(
|
||||
new GUIContent(" " + mcpClient.configStatus),
|
||||
GUILayout.Height(20),
|
||||
GUILayout.MinWidth(100)
|
||||
);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// Configure button with improved styling
|
||||
GUIStyle buttonStyle = new(GUI.skin.button)
|
||||
{
|
||||
padding = new RectOffset(15, 15, 5, 5),
|
||||
margin = new RectOffset(10, 10, 5, 5),
|
||||
};
|
||||
|
||||
// Create muted button style for Manual Setup
|
||||
GUIStyle mutedButtonStyle = new(buttonStyle);
|
||||
|
||||
if (mcpClient.mcpType == McpTypes.VSCode)
|
||||
{
|
||||
// Special handling for VSCode GitHub Copilot
|
||||
if (
|
||||
GUILayout.Button(
|
||||
"Auto Configure VSCode with GitHub Copilot",
|
||||
buttonStyle,
|
||||
GUILayout.Height(28)
|
||||
)
|
||||
)
|
||||
{
|
||||
ConfigureMcpClient(mcpClient);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Manual Setup", mutedButtonStyle, GUILayout.Height(28)))
|
||||
{
|
||||
// Show VSCode specific manual setup window
|
||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? mcpClient.windowsConfigPath
|
||||
: mcpClient.linuxConfigPath;
|
||||
|
||||
// Get the Python directory path
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
|
||||
// Create VSCode-specific configuration
|
||||
var vscodeConfig = new
|
||||
{
|
||||
mcp = new
|
||||
{
|
||||
servers = new
|
||||
{
|
||||
unityMCP = new
|
||||
{
|
||||
command = "uv",
|
||||
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||
string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
|
||||
|
||||
// Use the VSCodeManualSetupWindow directly since we're in the same namespace
|
||||
VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard client buttons
|
||||
if (
|
||||
GUILayout.Button(
|
||||
$"Auto Configure {mcpClient.name}",
|
||||
buttonStyle,
|
||||
GUILayout.Height(28)
|
||||
)
|
||||
)
|
||||
{
|
||||
ConfigureMcpClient(mcpClient);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Manual Setup", mutedButtonStyle, GUILayout.Height(28)))
|
||||
{
|
||||
// Get the appropriate config path based on OS
|
||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? mcpClient.windowsConfigPath
|
||||
: mcpClient.linuxConfigPath;
|
||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
// End horizontal layout if using half-width and at the end of a row
|
||||
if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 1)
|
||||
{
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space(5);
|
||||
}
|
||||
// Add space and end the horizontal layout if last item is odd
|
||||
else if (
|
||||
useHalfWidth
|
||||
&& mcpClients.clients.IndexOf(mcpClient) == mcpClients.clients.Count - 1
|
||||
)
|
||||
{
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space(5);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStatusDot(Rect statusRect, Color statusColor)
|
||||
{
|
||||
Rect dotRect = new(statusRect.x + 6, statusRect.y + 4, 12, 12);
|
||||
float offsetX = (statusRect.width - size) / 2;
|
||||
float offsetY = (statusRect.height - size) / 2;
|
||||
Rect dotRect = new(statusRect.x + offsetX, statusRect.y + offsetY, size, size);
|
||||
Vector3 center = new(
|
||||
dotRect.x + (dotRect.width / 2),
|
||||
dotRect.y + (dotRect.height / 2),
|
||||
0
|
||||
);
|
||||
float radius = dotRect.width / 2;
|
||||
float radius = size / 2;
|
||||
|
||||
// Draw the main dot
|
||||
Handles.color = statusColor;
|
||||
|
|
@ -256,59 +127,255 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
// Header
|
||||
DrawHeader();
|
||||
|
||||
// Main sections in a more compact layout
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// Left column - Status and Bridge
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.5f));
|
||||
DrawServerStatusSection();
|
||||
EditorGUILayout.Space(5);
|
||||
DrawBridgeSection();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
// Right column - Validation Settings
|
||||
EditorGUILayout.BeginVertical();
|
||||
DrawValidationSection();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
// Title with improved styling
|
||||
Rect titleRect = EditorGUILayout.GetControlRect(false, 30);
|
||||
EditorGUI.DrawRect(
|
||||
new Rect(titleRect.x, titleRect.y, titleRect.width, titleRect.height),
|
||||
new Color(0.2f, 0.2f, 0.2f, 0.1f)
|
||||
);
|
||||
|
||||
// Unified MCP Client Configuration
|
||||
DrawUnifiedClientConfiguration();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
EditorGUILayout.Space(15);
|
||||
Rect titleRect = EditorGUILayout.GetControlRect(false, 40);
|
||||
EditorGUI.DrawRect(titleRect, new Color(0.2f, 0.2f, 0.2f, 0.1f));
|
||||
|
||||
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 16,
|
||||
alignment = TextAnchor.MiddleLeft
|
||||
};
|
||||
|
||||
GUI.Label(
|
||||
new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height),
|
||||
"MCP Editor",
|
||||
EditorStyles.boldLabel
|
||||
new Rect(titleRect.x + 15, titleRect.y + 8, titleRect.width - 30, titleRect.height),
|
||||
"Unity MCP Editor",
|
||||
titleStyle
|
||||
);
|
||||
EditorGUILayout.Space(10);
|
||||
EditorGUILayout.Space(15);
|
||||
}
|
||||
|
||||
// Python Server Installation Status Section
|
||||
private void DrawServerStatusSection()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel);
|
||||
|
||||
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 14
|
||||
};
|
||||
EditorGUILayout.LabelField("Server Status", sectionTitleStyle);
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// Status indicator with colored dot
|
||||
Rect installStatusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
|
||||
DrawStatusDot(installStatusRect, pythonServerInstallationStatusColor);
|
||||
EditorGUILayout.LabelField(" " + pythonServerInstallationStatus);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
|
||||
DrawStatusDot(statusRect, pythonServerInstallationStatusColor, 16);
|
||||
|
||||
GUIStyle statusStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
EditorGUILayout.LabelField(pythonServerInstallationStatus, statusStyle, GUILayout.Height(28));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.LabelField($"Unity Port: {unityPort}");
|
||||
EditorGUILayout.LabelField($"MCP Port: {mcpPort}");
|
||||
EditorGUILayout.HelpBox(
|
||||
"Your MCP client (e.g. Cursor or Claude Desktop) will start the server automatically when you start it.",
|
||||
MessageType.Info
|
||||
);
|
||||
EditorGUILayout.Space(5);
|
||||
GUIStyle portStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||
{
|
||||
fontSize = 11
|
||||
};
|
||||
EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// Unity Bridge Section
|
||||
private void DrawBridgeSection()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.LabelField("Unity MCP Bridge", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField($"Status: {(isUnityBridgeRunning ? "Running" : "Stopped")}");
|
||||
EditorGUILayout.LabelField($"Port: {unityPort}");
|
||||
|
||||
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 14
|
||||
};
|
||||
EditorGUILayout.LabelField("Unity Bridge", sectionTitleStyle);
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Color bridgeColor = isUnityBridgeRunning ? Color.green : Color.red;
|
||||
Rect bridgeStatusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
|
||||
DrawStatusDot(bridgeStatusRect, bridgeColor, 16);
|
||||
|
||||
GUIStyle bridgeStatusStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
EditorGUILayout.LabelField(isUnityBridgeRunning ? "Running" : "Stopped", bridgeStatusStyle, GUILayout.Height(28));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button(isUnityBridgeRunning ? "Stop Bridge" : "Start Bridge"))
|
||||
EditorGUILayout.Space(8);
|
||||
if (GUILayout.Button(isUnityBridgeRunning ? "Stop Bridge" : "Start Bridge", GUILayout.Height(32)))
|
||||
{
|
||||
ToggleUnityBridge();
|
||||
}
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
foreach (McpClient mcpClient in mcpClients.clients)
|
||||
private void DrawValidationSection()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
ConfigurationSection(mcpClient);
|
||||
fontSize = 14
|
||||
};
|
||||
EditorGUILayout.LabelField("Script Validation", sectionTitleStyle);
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
validationLevelIndex = EditorGUILayout.Popup("Validation Level", validationLevelIndex, validationLevelOptions, GUILayout.Height(20));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SaveValidationLevelSetting();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
string description = GetValidationLevelDescription(validationLevelIndex);
|
||||
EditorGUILayout.HelpBox(description, MessageType.Info);
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
private void DrawUnifiedClientConfiguration()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
||||
GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 14
|
||||
};
|
||||
EditorGUILayout.LabelField("MCP Client Configuration", sectionTitleStyle);
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// Client selector
|
||||
string[] clientNames = mcpClients.clients.Select(c => c.name).ToArray();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
selectedClientIndex = EditorGUILayout.Popup("Select Client", selectedClientIndex, clientNames, GUILayout.Height(20));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
selectedClientIndex = Mathf.Clamp(selectedClientIndex, 0, mcpClients.clients.Count - 1);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
if (mcpClients.clients.Count > 0 && selectedClientIndex < mcpClients.clients.Count)
|
||||
{
|
||||
McpClient selectedClient = mcpClients.clients[selectedClientIndex];
|
||||
DrawClientConfigurationCompact(selectedClient);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawClientConfigurationCompact(McpClient mcpClient)
|
||||
{
|
||||
// Status display
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
|
||||
Color statusColor = GetStatusColor(mcpClient.status);
|
||||
DrawStatusDot(statusRect, statusColor, 16);
|
||||
|
||||
GUIStyle clientStatusStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
fontStyle = FontStyle.Bold
|
||||
};
|
||||
EditorGUILayout.LabelField(mcpClient.configStatus, clientStatusStyle, GUILayout.Height(28));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// Action buttons in horizontal layout
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (mcpClient.mcpType == McpTypes.VSCode)
|
||||
{
|
||||
if (GUILayout.Button("Auto Configure", GUILayout.Height(32)))
|
||||
{
|
||||
ConfigureMcpClient(mcpClient);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button($"Auto Configure", GUILayout.Height(32)))
|
||||
{
|
||||
ConfigureMcpClient(mcpClient);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Manual Setup", GUILayout.Height(32)))
|
||||
{
|
||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? mcpClient.windowsConfigPath
|
||||
: mcpClient.linuxConfigPath;
|
||||
|
||||
if (mcpClient.mcpType == McpTypes.VSCode)
|
||||
{
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
var vscodeConfig = new
|
||||
{
|
||||
mcp = new
|
||||
{
|
||||
servers = new
|
||||
{
|
||||
unityMCP = new
|
||||
{
|
||||
command = "uv",
|
||||
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||
string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
|
||||
VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
// Quick info
|
||||
GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||
{
|
||||
fontSize = 10
|
||||
};
|
||||
EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle);
|
||||
}
|
||||
|
||||
private void ToggleUnityBridge()
|
||||
|
|
@ -355,6 +422,7 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
existingConfig ??= new Newtonsoft.Json.Linq.JObject();
|
||||
|
||||
// Handle different client types with a switch statement
|
||||
//Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
|
||||
switch (mcpClient?.mcpType)
|
||||
{
|
||||
case McpTypes.VSCode:
|
||||
|
|
@ -370,6 +438,12 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
{
|
||||
existingConfig.mcp.servers = new Newtonsoft.Json.Linq.JObject();
|
||||
}
|
||||
|
||||
// Add/update UnityMCP server in VSCode settings
|
||||
existingConfig.mcp.servers.unityMCP =
|
||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
||||
JsonConvert.SerializeObject(unityMCPConfig)
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -379,15 +453,15 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
{
|
||||
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
|
||||
}
|
||||
|
||||
// Add/update UnityMCP server in standard MCP settings
|
||||
existingConfig.mcpServers.unityMCP =
|
||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
||||
JsonConvert.SerializeObject(unityMCPConfig)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add/update UnityMCP server in VSCode settings
|
||||
existingConfig.mcp.servers.unityMCP =
|
||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
||||
JsonConvert.SerializeObject(unityMCPConfig)
|
||||
);
|
||||
|
||||
// Write the merged configuration back to file
|
||||
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
|
||||
File.WriteAllText(configPath, mergedJson);
|
||||
|
|
@ -613,6 +687,50 @@ namespace UnityMcpBridge.Editor.Windows
|
|||
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
|
||||
}
|
||||
|
||||
private void LoadValidationLevelSetting()
|
||||
{
|
||||
string savedLevel = EditorPrefs.GetString("UnityMCP_ScriptValidationLevel", "standard");
|
||||
validationLevelIndex = savedLevel.ToLower() switch
|
||||
{
|
||||
"basic" => 0,
|
||||
"standard" => 1,
|
||||
"comprehensive" => 2,
|
||||
"strict" => 3,
|
||||
_ => 1 // Default to Standard
|
||||
};
|
||||
}
|
||||
|
||||
private void SaveValidationLevelSetting()
|
||||
{
|
||||
string levelString = validationLevelIndex switch
|
||||
{
|
||||
0 => "basic",
|
||||
1 => "standard",
|
||||
2 => "comprehensive",
|
||||
3 => "strict",
|
||||
_ => "standard"
|
||||
};
|
||||
EditorPrefs.SetString("UnityMCP_ScriptValidationLevel", levelString);
|
||||
}
|
||||
|
||||
private string GetValidationLevelDescription(int index)
|
||||
{
|
||||
return index switch
|
||||
{
|
||||
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"
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetCurrentValidationLevel()
|
||||
{
|
||||
string savedLevel = EditorPrefs.GetString("UnityMCP_ScriptValidationLevel", "standard");
|
||||
return savedLevel;
|
||||
}
|
||||
|
||||
private void CheckMcpConfiguration(McpClient mcpClient)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
Loading…
Reference in New Issue