From 91011052124847e1bd773f8be5ddf3149be6c94b Mon Sep 17 00:00:00 2001
From: Scriptwonder <1300285021@qq.com>
Date: Wed, 23 Jul 2025 23:31:47 -0400
Subject: [PATCH] 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 PR
---
README.md | 26 +-
UnityMcpBridge/Editor/Tools/ManageScript.cs | 683 +++++++++++++++++-
.../Editor/Windows/UnityMcpEditorWindow.cs | 492 ++++++++-----
3 files changed, 992 insertions(+), 209 deletions(-)
diff --git a/README.md b/README.md
index 60439a59..2d8f82e3 100644
--- a/README.md
+++ b/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)*
+ * [Optional] Roslyn for Advanced Script Validation
+
+ 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.
+
### 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:
✅ Completed Features
- [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)).
### 🔬 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)
diff --git a/UnityMcpBridge/Editor/Tools/ManageScript.cs b/UnityMcpBridge/Editor/Tools/ManageScript.cs
index 70c9f71b..d79e17a6 100644
--- a/UnityMcpBridge/Editor/Tools/ManageScript.cs
+++ b/UnityMcpBridge/Editor/Tools/ManageScript.cs
@@ -7,10 +7,43 @@
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
{
///
/// 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.
///
public static class ManageScript
{
@@ -168,12 +201,18 @@ string namespaceName
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)
+ {
+ string errorMessage = "Script validation failed:\n" + string.Join("\n", validationErrors);
+ return Response.Error(errorMessage);
+ }
+ else if (validationErrors != null && validationErrors.Length > 0)
{
- // 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}");
+ // Log warnings but don't block creation
+ Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));
}
try
@@ -243,11 +282,18 @@ string contents
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)
+ {
+ string errorMessage = "Script validation failed:\n" + string.Join("\n", validationErrors);
+ return Response.Error(errorMessage);
+ }
+ else if (validationErrors != null && validationErrors.Length > 0)
{
- Debug.LogWarning($"Potential syntax error in script being updated: {name}");
- // Consider if this should be a hard error or just a warning
+ // Log warnings but don't block update
+ Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors));
}
try
@@ -361,27 +407,624 @@ string namespaceName
}
///
- /// 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
+ ///
+ 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
+ };
+ }
+
+ ///
+ /// Validates C# script syntax using multiple validation layers.
///
private static bool ValidateScriptSyntax(string contents)
{
+ return ValidateScriptSyntax(contents, ValidationLevel.Standard, out _);
+ }
+
+ ///
+ /// Advanced syntax validation with detailed diagnostics and configurable strictness.
+ ///
+ private static bool ValidateScriptSyntax(string contents, ValidationLevel level, out string[] errors)
+ {
+ var errorList = new System.Collections.Generic.List();
+ errors = null;
+
if (string.IsNullOrEmpty(contents))
- return true; // Empty is technically valid?
+ {
+ return true; // Empty content is valid
+ }
+
+ // 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:")));
+ }
+
+ ///
+ /// Validation strictness levels
+ ///
+ private enum ValidationLevel
+ {
+ Basic, // Only syntax errors
+ Standard, // Syntax + Unity best practices
+ Comprehensive, // All checks + semantic analysis
+ Strict // Treat all issues as errors
+ }
+
+ ///
+ /// Validates basic code structure (braces, quotes, comments)
+ ///
+ private static bool ValidateBasicStructure(string contents, System.Collections.Generic.List errors)
+ {
+ bool isValid = true;
int braceBalance = 0;
- foreach (char c in contents)
+ 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)
{
- if (c == '{')
- braceBalance++;
- else if (c == '}')
- braceBalance--;
+ 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 braceBalance == 0;
- // This is extremely basic. A real C# parser/compiler check would be ideal
- // but is complex to implement directly here.
+ return isValid;
}
+
+#if USE_ROSLYN
+ ///
+ /// Cached compilation references for performance
+ ///
+ private static System.Collections.Generic.List _cachedReferences = null;
+ private static DateTime _cacheTime = DateTime.MinValue;
+ private static readonly TimeSpan CacheExpiry = TimeSpan.FromMinutes(5);
+
+ ///
+ /// Validates syntax using Roslyn compiler services
+ ///
+ private static bool ValidateScriptSyntaxRoslyn(string contents, ValidationLevel level, System.Collections.Generic.List 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;
+ }
+ }
+
+ ///
+ /// Validates script semantics using full compilation context to catch namespace, type, and method resolution errors
+ ///
+ private static bool ValidateScriptSemantics(string contents, System.Collections.Generic.List 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;
+ }
+ }
+
+ ///
+ /// Gets compilation references with caching for performance
+ ///
+ private static System.Collections.Generic.List GetCompilationReferences()
+ {
+ // Check cache validity
+ if (_cachedReferences != null && DateTime.Now - _cacheTime < CacheExpiry)
+ {
+ return _cachedReferences;
+ }
+
+ try
+ {
+ var references = new System.Collections.Generic.List();
+
+ // 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();
+ }
+ }
+#else
+ private static bool ValidateScriptSyntaxRoslyn(string contents, ValidationLevel level, System.Collections.Generic.List errors)
+ {
+ // Fallback when Roslyn is not available
+ return true;
+ }
+#endif
+
+ ///
+ /// Validates Unity-specific coding rules and best practices
+ /// //TODO: Naive Unity Checks and not really yield any results, need to be improved
+ ///
+ private static void ValidateScriptSyntaxUnity(string contents, System.Collections.Generic.List 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");
+ }
+ }
+
+ ///
+ /// Validates semantic rules and common coding issues
+ ///
+ private static void ValidateSemanticRules(string contents, System.Collections.Generic.List 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)
+ ///
+ /// Public method to validate script syntax with configurable validation level
+ /// Returns detailed validation results including errors and warnings
+ ///
+ // 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);
+ // }
+ // }
}
}
diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
index bf11f1fd..3b1e69d5 100644
--- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
+++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
@@ -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 @@ public class UnityMcpEditorWindow : EditorWindow
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 @@ private void OnEnable()
{
CheckMcpConfiguration(mcpClient);
}
+
+ // Load validation level setting
+ LoadValidationLevelSetting();
}
private Color GetStatusColor(McpStatus status)
@@ -79,89 +96,253 @@ private void UpdatePythonServerInstallationStatus()
}
}
- 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;
+ 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 = size / 2;
- // Begin horizontal layout if using half-width
- if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 0)
- {
- EditorGUILayout.BeginHorizontal();
- }
+ // Draw the main dot
+ Handles.color = statusColor;
+ Handles.DrawSolidDisc(center, Vector3.forward, radius);
- // Begin section with fixed width
- EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Width(sectionWidth));
+ // Draw the border
+ Color borderColor = new(
+ statusColor.r * 0.7f,
+ statusColor.g * 0.7f,
+ statusColor.b * 0.7f
+ );
+ Handles.color = borderColor;
+ Handles.DrawWireDisc(center, Vector3.forward, radius);
+ }
- // Header with improved styling
+ private void OnGUI()
+ {
+ 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);
- Rect headerRect = EditorGUILayout.GetControlRect(false, 24);
+ DrawBridgeSection();
+ EditorGUILayout.EndVertical();
+
+ // Right column - Validation Settings
+ EditorGUILayout.BeginVertical();
+ DrawValidationSection();
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space(10);
+
+ // 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(
- headerRect.x + 8,
- headerRect.y + 4,
- headerRect.width - 16,
- headerRect.height
- ),
- mcpClient.name + " Configuration",
- EditorStyles.boldLabel
+ new Rect(titleRect.x + 15, titleRect.y + 8, titleRect.width - 30, titleRect.height),
+ "Unity MCP Editor",
+ titleStyle
);
- EditorGUILayout.Space(5);
+ EditorGUILayout.Space(15);
+ }
- // Status indicator with colored dot
- Rect statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
- Color statusColor = GetStatusColor(mcpClient.status);
+ private void DrawServerStatusSection()
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
+ {
+ fontSize = 14
+ };
+ EditorGUILayout.LabelField("Server Status", sectionTitleStyle);
+ EditorGUILayout.Space(8);
+
+ 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();
- // Draw status dot
- DrawStatusDot(statusRect, statusColor);
+ EditorGUILayout.Space(5);
+ GUIStyle portStyle = new GUIStyle(EditorStyles.miniLabel)
+ {
+ fontSize = 11
+ };
+ EditorGUILayout.LabelField($"Ports: Unity {unityPort}, MCP {mcpPort}", portStyle);
+ EditorGUILayout.Space(5);
+ EditorGUILayout.EndVertical();
+ }
- // Status text with some padding
- EditorGUILayout.LabelField(
- new GUIContent(" " + mcpClient.configStatus),
- GUILayout.Height(20),
- GUILayout.MinWidth(100)
- );
+ private void DrawBridgeSection()
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ 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();
EditorGUILayout.Space(8);
+ if (GUILayout.Button(isUnityBridgeRunning ? "Stop Bridge" : "Start Bridge", GUILayout.Height(32)))
+ {
+ ToggleUnityBridge();
+ }
+ EditorGUILayout.Space(5);
+ EditorGUILayout.EndVertical();
+ }
- // Configure button with improved styling
- GUIStyle buttonStyle = new(GUI.skin.button)
+ private void DrawValidationSection()
+ {
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ GUIStyle sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
{
- padding = new RectOffset(15, 15, 5, 5),
- margin = new RectOffset(10, 10, 5, 5),
+ 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();
+ }
- // Create muted button style for Manual Setup
- GUIStyle mutedButtonStyle = new(buttonStyle);
+ 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)
{
- // Special handling for VSCode GitHub Copilot
- if (
- GUILayout.Button(
- "Auto Configure VSCode with GitHub Copilot",
- buttonStyle,
- GUILayout.Height(28)
- )
- )
+ if (GUILayout.Button("Auto Configure", GUILayout.Height(32)))
{
ConfigureMcpClient(mcpClient);
}
-
- if (GUILayout.Button("Manual Setup", mutedButtonStyle, GUILayout.Height(28)))
+ }
+ else
+ {
+ if (GUILayout.Button($"Auto Configure", GUILayout.Height(32)))
{
- // Show VSCode specific manual setup window
- string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
- ? mcpClient.windowsConfigPath
- : mcpClient.linuxConfigPath;
-
- // Get the Python directory path
- string pythonDir = FindPackagePythonDirectory();
+ ConfigureMcpClient(mcpClient);
+ }
+ }
+
+ if (GUILayout.Button("Manual Setup", GUILayout.Height(32)))
+ {
+ string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+ ? mcpClient.windowsConfigPath
+ : mcpClient.linuxConfigPath;
- // Create VSCode-specific configuration
+ if (mcpClient.mcpType == McpTypes.VSCode)
+ {
+ string pythonDir = FindPackagePythonDirectory();
var vscodeConfig = new
{
mcp = new
@@ -176,139 +357,25 @@ private void ConfigurationSection(McpClient mcpClient)
}
}
};
-
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)))
+ else
{
- // 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);
- Vector3 center = new(
- dotRect.x + (dotRect.width / 2),
- dotRect.y + (dotRect.height / 2),
- 0
- );
- float radius = dotRect.width / 2;
-
- // Draw the main dot
- Handles.color = statusColor;
- Handles.DrawSolidDisc(center, Vector3.forward, radius);
-
- // Draw the border
- Color borderColor = new(
- statusColor.r * 0.7f,
- statusColor.g * 0.7f,
- statusColor.b * 0.7f
- );
- Handles.color = borderColor;
- Handles.DrawWireDisc(center, Vector3.forward, radius);
- }
-
- private void OnGUI()
- {
- scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
-
- 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)
- );
- GUI.Label(
- new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height),
- "MCP Editor",
- EditorStyles.boldLabel
- );
- EditorGUILayout.Space(10);
-
- // Python Server Installation Status Section
- EditorGUILayout.BeginVertical(EditorStyles.helpBox);
- EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel);
-
- // Status indicator with colored dot
- Rect installStatusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
- DrawStatusDot(installStatusRect, pythonServerInstallationStatusColor);
- EditorGUILayout.LabelField(" " + pythonServerInstallationStatus);
+
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.EndVertical();
-
- EditorGUILayout.Space(10);
-
- // Unity Bridge Section
- EditorGUILayout.BeginVertical(EditorStyles.helpBox);
- EditorGUILayout.LabelField("Unity MCP Bridge", EditorStyles.boldLabel);
- EditorGUILayout.LabelField($"Status: {(isUnityBridgeRunning ? "Running" : "Stopped")}");
- EditorGUILayout.LabelField($"Port: {unityPort}");
-
- if (GUILayout.Button(isUnityBridgeRunning ? "Stop Bridge" : "Start Bridge"))
- {
- ToggleUnityBridge();
- }
- EditorGUILayout.EndVertical();
-
- foreach (McpClient mcpClient in mcpClients.clients)
+
+ EditorGUILayout.Space(8);
+ // Quick info
+ GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
{
- EditorGUILayout.Space(10);
- ConfigurationSection(mcpClient);
- }
-
- EditorGUILayout.EndScrollView();
+ fontSize = 10
+ };
+ EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle);
}
private void ToggleUnityBridge()
@@ -355,6 +422,7 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
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 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
{
existingConfig.mcp.servers = new Newtonsoft.Json.Linq.JObject();
}
+
+ // Add/update UnityMCP server in VSCode settings
+ existingConfig.mcp.servers.unityMCP =
+ JsonConvert.DeserializeObject(
+ JsonConvert.SerializeObject(unityMCPConfig)
+ );
break;
default:
@@ -379,15 +453,15 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC
{
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
}
+
+ // Add/update UnityMCP server in standard MCP settings
+ existingConfig.mcpServers.unityMCP =
+ JsonConvert.DeserializeObject(
+ JsonConvert.SerializeObject(unityMCPConfig)
+ );
break;
}
- // Add/update UnityMCP server in VSCode settings
- existingConfig.mcp.servers.unityMCP =
- JsonConvert.DeserializeObject(
- JsonConvert.SerializeObject(unityMCPConfig)
- );
-
// Write the merged configuration back to file
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
File.WriteAllText(configPath, mergedJson);
@@ -613,6 +687,50 @@ McpClient mcpClient
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