diff --git a/Editor/Commands/AssetCommandHandler.cs b/Editor/Commands/AssetCommandHandler.cs
index ea57405..77f953e 100644
--- a/Editor/Commands/AssetCommandHandler.cs
+++ b/Editor/Commands/AssetCommandHandler.cs
@@ -2,10 +2,9 @@ using UnityEngine;
using UnityEditor;
using System.IO;
using Newtonsoft.Json.Linq;
-using System.Linq;
using System.Collections.Generic;
-namespace MCPServer.Editor.Commands
+namespace UnityMCP.Editor.Commands
{
///
/// Handles asset-related commands for the MCP Server
@@ -51,10 +50,11 @@ namespace MCPServer.Editor.Commands
}
catch (System.Exception e)
{
- return new {
- success = false,
- error = $"Failed to import asset: {e.Message}",
- stackTrace = e.StackTrace
+ return new
+ {
+ success = false,
+ error = $"Failed to import asset: {e.Message}",
+ stackTrace = e.StackTrace
};
}
}
@@ -67,16 +67,16 @@ namespace MCPServer.Editor.Commands
try
{
string prefabPath = (string)@params["prefab_path"];
-
+
if (string.IsNullOrEmpty(prefabPath))
return new { success = false, error = "Prefab path cannot be empty" };
- Vector3 position = new Vector3(
+ Vector3 position = new(
(float)@params["position_x"],
(float)@params["position_y"],
(float)@params["position_z"]
);
- Vector3 rotation = new Vector3(
+ Vector3 rotation = new(
(float)@params["rotation_x"],
(float)@params["rotation_y"],
(float)@params["rotation_z"]
@@ -93,7 +93,7 @@ namespace MCPServer.Editor.Commands
{
return new { success = false, error = $"Failed to instantiate prefab: {prefabPath}" };
}
-
+
instance.transform.position = position;
instance.transform.rotation = Quaternion.Euler(rotation);
@@ -106,10 +106,11 @@ namespace MCPServer.Editor.Commands
}
catch (System.Exception e)
{
- return new {
- success = false,
- error = $"Failed to instantiate prefab: {e.Message}",
- stackTrace = e.StackTrace
+ return new
+ {
+ success = false,
+ error = $"Failed to instantiate prefab: {e.Message}",
+ stackTrace = e.StackTrace
};
}
}
@@ -162,9 +163,10 @@ namespace MCPServer.Editor.Commands
}
catch (System.Exception e)
{
- return new {
- success = false,
- error = $"Failed to create prefab: {e.Message}",
+ return new
+ {
+ success = false,
+ error = $"Failed to create prefab: {e.Message}",
stackTrace = e.StackTrace,
sourceInfo = $"Object: {@params["object_name"]}, Path: {@params["prefab_path"]}"
};
@@ -218,9 +220,9 @@ namespace MCPServer.Editor.Commands
assets.Add(new
{
name = Path.GetFileNameWithoutExtension(path),
- path = path,
+ path,
type = assetType?.Name ?? "Unknown",
- guid = guid
+ guid
});
}
diff --git a/Editor/Commands/CommandRegistry.cs b/Editor/Commands/CommandRegistry.cs
index 3878e15..1998386 100644
--- a/Editor/Commands/CommandRegistry.cs
+++ b/Editor/Commands/CommandRegistry.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
-namespace MCPServer.Editor.Commands
+namespace UnityMCP.Editor.Commands
{
///
/// Registry for all MCP command handlers
diff --git a/Editor/Commands/EditorControlHandler.cs b/Editor/Commands/EditorControlHandler.cs
index 256a720..f3162eb 100644
--- a/Editor/Commands/EditorControlHandler.cs
+++ b/Editor/Commands/EditorControlHandler.cs
@@ -5,386 +5,335 @@ using Newtonsoft.Json.Linq;
using System;
using System.Reflection;
using System.Collections.Generic;
-using System.Linq; // Add LINQ namespace for Select extension method
-using System.Globalization;
+using System.Linq;
-///
-/// Handles editor control commands like undo, redo, play, pause, stop, and build operations.
-///
-public static class EditorControlHandler
+namespace UnityMCP.Editor.Commands
{
///
- /// Handles editor control commands
+ /// Handles editor control commands like undo, redo, play, pause, stop, and build operations.
///
- public static object HandleEditorControl(JObject @params)
+ public static class EditorControlHandler
{
- string command = (string)@params["command"];
- JObject commandParams = (JObject)@params["params"];
-
- switch (command.ToUpper())
+ ///
+ /// Handles editor control commands
+ ///
+ public static object HandleEditorControl(JObject @params)
{
- case "UNDO":
- return HandleUndo();
- case "REDO":
- return HandleRedo();
- case "PLAY":
- return HandlePlay();
- case "PAUSE":
- return HandlePause();
- case "STOP":
- return HandleStop();
- case "BUILD":
- return HandleBuild(commandParams);
- case "EXECUTE_COMMAND":
- return HandleExecuteCommand(commandParams);
- case "READ_CONSOLE":
- return ReadConsole(commandParams);
- case "GET_AVAILABLE_COMMANDS":
- return GetAvailableCommands();
- default:
- return new { error = $"Unknown editor control command: {command}" };
- }
- }
+ string command = (string)@params["command"];
+ JObject commandParams = (JObject)@params["params"];
- private static object HandleUndo()
- {
- Undo.PerformUndo();
- return new { message = "Undo performed successfully" };
- }
-
- private static object HandleRedo()
- {
- Undo.PerformRedo();
- return new { message = "Redo performed successfully" };
- }
-
- private static object HandlePlay()
- {
- if (!EditorApplication.isPlaying)
- {
- EditorApplication.isPlaying = true;
- return new { message = "Entered play mode" };
- }
- return new { message = "Already in play mode" };
- }
-
- private static object HandlePause()
- {
- if (EditorApplication.isPlaying)
- {
- EditorApplication.isPaused = !EditorApplication.isPaused;
- return new { message = EditorApplication.isPaused ? "Game paused" : "Game resumed" };
- }
- return new { message = "Not in play mode" };
- }
-
- private static object HandleStop()
- {
- if (EditorApplication.isPlaying)
- {
- EditorApplication.isPlaying = false;
- return new { message = "Exited play mode" };
- }
- return new { message = "Not in play mode" };
- }
-
- private static object HandleBuild(JObject @params)
- {
- string platform = (string)@params["platform"];
- string buildPath = (string)@params["buildPath"];
-
- try
- {
- BuildTarget target = GetBuildTarget(platform);
- if ((int)target == -1)
+ return command.ToUpper() switch
{
- return new { error = $"Unsupported platform: {platform}" };
+ "UNDO" => HandleUndo(),
+ "REDO" => HandleRedo(),
+ "PLAY" => HandlePlay(),
+ "PAUSE" => HandlePause(),
+ "STOP" => HandleStop(),
+ "BUILD" => HandleBuild(commandParams),
+ "EXECUTE_COMMAND" => HandleExecuteCommand(commandParams),
+ "READ_CONSOLE" => ReadConsole(commandParams),
+ "GET_AVAILABLE_COMMANDS" => GetAvailableCommands(),
+ _ => new { error = $"Unknown editor control command: {command}" },
+ };
+ }
+
+ private static object HandleUndo()
+ {
+ Undo.PerformUndo();
+ return new { message = "Undo performed successfully" };
+ }
+
+ private static object HandleRedo()
+ {
+ Undo.PerformRedo();
+ return new { message = "Redo performed successfully" };
+ }
+
+ private static object HandlePlay()
+ {
+ if (!EditorApplication.isPlaying)
+ {
+ EditorApplication.isPlaying = true;
+ return new { message = "Entered play mode" };
+ }
+ return new { message = "Already in play mode" };
+ }
+
+ private static object HandlePause()
+ {
+ if (EditorApplication.isPlaying)
+ {
+ EditorApplication.isPaused = !EditorApplication.isPaused;
+ return new { message = EditorApplication.isPaused ? "Game paused" : "Game resumed" };
+ }
+ return new { message = "Not in play mode" };
+ }
+
+ private static object HandleStop()
+ {
+ if (EditorApplication.isPlaying)
+ {
+ EditorApplication.isPlaying = false;
+ return new { message = "Exited play mode" };
+ }
+ return new { message = "Not in play mode" };
+ }
+
+ private static object HandleBuild(JObject @params)
+ {
+ string platform = (string)@params["platform"];
+ string buildPath = (string)@params["buildPath"];
+
+ try
+ {
+ BuildTarget target = GetBuildTarget(platform);
+ if ((int)target == -1)
+ {
+ return new { error = $"Unsupported platform: {platform}" };
+ }
+
+ BuildPlayerOptions buildPlayerOptions = new()
+ {
+ scenes = GetEnabledScenes(),
+ target = target,
+ locationPathName = buildPath
+ };
+
+ BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
+ return new
+ {
+ message = "Build completed successfully",
+ report.summary
+ };
+ }
+ catch (Exception e)
+ {
+ return new { error = $"Build failed: {e.Message}" };
+ }
+ }
+
+ private static object HandleExecuteCommand(JObject @params)
+ {
+ string commandName = (string)@params["commandName"];
+ try
+ {
+ EditorApplication.ExecuteMenuItem(commandName);
+ return new { message = $"Executed command: {commandName}" };
+ }
+ catch (Exception e)
+ {
+ return new { error = $"Failed to execute command: {e.Message}" };
+ }
+ }
+
+ ///
+ /// Reads log messages from the Unity Console
+ ///
+ /// Parameters containing filtering options
+ /// Object containing console messages filtered by type
+ public static object ReadConsole(JObject @params)
+ {
+ // Default values for show flags
+ bool showLogs = true;
+ bool showWarnings = true;
+ bool showErrors = true;
+ string searchTerm = string.Empty;
+
+ // Get filter parameters if provided
+ if (@params != null)
+ {
+ if (@params["show_logs"] != null) showLogs = (bool)@params["show_logs"];
+ if (@params["show_warnings"] != null) showWarnings = (bool)@params["show_warnings"];
+ if (@params["show_errors"] != null) showErrors = (bool)@params["show_errors"];
+ if (@params["search_term"] != null) searchTerm = (string)@params["search_term"];
}
- BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
- buildPlayerOptions.scenes = GetEnabledScenes();
- buildPlayerOptions.target = target;
- buildPlayerOptions.locationPathName = buildPath;
-
- BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
- return new { message = "Build completed successfully", summary = report.summary };
- }
- catch (System.Exception e)
- {
- return new { error = $"Build failed: {e.Message}" };
- }
- }
-
- private static object HandleExecuteCommand(JObject @params)
- {
- string commandName = (string)@params["commandName"];
- try
- {
- EditorApplication.ExecuteMenuItem(commandName);
- return new { message = $"Executed command: {commandName}" };
- }
- catch (System.Exception e)
- {
- return new { error = $"Failed to execute command: {e.Message}" };
- }
- }
-
- ///
- /// Reads log messages from the Unity Console
- ///
- /// Parameters containing filtering options
- /// Object containing console messages filtered by type
- public static object ReadConsole(JObject @params)
- {
- // Default values for show flags
- bool showLogs = true;
- bool showWarnings = true;
- bool showErrors = true;
- string searchTerm = string.Empty;
-
- // Get filter parameters if provided
- if (@params != null)
- {
- if (@params["show_logs"] != null)
- showLogs = (bool)@params["show_logs"];
- if (@params["show_warnings"] != null)
- showWarnings = (bool)@params["show_warnings"];
- if (@params["show_errors"] != null)
- showErrors = (bool)@params["show_errors"];
- if (@params["search_term"] != null)
- searchTerm = (string)@params["search_term"];
- }
-
- try
- {
- // Get required types and methods via reflection
- Type logEntriesType = Type.GetType("UnityEditor.LogEntries,UnityEditor");
- Type logEntryType = Type.GetType("UnityEditor.LogEntry,UnityEditor");
-
- if (logEntriesType == null || logEntryType == null)
- return new
- {
- error = "Could not find required Unity logging types",
- entries = new List
public static object GetObjectProperties(JObject @params)
{
- string name = (string)@params["name"] ?? throw new System.Exception("Parameter 'name' is required.");
- var obj = GameObject.Find(name) ?? throw new System.Exception($"Object '{name}' not found.");
+ string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required.");
+ var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found.");
var components = obj.GetComponents()
.Select(c => new
@@ -216,9 +216,9 @@ namespace MCPServer.Editor.Commands
return new
{
- name = obj.name,
- tag = obj.tag,
- layer = obj.layer,
+ obj.name,
+ obj.tag,
+ obj.layer,
active = obj.activeSelf,
transform = new
{
@@ -235,11 +235,11 @@ namespace MCPServer.Editor.Commands
///
public static object GetComponentProperties(JObject @params)
{
- string objectName = (string)@params["object_name"] ?? throw new System.Exception("Parameter 'object_name' is required.");
- string componentType = (string)@params["component_type"] ?? throw new System.Exception("Parameter 'component_type' is required.");
+ string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required.");
+ string componentType = (string)@params["component_type"] ?? throw new Exception("Parameter 'component_type' is required.");
- var obj = GameObject.Find(objectName) ?? throw new System.Exception($"Object '{objectName}' not found.");
- var component = obj.GetComponent(componentType) ?? throw new System.Exception($"Component '{componentType}' not found on object '{objectName}'.");
+ var obj = GameObject.Find(objectName) ?? throw new Exception($"Object '{objectName}' not found.");
+ var component = obj.GetComponent(componentType) ?? throw new Exception($"Component '{componentType}' not found on object '{objectName}'.");
return GetComponentProperties(component);
}
@@ -249,12 +249,12 @@ namespace MCPServer.Editor.Commands
///
public static object FindObjectsByName(JObject @params)
{
- string name = (string)@params["name"] ?? throw new System.Exception("Parameter 'name' is required.");
+ string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required.");
var objects = GameObject.FindObjectsByType(FindObjectsSortMode.None)
.Where(o => o.name.Contains(name))
.Select(o => new
{
- name = o.name,
+ o.name,
path = GetGameObjectPath(o)
})
.ToList();
@@ -267,11 +267,11 @@ namespace MCPServer.Editor.Commands
///
public static object FindObjectsByTag(JObject @params)
{
- string tag = (string)@params["tag"] ?? throw new System.Exception("Parameter 'tag' is required.");
+ string tag = (string)@params["tag"] ?? throw new Exception("Parameter 'tag' is required.");
var objects = GameObject.FindGameObjectsWithTag(tag)
.Select(o => new
{
- name = o.name,
+ o.name,
path = GetGameObjectPath(o)
})
.ToList();
@@ -295,11 +295,11 @@ namespace MCPServer.Editor.Commands
///
public static object SelectObject(JObject @params)
{
- string name = (string)@params["name"] ?? throw new System.Exception("Parameter 'name' is required.");
- var obj = GameObject.Find(name) ?? throw new System.Exception($"Object '{name}' not found.");
+ string name = (string)@params["name"] ?? throw new Exception("Parameter 'name' is required.");
+ var obj = GameObject.Find(name) ?? throw new Exception($"Object '{name}' not found.");
Selection.activeGameObject = obj;
- return new { name = obj.name };
+ return new { obj.name };
}
///
@@ -315,7 +315,7 @@ namespace MCPServer.Editor.Commands
{
selected = new
{
- name = selected.name,
+ selected.name,
path = GetGameObjectPath(selected)
}
};
@@ -379,7 +379,7 @@ namespace MCPServer.Editor.Commands
{
return new
{
- name = obj.name,
+ obj.name,
children = Enumerable.Range(0, obj.transform.childCount)
.Select(i => BuildHierarchyNode(obj.transform.GetChild(i).gameObject))
.ToList()
diff --git a/Editor/Commands/SceneCommandHandler.cs b/Editor/Commands/SceneCommandHandler.cs
index 1a9324d..8f5dbc5 100644
--- a/Editor/Commands/SceneCommandHandler.cs
+++ b/Editor/Commands/SceneCommandHandler.cs
@@ -1,11 +1,11 @@
-using UnityEngine;
using UnityEngine.SceneManagement;
using System.Linq;
+using System;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
-namespace MCPServer.Editor.Commands
+namespace UnityMCP.Editor.Commands
{
///
/// Handles scene-related commands for the MCP Server
@@ -42,7 +42,7 @@ namespace MCPServer.Editor.Commands
EditorSceneManager.OpenScene(scenePath);
return new { success = true, message = $"Opened scene: {scenePath}" };
}
- catch (System.Exception e)
+ catch (Exception e)
{
return new { success = false, error = $"Failed to open scene: {e.Message}", stackTrace = e.StackTrace };
}
@@ -60,7 +60,7 @@ namespace MCPServer.Editor.Commands
EditorSceneManager.SaveScene(scene);
return new { success = true, message = $"Saved scene: {scene.path}" };
}
- catch (System.Exception e)
+ catch (Exception e)
{
return new { success = false, error = $"Failed to save scene: {e.Message}", stackTrace = e.StackTrace };
}
@@ -96,7 +96,7 @@ namespace MCPServer.Editor.Commands
return new { success = true, message = $"Created new scene at: {scenePath}" };
}
- catch (System.Exception e)
+ catch (Exception e)
{
return new { success = false, error = $"Failed to create new scene: {e.Message}", stackTrace = e.StackTrace };
}
@@ -131,7 +131,7 @@ namespace MCPServer.Editor.Commands
EditorSceneManager.OpenScene(scenePath);
return new { success = true, message = $"Changed to scene: {scenePath}" };
}
- catch (System.Exception e)
+ catch (Exception e)
{
return new { success = false, error = $"Failed to change scene: {e.Message}", stackTrace = e.StackTrace };
}
diff --git a/Editor/Commands/ScriptCommandHandler.cs b/Editor/Commands/ScriptCommandHandler.cs
index 18055f7..6b6af57 100644
--- a/Editor/Commands/ScriptCommandHandler.cs
+++ b/Editor/Commands/ScriptCommandHandler.cs
@@ -5,9 +5,8 @@ using System.IO;
using System.Text;
using System.Linq;
using Newtonsoft.Json.Linq;
-using MCPServer.Editor.Helpers;
-namespace MCPServer.Editor.Commands
+namespace UnityMCP.Editor.Commands
{
///
/// Handles script-related commands for Unity
@@ -19,12 +18,9 @@ namespace MCPServer.Editor.Commands
///
public static object ViewScript(JObject @params)
{
- string scriptPath = (string)@params["script_path"] ?? throw new System.Exception("Parameter 'script_path' is required.");
+ string scriptPath = (string)@params["script_path"] ?? throw new Exception("Parameter 'script_path' is required.");
bool requireExists = (bool?)@params["require_exists"] ?? true;
-
- // Debug to help diagnose issues
- Debug.Log($"ViewScript - Original script path: {scriptPath}");
-
+
// Handle path correctly to avoid double "Assets" folder issue
string relativePath;
if (scriptPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
@@ -36,16 +32,14 @@ namespace MCPServer.Editor.Commands
{
relativePath = scriptPath;
}
-
+
string fullPath = Path.Combine(Application.dataPath, relativePath);
- Debug.Log($"ViewScript - Relative path: {relativePath}");
- Debug.Log($"ViewScript - Full path: {fullPath}");
if (!File.Exists(fullPath))
{
if (requireExists)
{
- throw new System.Exception($"Script file not found: {scriptPath}");
+ throw new Exception($"Script file not found: {scriptPath}");
}
else
{
@@ -76,7 +70,7 @@ namespace MCPServer.Editor.Commands
///
public static object CreateScript(JObject @params)
{
- string scriptName = (string)@params["script_name"] ?? throw new System.Exception("Parameter 'script_name' is required.");
+ string scriptName = (string)@params["script_name"] ?? throw new Exception("Parameter 'script_name' is required.");
string scriptType = (string)@params["script_type"] ?? "MonoBehaviour";
string namespaceName = (string)@params["namespace"];
string template = (string)@params["template"];
@@ -93,7 +87,7 @@ namespace MCPServer.Editor.Commands
// Determine the script path
string scriptPath;
-
+
// Handle the script folder parameter
if (string.IsNullOrEmpty(scriptFolder))
{
@@ -105,7 +99,7 @@ namespace MCPServer.Editor.Commands
{
// Use provided folder path
scriptPath = scriptFolder;
-
+
// If scriptFolder starts with "Assets/", remove it for local path operations
if (scriptPath.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
{
@@ -115,10 +109,7 @@ namespace MCPServer.Editor.Commands
// Create the full directory path, avoiding Assets/Assets issue
string folderPath = Path.Combine(Application.dataPath, scriptPath);
- Debug.Log($"CreateScript - Script name: {scriptName}");
- Debug.Log($"CreateScript - Script path: {scriptPath}");
- Debug.Log($"CreateScript - Creating script in folder path: {folderPath}");
-
+
// Create directory if it doesn't exist
if (!Directory.Exists(folderPath))
{
@@ -129,7 +120,7 @@ namespace MCPServer.Editor.Commands
}
catch (Exception ex)
{
- throw new System.Exception($"Failed to create directory '{scriptPath}': {ex.Message}");
+ throw new Exception($"Failed to create directory '{scriptPath}': {ex.Message}");
}
}
@@ -137,7 +128,7 @@ namespace MCPServer.Editor.Commands
string fullFilePath = Path.Combine(folderPath, scriptName);
if (File.Exists(fullFilePath) && !overwrite)
{
- throw new System.Exception($"Script file '{scriptName}' already exists in '{scriptPath}' and overwrite is not enabled.");
+ throw new Exception($"Script file '{scriptName}' already exists in '{scriptPath}' and overwrite is not enabled.");
}
try
@@ -151,7 +142,7 @@ namespace MCPServer.Editor.Commands
else
{
// Otherwise generate content based on template and parameters
- StringBuilder contentBuilder = new StringBuilder();
+ StringBuilder contentBuilder = new();
// Add using directives
contentBuilder.AppendLine("using UnityEngine;");
@@ -212,8 +203,9 @@ namespace MCPServer.Editor.Commands
{
relativePath = $"Assets/{relativePath}";
}
-
- return new {
+
+ return new
+ {
message = $"Created script: {Path.Combine(relativePath, scriptName).Replace('\\', '/')}",
script_path = Path.Combine(relativePath, scriptName).Replace('\\', '/')
};
@@ -221,7 +213,7 @@ namespace MCPServer.Editor.Commands
catch (Exception ex)
{
Debug.LogError($"Failed to create script: {ex.Message}\n{ex.StackTrace}");
- throw new System.Exception($"Failed to create script '{scriptName}': {ex.Message}");
+ throw new Exception($"Failed to create script '{scriptName}': {ex.Message}");
}
}
@@ -230,8 +222,8 @@ namespace MCPServer.Editor.Commands
///
public static object UpdateScript(JObject @params)
{
- string scriptPath = (string)@params["script_path"] ?? throw new System.Exception("Parameter 'script_path' is required.");
- string content = (string)@params["content"] ?? throw new System.Exception("Parameter 'content' is required.");
+ string scriptPath = (string)@params["script_path"] ?? throw new Exception("Parameter 'script_path' is required.");
+ string content = (string)@params["content"] ?? throw new Exception("Parameter 'content' is required.");
bool createIfMissing = (bool?)@params["create_if_missing"] ?? false;
bool createFolderIfMissing = (bool?)@params["create_folder_if_missing"] ?? false;
@@ -246,14 +238,12 @@ namespace MCPServer.Editor.Commands
{
relativePath = scriptPath;
}
-
+
string fullPath = Path.Combine(Application.dataPath, relativePath);
string directory = Path.GetDirectoryName(fullPath);
-
+
// Debug the paths to help diagnose issues
- Debug.Log($"UpdateScript - Original script path: {scriptPath}");
- Debug.Log($"UpdateScript - Relative path: {relativePath}");
- Debug.Log($"UpdateScript - Full path: {fullPath}");
+
// Check if file exists, create if requested
if (!File.Exists(fullPath))
@@ -267,7 +257,7 @@ namespace MCPServer.Editor.Commands
}
else if (!Directory.Exists(directory))
{
- throw new System.Exception($"Directory does not exist: {Path.GetDirectoryName(scriptPath)}");
+ throw new Exception($"Directory does not exist: {Path.GetDirectoryName(scriptPath)}");
}
// Create the file with content
@@ -277,7 +267,7 @@ namespace MCPServer.Editor.Commands
}
else
{
- throw new System.Exception($"Script file not found: {scriptPath}");
+ throw new Exception($"Script file not found: {scriptPath}");
}
}
@@ -316,7 +306,7 @@ namespace MCPServer.Editor.Commands
}
if (!Directory.Exists(fullPath))
- throw new System.Exception($"Folder not found: {folderPath}");
+ throw new Exception($"Folder not found: {folderPath}");
string[] scripts = Directory.GetFiles(fullPath, "*.cs", SearchOption.AllDirectories)
.Select(path => path.Replace(Application.dataPath, "Assets"))
@@ -330,14 +320,14 @@ namespace MCPServer.Editor.Commands
///
public static object AttachScript(JObject @params)
{
- string objectName = (string)@params["object_name"] ?? throw new System.Exception("Parameter 'object_name' is required.");
- string scriptName = (string)@params["script_name"] ?? throw new System.Exception("Parameter 'script_name' is required.");
+ string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required.");
+ string scriptName = (string)@params["script_name"] ?? throw new Exception("Parameter 'script_name' is required.");
string scriptPath = (string)@params["script_path"]; // Optional
// Find the target object
GameObject targetObject = GameObject.Find(objectName);
if (targetObject == null)
- throw new System.Exception($"Object '{objectName}' not found in scene.");
+ throw new Exception($"Object '{objectName}' not found in scene.");
// Ensure script name ends with .cs
if (!scriptName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
@@ -349,7 +339,7 @@ namespace MCPServer.Editor.Commands
// Find the script asset
string[] guids;
-
+
if (!string.IsNullOrEmpty(scriptPath))
{
// If a specific path is provided, try that first
@@ -359,7 +349,7 @@ namespace MCPServer.Editor.Commands
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath(scriptPath);
if (scriptAsset != null)
{
- System.Type scriptType = scriptAsset.GetClass();
+ Type scriptType = scriptAsset.GetClass();
if (scriptType != null)
{
try
@@ -378,7 +368,7 @@ namespace MCPServer.Editor.Commands
catch (Exception ex)
{
Debug.LogError($"Error attaching script component: {ex.Message}");
- throw new System.Exception($"Failed to add component: {ex.Message}");
+ throw new Exception($"Failed to add component: {ex.Message}");
}
}
}
@@ -387,36 +377,36 @@ namespace MCPServer.Editor.Commands
// Use the file name for searching if direct path didn't work
guids = AssetDatabase.FindAssets(scriptNameWithoutExtension + " t:script");
-
+
if (guids.Length == 0)
{
// Try a broader search if exact match fails
guids = AssetDatabase.FindAssets(scriptNameWithoutExtension);
-
+
if (guids.Length == 0)
- throw new System.Exception($"Script '{scriptFileName}' not found in project.");
+ throw new Exception($"Script '{scriptFileName}' not found in project.");
}
// Check each potential script until we find one that can be attached
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
-
+
// Filter to only consider .cs files
if (!path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
continue;
-
+
// Double check the file name to avoid false matches
string foundFileName = Path.GetFileName(path);
- if (!string.Equals(foundFileName, scriptFileName, StringComparison.OrdinalIgnoreCase) &&
+ if (!string.Equals(foundFileName, scriptFileName, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(Path.GetFileNameWithoutExtension(foundFileName), scriptNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
continue;
-
+
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath(path);
if (scriptAsset == null)
continue;
- System.Type scriptType = scriptAsset.GetClass();
+ Type scriptType = scriptAsset.GetClass();
if (scriptType == null || !typeof(MonoBehaviour).IsAssignableFrom(scriptType))
continue;
@@ -452,7 +442,7 @@ namespace MCPServer.Editor.Commands
}
// If we've tried all possibilities and nothing worked
- throw new System.Exception($"Could not attach script '{scriptFileName}' to object '{objectName}'. No valid script found or component creation failed.");
+ throw new Exception($"Could not attach script '{scriptFileName}' to object '{objectName}'. No valid script found or component creation failed.");
}
}
}
\ No newline at end of file
diff --git a/Editor/Data.meta b/Editor/Data.meta
new file mode 100644
index 0000000..bb714ec
--- /dev/null
+++ b/Editor/Data.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e59036660cc33d24596fbbf6d4657a83
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Data/DefaultServerConfig.cs b/Editor/Data/DefaultServerConfig.cs
new file mode 100644
index 0000000..2df0185
--- /dev/null
+++ b/Editor/Data/DefaultServerConfig.cs
@@ -0,0 +1,17 @@
+using UnityMCP.Editor.Models;
+
+namespace UnityMCP.Editor.Data
+{
+ public class DefaultServerConfig : ServerConfig
+ {
+ public new string unityHost = "localhost";
+ public new int unityPort = 6400;
+ public new int mcpPort = 6500;
+ public new float connectionTimeout = 15.0f;
+ public new int bufferSize = 32768;
+ public new string logLevel = "INFO";
+ public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s";
+ public new int maxRetries = 3;
+ public new float retryDelay = 1.0f;
+ }
+}
\ No newline at end of file
diff --git a/Editor/Data/DefaultServerConfig.cs.meta b/Editor/Data/DefaultServerConfig.cs.meta
new file mode 100644
index 0000000..6df0a87
--- /dev/null
+++ b/Editor/Data/DefaultServerConfig.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: de8f5721c34f7194392e9d8c7d0226c0
\ No newline at end of file
diff --git a/Editor/Data/McpClients.cs b/Editor/Data/McpClients.cs
new file mode 100644
index 0000000..0c692da
--- /dev/null
+++ b/Editor/Data/McpClients.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using UnityMCP.Editor.Models;
+
+namespace UnityMCP.Editor.Data
+{
+ public class McpClients
+ {
+ public List clients = new() {
+ new() {
+ name = "Claude Desktop",
+ windowsConfigPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "Claude",
+ "claude_desktop_config.json"
+ ),
+ linuxConfigPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ "Library",
+ "Application Support",
+ "Claude",
+ "claude_desktop_config.json"
+ ),
+ mcpType = McpTypes.ClaudeDesktop,
+ configStatus = "Not Configured"
+ },
+ new() {
+ name = "Cursor",
+ windowsConfigPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ ".cursor",
+ "mcp.json"
+ ),
+ linuxConfigPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ ".cursor",
+ "mcp.json"
+ ),
+ mcpType = McpTypes.Cursor,
+ configStatus = "Not Configured"
+ }
+ };
+
+ // Initialize status enums after construction
+ public McpClients()
+ {
+ foreach (var client in clients)
+ {
+ if (client.configStatus == "Not Configured")
+ {
+ client.status = McpStatus.NotConfigured;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/Data/McpClients.cs.meta b/Editor/Data/McpClients.cs.meta
new file mode 100644
index 0000000..3c8449a
--- /dev/null
+++ b/Editor/Data/McpClients.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 711b86bbc1f661e4fb2c822e14970e16
\ No newline at end of file
diff --git a/Editor/Helpers/Vector3Helper.cs b/Editor/Helpers/Vector3Helper.cs
index 4056eff..55cb68c 100644
--- a/Editor/Helpers/Vector3Helper.cs
+++ b/Editor/Helpers/Vector3Helper.cs
@@ -1,7 +1,7 @@
using UnityEngine;
using Newtonsoft.Json.Linq;
-namespace MCPServer.Editor.Helpers
+namespace UnityMCP.Editor.Helpers
{
///
/// Helper class for Vector3 operations
diff --git a/Editor/MCPEditorWindow.cs b/Editor/MCPEditorWindow.cs
deleted file mode 100644
index f3579b1..0000000
--- a/Editor/MCPEditorWindow.cs
+++ /dev/null
@@ -1,645 +0,0 @@
-using UnityEngine;
-using UnityEditor;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
-using System;
-using Newtonsoft.Json;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using System.Text;
-using System.Collections.Generic;
-
-public class DefaultServerConfig : ServerConfig
-{
- public new string unityHost = "localhost";
- public new int unityPort = 6400;
- public new int mcpPort = 6500;
- public new float connectionTimeout = 15.0f;
- public new int bufferSize = 32768;
- public new string logLevel = "INFO";
- public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s";
- public new int maxRetries = 3;
- public new float retryDelay = 1.0f;
-
-}
-
-[Serializable]
-public class MCPConfig
-{
- [JsonProperty("mcpServers")]
- public MCPConfigServers mcpServers;
-}
-
-[Serializable]
-public class MCPConfigServers
-{
- [JsonProperty("unityMCP")]
- public MCPConfigServer unityMCP;
-}
-
-[Serializable]
-public class MCPConfigServer
-{
- [JsonProperty("command")]
- public string command;
-
- [JsonProperty("args")]
- public string[] args;
-}
-
-[Serializable]
-public class ServerConfig
-{
- [JsonProperty("unity_host")]
- public string unityHost = "localhost";
-
- [JsonProperty("unity_port")]
- public int unityPort;
-
- [JsonProperty("mcp_port")]
- public int mcpPort;
-
- [JsonProperty("connection_timeout")]
- public float connectionTimeout;
-
- [JsonProperty("buffer_size")]
- public int bufferSize;
-
- [JsonProperty("log_level")]
- public string logLevel;
-
- [JsonProperty("log_format")]
- public string logFormat;
-
- [JsonProperty("max_retries")]
- public int maxRetries;
-
- [JsonProperty("retry_delay")]
- public float retryDelay;
-}
-
-public class MCPEditorWindow : EditorWindow
-{
- private bool isUnityBridgeRunning = false;
- private Vector2 scrollPosition;
- private string claudeConfigStatus = "Not configured";
- private string pythonServerStatus = "Not Connected";
- private Color pythonServerStatusColor = Color.red;
- private const int unityPort = 6400; // Hardcoded Unity port
- private const int mcpPort = 6500; // Hardcoded MCP port
- private const float CONNECTION_CHECK_INTERVAL = 2f; // Check every 2 seconds
- private float lastCheckTime = 0f;
-
- [MenuItem("Window/Unity MCP")]
- public static void ShowWindow()
- {
- GetWindow("MCP Editor");
- }
-
- private void OnEnable()
- {
- // Check initial states
- isUnityBridgeRunning = UnityMCPBridge.IsRunning;
- CheckPythonServerConnection();
- }
-
- private void Update()
- {
- // Check Python server connection periodically
- if (Time.realtimeSinceStartup - lastCheckTime >= CONNECTION_CHECK_INTERVAL)
- {
- CheckPythonServerConnection();
- lastCheckTime = Time.realtimeSinceStartup;
- }
- }
-
- private async void CheckPythonServerConnection()
- {
- try
- {
- using (var client = new TcpClient())
- {
- // Try to connect with a short timeout
- var connectTask = client.ConnectAsync("localhost", unityPort);
- if (await Task.WhenAny(connectTask, Task.Delay(1000)) == connectTask)
- {
- // Try to send a ping message to verify connection is alive
- try
- {
- NetworkStream stream = client.GetStream();
- byte[] pingMessage = Encoding.UTF8.GetBytes("ping");
- await stream.WriteAsync(pingMessage, 0, pingMessage.Length);
-
- // Wait for response with timeout
- byte[] buffer = new byte[1024];
- var readTask = stream.ReadAsync(buffer, 0, buffer.Length);
- if (await Task.WhenAny(readTask, Task.Delay(1000)) == readTask)
- {
- // Connection successful and responsive
- pythonServerStatus = "Connected";
- pythonServerStatusColor = Color.green;
- UnityEngine.Debug.Log($"Python server connected successfully on port {unityPort}");
- }
- else
- {
- // No response received
- pythonServerStatus = "No Response";
- pythonServerStatusColor = Color.yellow;
- UnityEngine.Debug.LogWarning($"Python server not responding on port {unityPort}");
- }
- }
- catch (Exception e)
- {
- // Connection established but communication failed
- pythonServerStatus = "Communication Error";
- pythonServerStatusColor = Color.yellow;
- UnityEngine.Debug.LogWarning($"Error communicating with Python server: {e.Message}");
- }
- }
- else
- {
- // Connection failed
- pythonServerStatus = "Not Connected";
- pythonServerStatusColor = Color.red;
- UnityEngine.Debug.LogWarning($"Python server is not running or not accessible on port {unityPort}");
- }
- client.Close();
- }
- }
- catch (Exception e)
- {
- pythonServerStatus = "Connection Error";
- pythonServerStatusColor = Color.red;
- UnityEngine.Debug.LogError($"Error checking Python server connection: {e.Message}");
- }
- }
-
- private void OnGUI()
- {
- scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
-
- EditorGUILayout.Space(10);
- EditorGUILayout.LabelField("MCP Editor", EditorStyles.boldLabel);
- EditorGUILayout.Space(10);
-
- // Python Server Status Section
- EditorGUILayout.BeginVertical(EditorStyles.helpBox);
- EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel);
-
- // Status bar
- var statusRect = EditorGUILayout.BeginHorizontal();
- EditorGUI.DrawRect(new Rect(statusRect.x, statusRect.y, 10, 20), pythonServerStatusColor);
- EditorGUILayout.LabelField(pythonServerStatus);
- EditorGUILayout.EndHorizontal();
-
- EditorGUILayout.LabelField($"Unity Port: {unityPort}");
- EditorGUILayout.LabelField($"MCP Port: {mcpPort}");
- EditorGUILayout.HelpBox("Start the Python server using command line: 'uv run server.py' in the Python directory", 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();
-
- EditorGUILayout.Space(10);
-
- // Claude Desktop Configuration Section
- EditorGUILayout.BeginVertical(EditorStyles.helpBox);
- EditorGUILayout.LabelField("Claude Desktop Configuration", EditorStyles.boldLabel);
- EditorGUILayout.LabelField($"Status: {claudeConfigStatus}");
-
- if (GUILayout.Button("Configure Claude Desktop"))
- {
- ConfigureClaudeDesktop();
- }
- EditorGUILayout.EndVertical();
-
- EditorGUILayout.EndScrollView();
- }
-
- private void ToggleUnityBridge()
- {
- if (isUnityBridgeRunning)
- {
- UnityMCPBridge.Stop();
- }
- else
- {
- UnityMCPBridge.Start();
- }
- isUnityBridgeRunning = !isUnityBridgeRunning;
- }
-
- private void ConfigureClaudeDesktop()
- {
- try
- {
- // Determine the config file path based on OS
- string configPath;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- configPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "Claude",
- "claude_desktop_config.json"
- );
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- configPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
- "Library",
- "Application Support",
- "Claude",
- "claude_desktop_config.json"
- );
- }
- else
- {
- claudeConfigStatus = "Unsupported OS";
- return;
- }
-
- // Create directory if it doesn't exist
- Directory.CreateDirectory(Path.GetDirectoryName(configPath));
-
- // Find the server.py file location
- string serverPath = null;
- string pythonDir = null;
-
- // List of possible locations to search
- var possiblePaths = new List
- {
- // Search in Assets folder - Manual installation
- Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python", "server.py")),
- Path.GetFullPath(Path.Combine(Application.dataPath, "Packages", "com.justinpbarnett.unity-mcp", "Python", "server.py")),
-
- // Search in package cache - Package manager installation
- Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Library", "PackageCache", "com.justinpbarnett.unity-mcp@*", "Python", "server.py")),
-
- // Search in package manager packages - Git installation
- Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Packages", "com.justinpbarnett.unity-mcp", "Python", "server.py"))
- };
-
- UnityEngine.Debug.Log("Searching for server.py in the following locations:");
-
- // First try with explicit paths
- foreach (var path in possiblePaths)
- {
- // Skip wildcard paths for now
- if (path.Contains("*")) continue;
-
- UnityEngine.Debug.Log($"Checking: {path}");
- if (File.Exists(path))
- {
- serverPath = path;
- pythonDir = Path.GetDirectoryName(serverPath);
- UnityEngine.Debug.Log($"Found server.py at: {serverPath}");
- break;
- }
- }
-
- // If not found, try with wildcard paths (package cache with version)
- if (serverPath == null)
- {
- foreach (var path in possiblePaths)
- {
- if (!path.Contains("*")) continue;
-
- string directoryPath = Path.GetDirectoryName(path);
- string searchPattern = Path.GetFileName(Path.GetDirectoryName(path));
- string parentDir = Path.GetDirectoryName(directoryPath);
-
- if (Directory.Exists(parentDir))
- {
- var matchingDirs = Directory.GetDirectories(parentDir, searchPattern);
- UnityEngine.Debug.Log($"Searching in: {parentDir} for pattern: {searchPattern}, found {matchingDirs.Length} matches");
-
- foreach (var dir in matchingDirs)
- {
- string candidatePath = Path.Combine(dir, "Python", "server.py");
- UnityEngine.Debug.Log($"Checking: {candidatePath}");
-
- if (File.Exists(candidatePath))
- {
- serverPath = candidatePath;
- pythonDir = Path.GetDirectoryName(serverPath);
- UnityEngine.Debug.Log($"Found server.py at: {serverPath}");
- break;
- }
- }
-
- if (serverPath != null) break;
- }
- }
- }
-
- if (serverPath == null || !File.Exists(serverPath))
- {
- ShowManualConfigurationInstructions(configPath);
- return;
- }
-
- UnityEngine.Debug.Log($"Using server.py at: {serverPath}");
- UnityEngine.Debug.Log($"Python directory: {pythonDir}");
-
- // Load existing configuration if it exists
- dynamic existingConfig = null;
- if (File.Exists(configPath))
- {
- try
- {
- string existingJson = File.ReadAllText(configPath);
- existingConfig = JsonConvert.DeserializeObject(existingJson);
- }
- catch (Exception ex)
- {
- UnityEngine.Debug.LogWarning($"Failed to parse existing Claude config: {ex.Message}. Creating new config.");
- }
- }
-
- // If no existing config or parsing failed, create a new one
- if (existingConfig == null)
- {
- existingConfig = new
- {
- mcpServers = new Dictionary()
- };
- }
-
- // Create the Unity MCP server configuration
- var unityMCPConfig = new MCPConfigServer
- {
- command = "uv",
- args = new[]
- {
- "--directory",
- pythonDir,
- "run",
- "server.py"
- }
- };
- // Add or update the Unity MCP configuration while preserving the rest
- var mcpServers = existingConfig.mcpServers as Newtonsoft.Json.Linq.JObject
- ?? new Newtonsoft.Json.Linq.JObject();
-
- mcpServers["unityMCP"] = Newtonsoft.Json.Linq.JToken.FromObject(unityMCPConfig);
- existingConfig.mcpServers = mcpServers;
- // Serialize and write to file with proper formatting
- var jsonSettings = new JsonSerializerSettings
- {
- Formatting = Formatting.Indented
- };
- string jsonConfig = JsonConvert.SerializeObject(existingConfig, jsonSettings);
- File.WriteAllText(configPath, jsonConfig);
-
- claudeConfigStatus = "Configured successfully";
- UnityEngine.Debug.Log($"Claude Desktop configuration saved to: {configPath}");
- UnityEngine.Debug.Log($"Configuration contents:\n{jsonConfig}");
- }
- catch (Exception e)
- {
- // Determine the config file path based on OS for error message
- string configPath = "";
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- configPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "Claude",
- "claude_desktop_config.json"
- );
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- configPath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
- "Library",
- "Application Support",
- "Claude",
- "claude_desktop_config.json"
- );
- }
-
- ShowManualConfigurationInstructions(configPath);
- UnityEngine.Debug.LogError($"Failed to configure Claude Desktop: {e.Message}\n{e.StackTrace}");
- }
- }
-
- private void ShowManualConfigurationInstructions(string configPath)
- {
- claudeConfigStatus = "Error: Manual configuration required";
-
- // Get the Python directory path using Package Manager API
- string pythonDir = FindPackagePythonDirectory();
-
- // Create the manual configuration message
- var jsonConfig = new MCPConfig
- {
- mcpServers = new MCPConfigServers
- {
- unityMCP = new MCPConfigServer
- {
- command = "uv",
- args = new[]
- {
- "--directory",
- pythonDir,
- "run",
- "server.py"
- }
- }
- }
- };
-
- var jsonSettings = new JsonSerializerSettings
- {
- Formatting = Formatting.Indented
- };
- string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
-
- // Show a dedicated configuration window instead of console logs
- ManualConfigWindow.ShowWindow(configPath, manualConfigJson);
- }
-
- private string FindPackagePythonDirectory()
- {
- string pythonDir = "/path/to/your/unity-mcp/Python";
-
- try
- {
- // Try to find the package using Package Manager API
- var request = UnityEditor.PackageManager.Client.List();
- while (!request.IsCompleted) { } // Wait for the request to complete
-
- if (request.Status == UnityEditor.PackageManager.StatusCode.Success)
- {
- foreach (var package in request.Result)
- {
- UnityEngine.Debug.Log($"Package: {package.name}, Path: {package.resolvedPath}");
-
- if (package.name == "com.justinpbarnett.unity-mcp")
- {
- string packagePath = package.resolvedPath;
- string potentialPythonDir = Path.Combine(packagePath, "Python");
-
- if (Directory.Exists(potentialPythonDir) &&
- File.Exists(Path.Combine(potentialPythonDir, "server.py")))
- {
- UnityEngine.Debug.Log($"Found package Python directory at: {potentialPythonDir}");
- return potentialPythonDir;
- }
- }
- }
- }
- else if (request.Error != null)
- {
- UnityEngine.Debug.LogError("Failed to list packages: " + request.Error.message);
- }
-
- // If not found via Package Manager, try manual approaches
- // First check for local installation
- string[] possibleDirs = {
- Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python"))
- };
-
- foreach (var dir in possibleDirs)
- {
- UnityEngine.Debug.Log($"Checking local directory: {dir}");
- if (Directory.Exists(dir) && File.Exists(Path.Combine(dir, "server.py")))
- {
- UnityEngine.Debug.Log($"Found local Python directory at: {dir}");
- return dir;
- }
- }
-
- // If still not found, return the placeholder path
- UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path");
- }
- catch (Exception e)
- {
- UnityEngine.Debug.LogError($"Error finding package path: {e.Message}");
- }
-
- return pythonDir;
- }
-}
-
-// Editor window to display manual configuration instructions
-public class ManualConfigWindow : EditorWindow
-{
- private string configPath;
- private string configJson;
- private Vector2 scrollPos;
- private bool pathCopied = false;
- private bool jsonCopied = false;
- private float copyFeedbackTimer = 0;
-
- public static void ShowWindow(string configPath, string configJson)
- {
- var window = GetWindow("Manual Configuration");
- window.configPath = configPath;
- window.configJson = configJson;
- window.minSize = new Vector2(500, 400);
- window.Show();
- }
-
- private void OnGUI()
- {
- scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
-
- // Header
- EditorGUILayout.Space(10);
- EditorGUILayout.LabelField("Claude Desktop Manual Configuration", EditorStyles.boldLabel);
- EditorGUILayout.Space(10);
-
- // Instructions
- EditorGUILayout.LabelField("The automatic configuration failed. Please follow these steps:", EditorStyles.boldLabel);
- EditorGUILayout.Space(5);
-
- EditorGUILayout.LabelField("1. Open Claude Desktop and go to Settings > Developer > Edit Config", EditorStyles.wordWrappedLabel);
- EditorGUILayout.LabelField("2. Create or edit the configuration file at:", EditorStyles.wordWrappedLabel);
-
- // Config path section with copy button
- EditorGUILayout.BeginHorizontal();
- EditorGUILayout.SelectableLabel(configPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
-
- if (GUILayout.Button("Copy Path", GUILayout.Width(80)))
- {
- EditorGUIUtility.systemCopyBuffer = configPath;
- pathCopied = true;
- copyFeedbackTimer = 2f;
- }
-
- EditorGUILayout.EndHorizontal();
-
- if (pathCopied)
- {
- EditorGUILayout.LabelField("Path copied to clipboard!", EditorStyles.miniLabel);
- }
-
- EditorGUILayout.Space(10);
-
- // JSON configuration
- EditorGUILayout.LabelField("3. Paste the following JSON configuration:", EditorStyles.wordWrappedLabel);
- EditorGUILayout.Space(5);
-
- EditorGUILayout.LabelField("Make sure to replace the Python path if necessary:", EditorStyles.wordWrappedLabel);
- EditorGUILayout.Space(5);
-
- // JSON text area with copy button
- GUIStyle textAreaStyle = new GUIStyle(EditorStyles.textArea)
- {
- wordWrap = true,
- richText = true
- };
-
- EditorGUILayout.BeginHorizontal();
- EditorGUILayout.SelectableLabel(configJson, textAreaStyle, GUILayout.MinHeight(200));
- EditorGUILayout.EndHorizontal();
-
- if (GUILayout.Button("Copy JSON Configuration"))
- {
- EditorGUIUtility.systemCopyBuffer = configJson;
- jsonCopied = true;
- copyFeedbackTimer = 2f;
- }
-
- if (jsonCopied)
- {
- EditorGUILayout.LabelField("JSON copied to clipboard!", EditorStyles.miniLabel);
- }
-
- EditorGUILayout.Space(10);
-
- // Additional note
- EditorGUILayout.HelpBox("After configuring, restart Claude Desktop to apply the changes.", MessageType.Info);
-
- EditorGUILayout.EndScrollView();
- }
-
- private void Update()
- {
- // Handle the feedback message timer
- if (copyFeedbackTimer > 0)
- {
- copyFeedbackTimer -= Time.deltaTime;
- if (copyFeedbackTimer <= 0)
- {
- pathCopied = false;
- jsonCopied = false;
- Repaint();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Editor/Models/Command.cs b/Editor/Models/Command.cs
index a7048b6..4f153f4 100644
--- a/Editor/Models/Command.cs
+++ b/Editor/Models/Command.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json.Linq;
-namespace MCPServer.Editor.Models
+namespace UnityMCP.Editor.Models
{
///
/// Represents a command received from the MCP client
diff --git a/Editor/Models/MCPConfig.cs b/Editor/Models/MCPConfig.cs
new file mode 100644
index 0000000..9372b22
--- /dev/null
+++ b/Editor/Models/MCPConfig.cs
@@ -0,0 +1,12 @@
+using System;
+using Newtonsoft.Json;
+
+namespace UnityMCP.Editor.Models
+{
+ [Serializable]
+ public class MCPConfig
+ {
+ [JsonProperty("mcpServers")]
+ public MCPConfigServers mcpServers;
+ }
+}
diff --git a/Editor/Models/MCPConfig.cs.meta b/Editor/Models/MCPConfig.cs.meta
new file mode 100644
index 0000000..1f70925
--- /dev/null
+++ b/Editor/Models/MCPConfig.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c17c09908f0c1524daa8b6957ce1f7f5
\ No newline at end of file
diff --git a/Editor/Models/MCPConfigServer.cs b/Editor/Models/MCPConfigServer.cs
new file mode 100644
index 0000000..d182f67
--- /dev/null
+++ b/Editor/Models/MCPConfigServer.cs
@@ -0,0 +1,15 @@
+using System;
+using Newtonsoft.Json;
+
+namespace UnityMCP.Editor.Models
+{
+ [Serializable]
+ public class MCPConfigServer
+ {
+ [JsonProperty("command")]
+ public string command;
+
+ [JsonProperty("args")]
+ public string[] args;
+ }
+}
diff --git a/Editor/Models/MCPConfigServer.cs.meta b/Editor/Models/MCPConfigServer.cs.meta
new file mode 100644
index 0000000..4dad0b4
--- /dev/null
+++ b/Editor/Models/MCPConfigServer.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5fae9d995f514e9498e9613e2cdbeca9
\ No newline at end of file
diff --git a/Editor/Models/MCPConfigServers.cs b/Editor/Models/MCPConfigServers.cs
new file mode 100644
index 0000000..de9e875
--- /dev/null
+++ b/Editor/Models/MCPConfigServers.cs
@@ -0,0 +1,12 @@
+using System;
+using Newtonsoft.Json;
+
+namespace UnityMCP.Editor.Models
+{
+ [Serializable]
+ public class MCPConfigServers
+ {
+ [JsonProperty("unityMCP")]
+ public MCPConfigServer unityMCP;
+ }
+}
diff --git a/Editor/Models/MCPConfigServers.cs.meta b/Editor/Models/MCPConfigServers.cs.meta
new file mode 100644
index 0000000..9ef1310
--- /dev/null
+++ b/Editor/Models/MCPConfigServers.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: bcb583553e8173b49be71a5c43bd9502
\ No newline at end of file
diff --git a/Editor/Models/McpClient.cs b/Editor/Models/McpClient.cs
new file mode 100644
index 0000000..d900f41
--- /dev/null
+++ b/Editor/Models/McpClient.cs
@@ -0,0 +1,48 @@
+namespace UnityMCP.Editor.Models
+{
+ public class McpClient
+ {
+ public string name;
+ public string windowsConfigPath;
+ public string linuxConfigPath;
+ public McpTypes mcpType;
+ public string configStatus;
+ public McpStatus status = McpStatus.NotConfigured;
+
+ // Helper method to convert the enum to a display string
+ public string GetStatusDisplayString()
+ {
+ return status switch
+ {
+ McpStatus.NotConfigured => "Not Configured",
+ McpStatus.Configured => "Configured",
+ McpStatus.Running => "Running",
+ McpStatus.Connected => "Connected",
+ McpStatus.IncorrectPath => "Incorrect Path",
+ McpStatus.CommunicationError => "Communication Error",
+ McpStatus.NoResponse => "No Response",
+ McpStatus.UnsupportedOS => "Unsupported OS",
+ McpStatus.MissingConfig => "Missing UnityMCP Config",
+ McpStatus.Error => configStatus.StartsWith("Error:") ? configStatus : "Error",
+ _ => "Unknown"
+ };
+ }
+
+ // Helper method to set both status enum and string for backward compatibility
+ public void SetStatus(McpStatus newStatus, string errorDetails = null)
+ {
+ status = newStatus;
+
+ if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails))
+ {
+ configStatus = $"Error: {errorDetails}";
+ }
+ else
+ {
+ configStatus = GetStatusDisplayString();
+ }
+ }
+ }
+}
+
+
diff --git a/Editor/Models/McpClient.cs.meta b/Editor/Models/McpClient.cs.meta
new file mode 100644
index 0000000..a11df35
--- /dev/null
+++ b/Editor/Models/McpClient.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: b1afa56984aec0d41808edcebf805e6a
\ No newline at end of file
diff --git a/Editor/Models/McpStatus.cs b/Editor/Models/McpStatus.cs
new file mode 100644
index 0000000..36308e5
--- /dev/null
+++ b/Editor/Models/McpStatus.cs
@@ -0,0 +1,17 @@
+namespace UnityMCP.Editor.Models
+{
+ // Enum representing the various status states for MCP clients
+ public enum McpStatus
+ {
+ NotConfigured, // Not set up yet
+ Configured, // Successfully configured
+ Running, // Service is running
+ Connected, // Successfully connected
+ IncorrectPath, // Configuration has incorrect paths
+ CommunicationError, // Connected but communication issues
+ NoResponse, // Connected but not responding
+ MissingConfig, // Config file exists but missing required elements
+ UnsupportedOS, // OS is not supported
+ Error // General error state
+ }
+}
\ No newline at end of file
diff --git a/Editor/Models/McpStatus.cs.meta b/Editor/Models/McpStatus.cs.meta
new file mode 100644
index 0000000..4e5feb5
--- /dev/null
+++ b/Editor/Models/McpStatus.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: aa63057c9e5282d4887352578bf49971
\ No newline at end of file
diff --git a/Editor/Models/McpTypes.cs b/Editor/Models/McpTypes.cs
new file mode 100644
index 0000000..2c08e82
--- /dev/null
+++ b/Editor/Models/McpTypes.cs
@@ -0,0 +1,8 @@
+namespace UnityMCP.Editor.Models
+{
+ public enum McpTypes
+ {
+ ClaudeDesktop,
+ Cursor
+ }
+}
\ No newline at end of file
diff --git a/Editor/Models/McpTypes.cs.meta b/Editor/Models/McpTypes.cs.meta
new file mode 100644
index 0000000..d20128c
--- /dev/null
+++ b/Editor/Models/McpTypes.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 9ca97c5ff5ed74c4fbb65cfa9d2bfed1
\ No newline at end of file
diff --git a/Editor/Models/ServerConfig.cs b/Editor/Models/ServerConfig.cs
new file mode 100644
index 0000000..4dc729d
--- /dev/null
+++ b/Editor/Models/ServerConfig.cs
@@ -0,0 +1,36 @@
+using System;
+using Newtonsoft.Json;
+
+namespace UnityMCP.Editor.Models
+{
+ [Serializable]
+ public class ServerConfig
+ {
+ [JsonProperty("unity_host")]
+ public string unityHost = "localhost";
+
+ [JsonProperty("unity_port")]
+ public int unityPort;
+
+ [JsonProperty("mcp_port")]
+ public int mcpPort;
+
+ [JsonProperty("connection_timeout")]
+ public float connectionTimeout;
+
+ [JsonProperty("buffer_size")]
+ public int bufferSize;
+
+ [JsonProperty("log_level")]
+ public string logLevel;
+
+ [JsonProperty("log_format")]
+ public string logFormat;
+
+ [JsonProperty("max_retries")]
+ public int maxRetries;
+
+ [JsonProperty("retry_delay")]
+ public float retryDelay;
+ }
+}
diff --git a/Editor/Models/ServerConfig.cs.meta b/Editor/Models/ServerConfig.cs.meta
new file mode 100644
index 0000000..0c4b377
--- /dev/null
+++ b/Editor/Models/ServerConfig.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e4e45386fcc282249907c2e3c7e5d9c6
\ No newline at end of file
diff --git a/Editor/UnityMCPBridge.cs b/Editor/UnityMCPBridge.cs
index 5b6eda6..3d4c600 100644
--- a/Editor/UnityMCPBridge.cs
+++ b/Editor/UnityMCPBridge.cs
@@ -3,344 +3,343 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
-using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using MCPServer.Editor.Models;
-using MCPServer.Editor.Commands;
-using MCPServer.Editor.Helpers;
using System.IO;
+using UnityMCP.Editor.Models;
+using UnityMCP.Editor.Commands;
-[InitializeOnLoad]
-public static partial class UnityMCPBridge
+namespace UnityMCP.Editor
{
- private static TcpListener listener;
- private static bool isRunning = false;
- private static readonly object lockObj = new object();
- private static Dictionary tcs)> commandQueue = new();
- private static readonly int unityPort = 6400; // Hardcoded port
-
- // Add public property to expose running state
- public static bool IsRunning => isRunning;
-
- // Add method to check existence of a folder
- public static bool FolderExists(string path)
+ [InitializeOnLoad]
+ public static partial class UnityMCPBridge
{
- if (string.IsNullOrEmpty(path))
- return false;
+ private static TcpListener listener;
+ private static bool isRunning = false;
+ private static readonly object lockObj = new();
+ private static Dictionary tcs)> commandQueue = new();
+ private static readonly int unityPort = 6400; // Hardcoded port
- if (path.Equals("Assets", StringComparison.OrdinalIgnoreCase))
- return true;
+ public static bool IsRunning => isRunning;
- string fullPath = Path.Combine(Application.dataPath, path.StartsWith("Assets/") ? path.Substring(7) : path);
- return Directory.Exists(fullPath);
- }
-
- static UnityMCPBridge()
- {
- Start();
- EditorApplication.quitting += Stop;
- }
-
- public static void Start()
- {
- if (isRunning) return;
- isRunning = true;
- listener = new TcpListener(IPAddress.Loopback, unityPort);
- listener.Start();
- Debug.Log($"UnityMCPBridge started on port {unityPort}.");
- Task.Run(ListenerLoop);
- EditorApplication.update += ProcessCommands;
- }
-
- public static void Stop()
- {
- if (!isRunning) return;
- isRunning = false;
- listener.Stop();
- EditorApplication.update -= ProcessCommands;
- Debug.Log("UnityMCPBridge stopped.");
- }
-
- private static async Task ListenerLoop()
- {
- while (isRunning)
+ public static bool FolderExists(string path)
{
- try
- {
- var client = await listener.AcceptTcpClientAsync();
- // Enable basic socket keepalive
- client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
+ if (string.IsNullOrEmpty(path))
+ return false;
- // Set longer receive timeout to prevent quick disconnections
- client.ReceiveTimeout = 60000; // 60 seconds
+ if (path.Equals("Assets", StringComparison.OrdinalIgnoreCase))
+ return true;
- // Fire and forget each client connection
- _ = HandleClientAsync(client);
- }
- catch (Exception ex)
- {
- if (isRunning) Debug.LogError($"Listener error: {ex.Message}");
- }
+ string fullPath = Path.Combine(Application.dataPath, path.StartsWith("Assets/") ? path.Substring(7) : path);
+ return Directory.Exists(fullPath);
}
- }
- private static async Task HandleClientAsync(TcpClient client)
- {
- using (client)
- using (var stream = client.GetStream())
+ static UnityMCPBridge()
+ {
+ Start();
+ EditorApplication.quitting += Stop;
+ }
+
+ public static void Start()
+ {
+ if (isRunning) return;
+ isRunning = true;
+ listener = new TcpListener(IPAddress.Loopback, unityPort);
+ listener.Start();
+ Debug.Log($"UnityMCPBridge started on port {unityPort}.");
+ Task.Run(ListenerLoop);
+ EditorApplication.update += ProcessCommands;
+ }
+
+ public static void Stop()
+ {
+ if (!isRunning) return;
+ isRunning = false;
+ listener.Stop();
+ EditorApplication.update -= ProcessCommands;
+ Debug.Log("UnityMCPBridge stopped.");
+ }
+
+ private static async Task ListenerLoop()
{
- var buffer = new byte[8192];
while (isRunning)
{
try
{
- int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
- if (bytesRead == 0) break; // Client disconnected
+ var client = await listener.AcceptTcpClientAsync();
+ // Enable basic socket keepalive
+ client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
- string commandText = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
- string commandId = Guid.NewGuid().ToString();
- var tcs = new TaskCompletionSource();
+ // Set longer receive timeout to prevent quick disconnections
+ client.ReceiveTimeout = 60000; // 60 seconds
- // Special handling for ping command to avoid JSON parsing
- if (commandText.Trim() == "ping")
- {
- // Direct response to ping without going through JSON parsing
- byte[] pingResponseBytes = System.Text.Encoding.UTF8.GetBytes("{\"status\":\"success\",\"result\":{\"message\":\"pong\"}}");
- await stream.WriteAsync(pingResponseBytes, 0, pingResponseBytes.Length);
- continue;
- }
-
- lock (lockObj)
- {
- commandQueue[commandId] = (commandText, tcs);
- }
-
- string response = await tcs.Task;
- byte[] responseBytes = System.Text.Encoding.UTF8.GetBytes(response);
- await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
+ // Fire and forget each client connection
+ _ = HandleClientAsync(client);
}
catch (Exception ex)
{
- Debug.LogError($"Client handler error: {ex.Message}");
- break;
+ if (isRunning) Debug.LogError($"Listener error: {ex.Message}");
}
}
}
- }
- private static void ProcessCommands()
- {
- List processedIds = new();
- lock (lockObj)
+ private static async Task HandleClientAsync(TcpClient client)
{
- foreach (var kvp in commandQueue.ToList())
+ using (client)
+ using (var stream = client.GetStream())
{
- string id = kvp.Key;
- string commandText = kvp.Value.commandJson;
- var tcs = kvp.Value.tcs;
-
- try
+ var buffer = new byte[8192];
+ while (isRunning)
{
- // Special case handling
- if (string.IsNullOrEmpty(commandText))
+ try
{
- var emptyResponse = new
+ int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
+ if (bytesRead == 0) break; // Client disconnected
+
+ string commandText = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
+ string commandId = Guid.NewGuid().ToString();
+ var tcs = new TaskCompletionSource();
+
+ // Special handling for ping command to avoid JSON parsing
+ if (commandText.Trim() == "ping")
+ {
+ // Direct response to ping without going through JSON parsing
+ byte[] pingResponseBytes = System.Text.Encoding.UTF8.GetBytes("{\"status\":\"success\",\"result\":{\"message\":\"pong\"}}");
+ await stream.WriteAsync(pingResponseBytes, 0, pingResponseBytes.Length);
+ continue;
+ }
+
+ lock (lockObj)
+ {
+ commandQueue[commandId] = (commandText, tcs);
+ }
+
+ string response = await tcs.Task;
+ byte[] responseBytes = System.Text.Encoding.UTF8.GetBytes(response);
+ await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Client handler error: {ex.Message}");
+ break;
+ }
+ }
+ }
+ }
+
+ private static void ProcessCommands()
+ {
+ List processedIds = new();
+ lock (lockObj)
+ {
+ foreach (var kvp in commandQueue.ToList())
+ {
+ string id = kvp.Key;
+ string commandText = kvp.Value.commandJson;
+ var tcs = kvp.Value.tcs;
+
+ try
+ {
+ // Special case handling
+ if (string.IsNullOrEmpty(commandText))
+ {
+ var emptyResponse = new
+ {
+ status = "error",
+ error = "Empty command received"
+ };
+ tcs.SetResult(JsonConvert.SerializeObject(emptyResponse));
+ processedIds.Add(id);
+ continue;
+ }
+
+ // Trim the command text to remove any whitespace
+ commandText = commandText.Trim();
+
+ // Non-JSON direct commands handling (like ping)
+ if (commandText == "ping")
+ {
+ var pingResponse = new
+ {
+ status = "success",
+ result = new { message = "pong" }
+ };
+ tcs.SetResult(JsonConvert.SerializeObject(pingResponse));
+ processedIds.Add(id);
+ continue;
+ }
+
+ // Check if the command is valid JSON before attempting to deserialize
+ if (!IsValidJson(commandText))
+ {
+ var invalidJsonResponse = new
+ {
+ status = "error",
+ error = "Invalid JSON format",
+ receivedText = commandText.Length > 50 ? commandText.Substring(0, 50) + "..." : commandText
+ };
+ tcs.SetResult(JsonConvert.SerializeObject(invalidJsonResponse));
+ processedIds.Add(id);
+ continue;
+ }
+
+ // Normal JSON command processing
+ var command = JsonConvert.DeserializeObject(commandText);
+ if (command == null)
+ {
+ var nullCommandResponse = new
+ {
+ status = "error",
+ error = "Command deserialized to null",
+ details = "The command was valid JSON but could not be deserialized to a Command object"
+ };
+ tcs.SetResult(JsonConvert.SerializeObject(nullCommandResponse));
+ }
+ else
+ {
+ string responseJson = ExecuteCommand(command);
+ tcs.SetResult(responseJson);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Error processing command: {ex.Message}\n{ex.StackTrace}");
+
+ var response = new
{
status = "error",
- error = "Empty command received"
+ error = ex.Message,
+ commandType = "Unknown (error during processing)",
+ receivedText = commandText?.Length > 50 ? commandText.Substring(0, 50) + "..." : commandText
};
- tcs.SetResult(JsonConvert.SerializeObject(emptyResponse));
- processedIds.Add(id);
- continue;
- }
-
- // Trim the command text to remove any whitespace
- commandText = commandText.Trim();
-
- // Non-JSON direct commands handling (like ping)
- if (commandText == "ping")
- {
- var pingResponse = new
- {
- status = "success",
- result = new { message = "pong" }
- };
- tcs.SetResult(JsonConvert.SerializeObject(pingResponse));
- processedIds.Add(id);
- continue;
- }
-
- // Check if the command is valid JSON before attempting to deserialize
- if (!IsValidJson(commandText))
- {
- var invalidJsonResponse = new
- {
- status = "error",
- error = "Invalid JSON format",
- receivedText = commandText.Length > 50 ? commandText.Substring(0, 50) + "..." : commandText
- };
- tcs.SetResult(JsonConvert.SerializeObject(invalidJsonResponse));
- processedIds.Add(id);
- continue;
- }
-
- // Normal JSON command processing
- var command = JsonConvert.DeserializeObject(commandText);
- if (command == null)
- {
- var nullCommandResponse = new
- {
- status = "error",
- error = "Command deserialized to null",
- details = "The command was valid JSON but could not be deserialized to a Command object"
- };
- tcs.SetResult(JsonConvert.SerializeObject(nullCommandResponse));
- }
- else
- {
- string responseJson = ExecuteCommand(command);
+ string responseJson = JsonConvert.SerializeObject(response);
tcs.SetResult(responseJson);
}
+
+ processedIds.Add(id);
}
- catch (Exception ex)
+
+ foreach (var id in processedIds)
{
- Debug.LogError($"Error processing command: {ex.Message}\n{ex.StackTrace}");
-
- var response = new
- {
- status = "error",
- error = ex.Message,
- commandType = "Unknown (error during processing)",
- receivedText = commandText?.Length > 50 ? commandText.Substring(0, 50) + "..." : commandText
- };
- string responseJson = JsonConvert.SerializeObject(response);
- tcs.SetResult(responseJson);
+ commandQueue.Remove(id);
}
-
- processedIds.Add(id);
- }
-
- foreach (var id in processedIds)
- {
- commandQueue.Remove(id);
}
}
- }
- // Helper method to check if a string is valid JSON
- private static bool IsValidJson(string text)
- {
- if (string.IsNullOrWhiteSpace(text))
+ // Helper method to check if a string is valid JSON
+ private static bool IsValidJson(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ return false;
+
+ text = text.Trim();
+ if ((text.StartsWith("{") && text.EndsWith("}")) || // Object
+ (text.StartsWith("[") && text.EndsWith("]"))) // Array
+ {
+ try
+ {
+ JToken.Parse(text);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
return false;
+ }
- text = text.Trim();
- if ((text.StartsWith("{") && text.EndsWith("}")) || // Object
- (text.StartsWith("[") && text.EndsWith("]"))) // Array
+ private static string ExecuteCommand(Command command)
{
try
{
- JToken.Parse(text);
- return true;
+ if (string.IsNullOrEmpty(command.type))
+ {
+ var errorResponse = new
+ {
+ status = "error",
+ error = "Command type cannot be empty",
+ details = "A valid command type is required for processing"
+ };
+ return JsonConvert.SerializeObject(errorResponse);
+ }
+
+ // Handle ping command for connection verification
+ if (command.type == "ping")
+ {
+ var pingResponse = new { status = "success", result = new { message = "pong" } };
+ return JsonConvert.SerializeObject(pingResponse);
+ }
+
+ object result = command.type switch
+ {
+ "GET_SCENE_INFO" => SceneCommandHandler.GetSceneInfo(),
+ "OPEN_SCENE" => SceneCommandHandler.OpenScene(command.@params),
+ "SAVE_SCENE" => SceneCommandHandler.SaveScene(),
+ "NEW_SCENE" => SceneCommandHandler.NewScene(command.@params),
+ "CHANGE_SCENE" => SceneCommandHandler.ChangeScene(command.@params),
+ "GET_OBJECT_INFO" => ObjectCommandHandler.GetObjectInfo(command.@params),
+ "CREATE_OBJECT" => ObjectCommandHandler.CreateObject(command.@params),
+ "MODIFY_OBJECT" => ObjectCommandHandler.ModifyObject(command.@params),
+ "DELETE_OBJECT" => ObjectCommandHandler.DeleteObject(command.@params),
+ "GET_OBJECT_PROPERTIES" => ObjectCommandHandler.GetObjectProperties(command.@params),
+ "GET_COMPONENT_PROPERTIES" => ObjectCommandHandler.GetComponentProperties(command.@params),
+ "FIND_OBJECTS_BY_NAME" => ObjectCommandHandler.FindObjectsByName(command.@params),
+ "FIND_OBJECTS_BY_TAG" => ObjectCommandHandler.FindObjectsByTag(command.@params),
+ "GET_HIERARCHY" => ObjectCommandHandler.GetHierarchy(),
+ "SELECT_OBJECT" => ObjectCommandHandler.SelectObject(command.@params),
+ "GET_SELECTED_OBJECT" => ObjectCommandHandler.GetSelectedObject(),
+ "SET_MATERIAL" => MaterialCommandHandler.SetMaterial(command.@params),
+ "VIEW_SCRIPT" => ScriptCommandHandler.ViewScript(command.@params),
+ "CREATE_SCRIPT" => ScriptCommandHandler.CreateScript(command.@params),
+ "UPDATE_SCRIPT" => ScriptCommandHandler.UpdateScript(command.@params),
+ "LIST_SCRIPTS" => ScriptCommandHandler.ListScripts(command.@params),
+ "ATTACH_SCRIPT" => ScriptCommandHandler.AttachScript(command.@params),
+ "IMPORT_ASSET" => AssetCommandHandler.ImportAsset(command.@params),
+ "INSTANTIATE_PREFAB" => AssetCommandHandler.InstantiatePrefab(command.@params),
+ "CREATE_PREFAB" => AssetCommandHandler.CreatePrefab(command.@params),
+ "APPLY_PREFAB" => AssetCommandHandler.ApplyPrefab(command.@params),
+ "GET_ASSET_LIST" => AssetCommandHandler.GetAssetList(command.@params),
+ "EDITOR_CONTROL" => EditorControlHandler.HandleEditorControl(command.@params),
+ _ => throw new Exception($"Unknown command type: {command.type}")
+ };
+
+ var response = new { status = "success", result };
+ return JsonConvert.SerializeObject(response);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"Error executing command {command.type}: {ex.Message}\n{ex.StackTrace}");
+ var response = new
+ {
+ status = "error",
+ error = ex.Message,
+ command = command.type,
+ stackTrace = ex.StackTrace,
+ paramsSummary = command.@params != null ? GetParamsSummary(command.@params) : "No parameters"
+ };
+ return JsonConvert.SerializeObject(response);
+ }
+ }
+
+ // Helper method to get a summary of parameters for error reporting
+ private static string GetParamsSummary(JObject @params)
+ {
+ try
+ {
+ if (@params == null || !@params.HasValues)
+ return "No parameters";
+
+ return string.Join(", ", @params.Properties().Select(p => $"{p.Name}: {p.Value?.ToString()?.Substring(0, Math.Min(20, p.Value?.ToString()?.Length ?? 0))}"));
}
catch
{
- return false;
+ return "Could not summarize parameters";
}
}
-
- return false;
- }
-
- private static string ExecuteCommand(Command command)
- {
- try
- {
- if (string.IsNullOrEmpty(command.type))
- {
- var errorResponse = new
- {
- status = "error",
- error = "Command type cannot be empty",
- details = "A valid command type is required for processing"
- };
- return JsonConvert.SerializeObject(errorResponse);
- }
-
- // Handle ping command for connection verification
- if (command.type == "ping")
- {
- var pingResponse = new { status = "success", result = new { message = "pong" } };
- return JsonConvert.SerializeObject(pingResponse);
- }
-
- object result = command.type switch
- {
- "GET_SCENE_INFO" => SceneCommandHandler.GetSceneInfo(),
- "OPEN_SCENE" => SceneCommandHandler.OpenScene(command.@params),
- "SAVE_SCENE" => SceneCommandHandler.SaveScene(),
- "NEW_SCENE" => SceneCommandHandler.NewScene(command.@params),
- "CHANGE_SCENE" => SceneCommandHandler.ChangeScene(command.@params),
- "GET_OBJECT_INFO" => ObjectCommandHandler.GetObjectInfo(command.@params),
- "CREATE_OBJECT" => ObjectCommandHandler.CreateObject(command.@params),
- "MODIFY_OBJECT" => ObjectCommandHandler.ModifyObject(command.@params),
- "DELETE_OBJECT" => ObjectCommandHandler.DeleteObject(command.@params),
- "GET_OBJECT_PROPERTIES" => ObjectCommandHandler.GetObjectProperties(command.@params),
- "GET_COMPONENT_PROPERTIES" => ObjectCommandHandler.GetComponentProperties(command.@params),
- "FIND_OBJECTS_BY_NAME" => ObjectCommandHandler.FindObjectsByName(command.@params),
- "FIND_OBJECTS_BY_TAG" => ObjectCommandHandler.FindObjectsByTag(command.@params),
- "GET_HIERARCHY" => ObjectCommandHandler.GetHierarchy(),
- "SELECT_OBJECT" => ObjectCommandHandler.SelectObject(command.@params),
- "GET_SELECTED_OBJECT" => ObjectCommandHandler.GetSelectedObject(),
- "SET_MATERIAL" => MaterialCommandHandler.SetMaterial(command.@params),
- "VIEW_SCRIPT" => ScriptCommandHandler.ViewScript(command.@params),
- "CREATE_SCRIPT" => ScriptCommandHandler.CreateScript(command.@params),
- "UPDATE_SCRIPT" => ScriptCommandHandler.UpdateScript(command.@params),
- "LIST_SCRIPTS" => ScriptCommandHandler.ListScripts(command.@params),
- "ATTACH_SCRIPT" => ScriptCommandHandler.AttachScript(command.@params),
- "IMPORT_ASSET" => AssetCommandHandler.ImportAsset(command.@params),
- "INSTANTIATE_PREFAB" => AssetCommandHandler.InstantiatePrefab(command.@params),
- "CREATE_PREFAB" => AssetCommandHandler.CreatePrefab(command.@params),
- "APPLY_PREFAB" => AssetCommandHandler.ApplyPrefab(command.@params),
- "GET_ASSET_LIST" => AssetCommandHandler.GetAssetList(command.@params),
- "EDITOR_CONTROL" => EditorControlHandler.HandleEditorControl(command.@params),
- _ => throw new Exception($"Unknown command type: {command.type}")
- };
-
- var response = new { status = "success", result };
- return JsonConvert.SerializeObject(response);
- }
- catch (Exception ex)
- {
- Debug.LogError($"Error executing command {command.type}: {ex.Message}\n{ex.StackTrace}");
- var response = new
- {
- status = "error",
- error = ex.Message,
- command = command.type,
- stackTrace = ex.StackTrace,
- paramsSummary = command.@params != null ? GetParamsSummary(command.@params) : "No parameters"
- };
- return JsonConvert.SerializeObject(response);
- }
- }
-
- // Helper method to get a summary of parameters for error reporting
- private static string GetParamsSummary(JObject @params)
- {
- try
- {
- if (@params == null || !@params.HasValues)
- return "No parameters";
-
- return string.Join(", ", @params.Properties().Select(p => $"{p.Name}: {p.Value?.ToString()?.Substring(0, Math.Min(20, p.Value?.ToString()?.Length ?? 0))}"));
- }
- catch
- {
- return "Could not summarize parameters";
- }
}
}
\ No newline at end of file
diff --git a/Editor/Windows.meta b/Editor/Windows.meta
new file mode 100644
index 0000000..eda016e
--- /dev/null
+++ b/Editor/Windows.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d2ee39f5d4171184eb208e865c1ef4c1
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Windows/ManualConfigEditorWindow.cs b/Editor/Windows/ManualConfigEditorWindow.cs
new file mode 100644
index 0000000..dd1ca05
--- /dev/null
+++ b/Editor/Windows/ManualConfigEditorWindow.cs
@@ -0,0 +1,201 @@
+using UnityEngine;
+using UnityEditor;
+using System.Runtime.InteropServices;
+using UnityMCP.Editor.Models;
+
+namespace UnityMCP.Editor.Windows
+{
+ // Editor window to display manual configuration instructions
+ public class ManualConfigEditorWindow : EditorWindow
+ {
+ private string configPath;
+ private string configJson;
+ private Vector2 scrollPos;
+ private bool pathCopied = false;
+ private bool jsonCopied = false;
+ private float copyFeedbackTimer = 0;
+ private McpClient mcpClient;
+
+ public static void ShowWindow(string configPath, string configJson, McpClient mcpClient)
+ {
+ var window = GetWindow("Manual Configuration");
+ window.configPath = configPath;
+ window.configJson = configJson;
+ window.mcpClient = mcpClient;
+ window.minSize = new Vector2(500, 400);
+ window.Show();
+ }
+
+ private void OnGUI()
+ {
+ scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
+
+ // Header with improved styling
+ EditorGUILayout.Space(10);
+ 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),
+ mcpClient.name + " Manual Configuration", EditorStyles.boldLabel);
+ EditorGUILayout.Space(10);
+
+ // Instructions with improved styling
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ Rect headerRect = EditorGUILayout.GetControlRect(false, 24);
+ EditorGUI.DrawRect(new Rect(headerRect.x, headerRect.y, headerRect.width, headerRect.height), new Color(0.1f, 0.1f, 0.1f, 0.2f));
+ GUI.Label(new Rect(headerRect.x + 8, headerRect.y + 4, headerRect.width - 16, headerRect.height),
+ "The automatic configuration failed. Please follow these steps:", EditorStyles.boldLabel);
+ EditorGUILayout.Space(10);
+
+ GUIStyle instructionStyle = new(EditorStyles.wordWrappedLabel)
+ {
+ margin = new RectOffset(10, 10, 5, 5)
+ };
+
+ EditorGUILayout.LabelField("1. Open " + mcpClient.name + " config file by either:", instructionStyle);
+ if (mcpClient.mcpType == McpTypes.ClaudeDesktop)
+ {
+ EditorGUILayout.LabelField(" a) Going to Settings > Developer > Edit Config", instructionStyle);
+ }
+ else if (mcpClient.mcpType == McpTypes.Cursor)
+ {
+ EditorGUILayout.LabelField(" a) Going to File > Preferences > Cursor Settings > MCP > Add new global MCP server", instructionStyle);
+ }
+ EditorGUILayout.LabelField(" OR", instructionStyle);
+ EditorGUILayout.LabelField(" b) Opening the configuration file at:", instructionStyle);
+
+ // Path section with improved styling
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ string displayPath;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ displayPath = mcpClient.windowsConfigPath;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ displayPath = mcpClient.linuxConfigPath;
+ }
+ else
+ {
+ displayPath = configPath;
+ }
+
+ // Prevent text overflow by allowing the text field to wrap
+ GUIStyle pathStyle = new(EditorStyles.textField)
+ {
+ wordWrap = true
+ };
+
+ EditorGUILayout.TextField(displayPath, pathStyle, GUILayout.Height(EditorGUIUtility.singleLineHeight));
+
+ // Copy button with improved styling
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.FlexibleSpace();
+ GUIStyle copyButtonStyle = new(GUI.skin.button)
+ {
+ padding = new RectOffset(15, 15, 5, 5),
+ margin = new RectOffset(10, 10, 5, 5)
+ };
+
+ if (GUILayout.Button("Copy Path", copyButtonStyle, GUILayout.Height(25), GUILayout.Width(100)))
+ {
+ EditorGUIUtility.systemCopyBuffer = displayPath;
+ pathCopied = true;
+ copyFeedbackTimer = 2f;
+ }
+
+ if (GUILayout.Button("Open File", copyButtonStyle, GUILayout.Height(25), GUILayout.Width(100)))
+ {
+ // Open the file using the system's default application
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = displayPath,
+ UseShellExecute = true
+ });
+ }
+
+ if (pathCopied)
+ {
+ GUIStyle feedbackStyle = new(EditorStyles.label);
+ feedbackStyle.normal.textColor = Color.green;
+ EditorGUILayout.LabelField("Copied!", feedbackStyle, GUILayout.Width(60));
+ }
+
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.Space(10);
+
+ EditorGUILayout.LabelField("2. Paste the following JSON configuration:", instructionStyle);
+
+ // JSON section with improved styling
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+
+ // Improved text area for JSON with syntax highlighting colors
+ GUIStyle jsonStyle = new(EditorStyles.textArea)
+ {
+ font = EditorStyles.boldFont,
+ wordWrap = true
+ };
+ jsonStyle.normal.textColor = new Color(0.3f, 0.6f, 0.9f); // Syntax highlighting blue
+
+ // Draw the JSON in a text area with a taller height for better readability
+ EditorGUILayout.TextArea(configJson, jsonStyle, GUILayout.Height(200));
+
+ // Copy JSON button with improved styling
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.FlexibleSpace();
+
+ if (GUILayout.Button("Copy JSON", copyButtonStyle, GUILayout.Height(25), GUILayout.Width(100)))
+ {
+ EditorGUIUtility.systemCopyBuffer = configJson;
+ jsonCopied = true;
+ copyFeedbackTimer = 2f;
+ }
+
+ if (jsonCopied)
+ {
+ GUIStyle feedbackStyle = new(EditorStyles.label);
+ feedbackStyle.normal.textColor = Color.green;
+ EditorGUILayout.LabelField("Copied!", feedbackStyle, GUILayout.Width(60));
+ }
+
+ EditorGUILayout.EndHorizontal();
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField("3. Save the file and restart " + mcpClient.name, instructionStyle);
+
+ EditorGUILayout.EndVertical();
+
+ EditorGUILayout.Space(10);
+
+ // Close button at the bottom
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.FlexibleSpace();
+ if (GUILayout.Button("Close", GUILayout.Height(30), GUILayout.Width(100)))
+ {
+ Close();
+ }
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private void Update()
+ {
+ // Handle the feedback message timer
+ if (copyFeedbackTimer > 0)
+ {
+ copyFeedbackTimer -= Time.deltaTime;
+ if (copyFeedbackTimer <= 0)
+ {
+ pathCopied = false;
+ jsonCopied = false;
+ Repaint();
+ }
+ }
+ }
+ }
+}
diff --git a/Editor/Windows/ManualConfigEditorWindow.cs.meta b/Editor/Windows/ManualConfigEditorWindow.cs.meta
new file mode 100644
index 0000000..b5797cc
--- /dev/null
+++ b/Editor/Windows/ManualConfigEditorWindow.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 36798bd7b867b8e43ac86885e94f928f
\ No newline at end of file
diff --git a/Editor/Windows/UnityMCPEditorWindow.cs b/Editor/Windows/UnityMCPEditorWindow.cs
new file mode 100644
index 0000000..999761a
--- /dev/null
+++ b/Editor/Windows/UnityMCPEditorWindow.cs
@@ -0,0 +1,663 @@
+using UnityEngine;
+using UnityEditor;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System;
+using Newtonsoft.Json;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Text;
+using System.Collections.Generic;
+using System.Linq;
+using UnityMCP.Editor.Models;
+using UnityMCP.Editor.Data;
+
+namespace UnityMCP.Editor.Windows
+{
+ public class UnityMCPEditorWindow : EditorWindow
+ {
+ private bool isUnityBridgeRunning = false;
+ private Vector2 scrollPosition;
+ private string claudeConfigStatus = "Not configured";
+ private string cursorConfigStatus = "Not configured";
+ private string pythonServerStatus = "Not Connected";
+ private Color pythonServerStatusColor = Color.red;
+ private const int unityPort = 6400; // Hardcoded Unity port
+ private const int mcpPort = 6500; // Hardcoded MCP port
+ private const float CONNECTION_CHECK_INTERVAL = 2f; // Check every 2 seconds
+ private float lastCheckTime = 0f;
+ private McpClients mcpClients = new();
+
+ private List possiblePaths = new()
+ {
+ Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python", "server.py")),
+ Path.GetFullPath(Path.Combine(Application.dataPath, "Packages", "com.justinpbarnett.unity-mcp", "Python", "server.py")),
+ Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Library", "PackageCache", "com.justinpbarnett.unity-mcp@*", "Python", "server.py")),
+ Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Packages", "com.justinpbarnett.unity-mcp", "Python", "server.py"))
+ };
+
+ [MenuItem("Window/Unity MCP")]
+ public static void ShowWindow()
+ {
+ GetWindow("MCP Editor");
+ }
+
+ private void OnEnable()
+ {
+ // Check initial states
+ isUnityBridgeRunning = UnityMCPBridge.IsRunning;
+ CheckPythonServerConnection();
+ foreach (McpClient mcpClient in mcpClients.clients)
+ {
+ CheckMcpConfiguration(mcpClient);
+ }
+ }
+
+ private void Update()
+ {
+ // Check Python server connection periodically
+ if (Time.realtimeSinceStartup - lastCheckTime >= CONNECTION_CHECK_INTERVAL)
+ {
+ CheckPythonServerConnection();
+ lastCheckTime = Time.realtimeSinceStartup;
+ }
+ }
+
+ private async void CheckPythonServerConnection()
+ {
+ try
+ {
+ using (var client = new TcpClient())
+ {
+ // Try to connect with a short timeout
+ var connectTask = client.ConnectAsync("localhost", unityPort);
+ if (await Task.WhenAny(connectTask, Task.Delay(1000)) == connectTask)
+ {
+ // Try to send a ping message to verify connection is alive
+ try
+ {
+ NetworkStream stream = client.GetStream();
+ byte[] pingMessage = Encoding.UTF8.GetBytes("ping");
+ await stream.WriteAsync(pingMessage, 0, pingMessage.Length);
+
+ // Wait for response with timeout
+ byte[] buffer = new byte[1024];
+ var readTask = stream.ReadAsync(buffer, 0, buffer.Length);
+ if (await Task.WhenAny(readTask, Task.Delay(1000)) == readTask)
+ {
+ int bytesRead = await readTask;
+ if (bytesRead <= 0)
+ {
+ // Received empty response
+ pythonServerStatus = "Invalid Response";
+ pythonServerStatusColor = GetStatusColor(McpStatus.NoResponse);
+ return;
+ }
+
+ // Validate the response is actually from our server
+ string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+ if (response.Contains("pong"))
+ {
+ // Connection successful and responsive with valid response
+ pythonServerStatus = "Connected";
+ pythonServerStatusColor = GetStatusColor(McpStatus.Connected);
+ }
+ else
+ {
+ // Received response but not the expected one
+ pythonServerStatus = "Invalid Server";
+ pythonServerStatusColor = GetStatusColor(McpStatus.CommunicationError);
+ }
+ }
+ else
+ {
+ // No response received
+ pythonServerStatus = "No Response";
+ pythonServerStatusColor = GetStatusColor(McpStatus.NoResponse);
+ UnityEngine.Debug.LogWarning($"Python server not responding on port {unityPort}");
+ }
+ }
+ catch (Exception e)
+ {
+ // Connection established but communication failed
+ pythonServerStatus = "Communication Error";
+ pythonServerStatusColor = GetStatusColor(McpStatus.CommunicationError);
+ UnityEngine.Debug.LogWarning($"Error communicating with Python server: {e.Message}");
+ }
+ }
+ else
+ {
+ // Connection failed
+ pythonServerStatus = "Not Connected";
+ pythonServerStatusColor = GetStatusColor(McpStatus.NotConfigured);
+ UnityEngine.Debug.LogWarning($"Python server is not running or not accessible on port {unityPort}");
+ }
+ client.Close();
+ }
+ }
+ catch (Exception e)
+ {
+ pythonServerStatus = "Connection Error";
+ pythonServerStatusColor = GetStatusColor(McpStatus.Error);
+ UnityEngine.Debug.LogError($"Error checking Python server connection: {e.Message}");
+ }
+ }
+
+ private Color GetStatusColor(McpStatus status)
+ {
+ // Return appropriate color based on the status enum
+ return status switch
+ {
+ McpStatus.Configured => Color.green,
+ McpStatus.Running => Color.green,
+ McpStatus.Connected => Color.green,
+ McpStatus.IncorrectPath => Color.yellow,
+ McpStatus.CommunicationError => Color.yellow,
+ McpStatus.NoResponse => Color.yellow,
+ _ => Color.red // Default to red for error states or not configured
+ };
+ }
+
+ private void ConfigurationSection(McpClient mcpClient)
+ {
+ // 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);
+ buttonStyle.padding = new RectOffset(15, 15, 5, 5);
+ buttonStyle.margin = new RectOffset(10, 10, 5, 5);
+
+ // Create muted button style for Manual Setup
+ GUIStyle mutedButtonStyle = new(buttonStyle);
+
+ 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);
+ 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 Status Section
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel);
+
+ // Status indicator with colored dot
+ var statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
+ DrawStatusDot(statusRect, pythonServerStatusColor);
+ EditorGUILayout.LabelField(" " + pythonServerStatus);
+ 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(10);
+ ConfigurationSection(mcpClient);
+ }
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private void ToggleUnityBridge()
+ {
+ if (isUnityBridgeRunning)
+ {
+ UnityMCPBridge.Stop();
+ }
+ else
+ {
+ UnityMCPBridge.Start();
+ }
+
+ isUnityBridgeRunning = !isUnityBridgeRunning;
+ }
+
+
+ private string GetPythonDirectory(List possiblePaths)
+ {
+ foreach (var path in possiblePaths)
+ {
+ // Skip wildcard paths for now
+ if (path.Contains("*")) continue;
+
+ if (File.Exists(path))
+ {
+ return Path.GetDirectoryName(path);
+ }
+ }
+
+ foreach (var path in possiblePaths)
+ {
+ if (!path.Contains("*")) continue;
+
+ string directoryPath = Path.GetDirectoryName(path);
+ string searchPattern = Path.GetFileName(Path.GetDirectoryName(path));
+ string parentDir = Path.GetDirectoryName(directoryPath);
+
+ if (Directory.Exists(parentDir))
+ {
+ var matchingDirs = Directory.GetDirectories(parentDir, searchPattern);
+
+ foreach (var dir in matchingDirs)
+ {
+ string candidatePath = Path.Combine(dir, "Python", "server.py");
+
+ if (File.Exists(candidatePath))
+ {
+ return Path.GetDirectoryName(candidatePath);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private string WriteToConfig(string pythonDir, string configPath)
+ {
+ // Create configuration object for unityMCP
+ var unityMCPConfig = new MCPConfigServer
+ {
+ command = "uv",
+ args = new[]
+ {
+ "--directory",
+ pythonDir,
+ "run",
+ "server.py"
+ }
+ };
+
+ var jsonSettings = new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented
+ };
+
+ // Read existing config if it exists
+ string existingJson = "{}";
+ if (File.Exists(configPath))
+ {
+ try
+ {
+ existingJson = File.ReadAllText(configPath);
+ }
+ catch (Exception e)
+ {
+ UnityEngine.Debug.LogWarning($"Error reading existing config: {e.Message}.");
+ }
+ }
+
+ // Parse the existing JSON while preserving all properties
+ dynamic existingConfig = JsonConvert.DeserializeObject(existingJson);
+ if (existingConfig == null)
+ {
+ existingConfig = new Newtonsoft.Json.Linq.JObject();
+ }
+
+ // Ensure mcpServers object exists
+ if (existingConfig.mcpServers == null)
+ {
+ existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
+ }
+
+ // Add/update unityMCP while preserving other servers
+ existingConfig.mcpServers.unityMCP = JsonConvert.DeserializeObject(
+ JsonConvert.SerializeObject(unityMCPConfig)
+ );
+
+ // Write the merged configuration back to file
+ string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
+ File.WriteAllText(configPath, mergedJson);
+
+ return "Configured successfully";
+ }
+
+ private void ShowManualConfigurationInstructions(string configPath, McpClient mcpClient)
+ {
+ mcpClient.SetStatus(McpStatus.Error, "Manual configuration required");
+
+ ShowManualInstructionsWindow(configPath, mcpClient);
+ }
+
+ // New method to show manual instructions without changing status
+ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient)
+ {
+ // Get the Python directory path using Package Manager API
+ string pythonDir = FindPackagePythonDirectory();
+
+ // Create the manual configuration message
+ var jsonConfig = new MCPConfig
+ {
+ mcpServers = new MCPConfigServers
+ {
+ unityMCP = new MCPConfigServer
+ {
+ command = "uv",
+ args = new[]
+ {
+ "--directory",
+ pythonDir,
+ "run",
+ "server.py"
+ }
+ }
+ }
+ };
+
+ var jsonSettings = new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented
+ };
+ string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
+
+ ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
+ }
+
+ private string FindPackagePythonDirectory()
+ {
+ string pythonDir = "/path/to/your/unity-mcp/Python";
+
+ try
+ {
+ // Try to find the package using Package Manager API
+ var request = UnityEditor.PackageManager.Client.List();
+ while (!request.IsCompleted) { } // Wait for the request to complete
+
+ if (request.Status == UnityEditor.PackageManager.StatusCode.Success)
+ {
+ foreach (var package in request.Result)
+ {
+ if (package.name == "com.justinpbarnett.unity-mcp")
+ {
+ string packagePath = package.resolvedPath;
+ string potentialPythonDir = Path.Combine(packagePath, "Python");
+
+ if (Directory.Exists(potentialPythonDir) &&
+ File.Exists(Path.Combine(potentialPythonDir, "server.py")))
+ {
+ return potentialPythonDir;
+ }
+ }
+ }
+ }
+ else if (request.Error != null)
+ {
+ UnityEngine.Debug.LogError("Failed to list packages: " + request.Error.message);
+ }
+
+ // If not found via Package Manager, try manual approaches
+ // First check for local installation
+ string[] possibleDirs = {
+ Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python"))
+ };
+
+ foreach (var dir in possibleDirs)
+ {
+ if (Directory.Exists(dir) && File.Exists(Path.Combine(dir, "server.py")))
+ {
+ return dir;
+ }
+ }
+
+ // If still not found, return the placeholder path
+ UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path");
+ }
+ catch (Exception e)
+ {
+ UnityEngine.Debug.LogError($"Error finding package path: {e.Message}");
+ }
+
+ return pythonDir;
+ }
+
+ private string ConfigureMcpClient(McpClient mcpClient)
+ {
+ try
+ {
+ // Determine the config file path based on OS
+ string configPath;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ configPath = mcpClient.windowsConfigPath;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ configPath = mcpClient.linuxConfigPath;
+ }
+ else
+ {
+ return "Unsupported OS";
+ }
+
+ // Create directory if it doesn't exist
+ Directory.CreateDirectory(Path.GetDirectoryName(configPath));
+
+ // Find the server.py file location
+ string pythonDir = GetPythonDirectory(possiblePaths);
+
+ if (pythonDir == null || !File.Exists(Path.Combine(pythonDir, "server.py")))
+ {
+ ShowManualInstructionsWindow(configPath, mcpClient);
+ return "Manual Configuration Required";
+ }
+
+ string result = WriteToConfig(pythonDir, configPath);
+
+ // Update the client status after successful configuration
+ if (result == "Configured successfully")
+ {
+ mcpClient.SetStatus(McpStatus.Configured);
+ }
+
+ return result;
+ }
+ catch (Exception e)
+ {
+ // Determine the config file path based on OS for error message
+ string configPath = "";
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ configPath = mcpClient.windowsConfigPath;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ configPath = mcpClient.linuxConfigPath;
+ }
+
+ ShowManualInstructionsWindow(configPath, mcpClient);
+ UnityEngine.Debug.LogError($"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}");
+ return $"Failed to configure {mcpClient.name}";
+ }
+ }
+
+
+ private void ShowCursorManualConfigurationInstructions(string configPath, McpClient mcpClient)
+ {
+ mcpClient.SetStatus(McpStatus.Error, "Manual configuration required");
+
+ // Get the Python directory path using Package Manager API
+ string pythonDir = FindPackagePythonDirectory();
+
+ // Create the manual configuration message
+ var jsonConfig = new MCPConfig
+ {
+ mcpServers = new MCPConfigServers
+ {
+ unityMCP = new MCPConfigServer
+ {
+ command = "uv",
+ args = new[]
+ {
+ "--directory",
+ pythonDir,
+ "run",
+ "server.py"
+ }
+ }
+ }
+ };
+
+ var jsonSettings = new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented
+ };
+ string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
+
+ ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
+ }
+
+ private void CheckMcpConfiguration(McpClient mcpClient)
+ {
+ try
+ {
+ string configPath;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ configPath = mcpClient.windowsConfigPath;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ configPath = mcpClient.linuxConfigPath;
+ }
+ else
+ {
+ mcpClient.SetStatus(McpStatus.UnsupportedOS);
+ return;
+ }
+
+ if (!File.Exists(configPath))
+ {
+ mcpClient.SetStatus(McpStatus.NotConfigured);
+ return;
+ }
+
+ string configJson = File.ReadAllText(configPath);
+ var config = JsonConvert.DeserializeObject(configJson);
+
+ if (config?.mcpServers?.unityMCP != null)
+ {
+ string pythonDir = GetPythonDirectory(possiblePaths);
+ if (pythonDir != null && Array.Exists(config.mcpServers.unityMCP.args, arg => arg.Contains(pythonDir, StringComparison.Ordinal)))
+ {
+ mcpClient.SetStatus(McpStatus.Configured);
+ }
+ else
+ {
+ mcpClient.SetStatus(McpStatus.IncorrectPath);
+ }
+ }
+ else
+ {
+ mcpClient.SetStatus(McpStatus.MissingConfig);
+ }
+ }
+ catch (Exception e)
+ {
+ mcpClient.SetStatus(McpStatus.Error, e.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/MCPEditorWindow.cs.meta b/Editor/Windows/UnityMCPEditorWindow.cs.meta
similarity index 100%
rename from Editor/MCPEditorWindow.cs.meta
rename to Editor/Windows/UnityMCPEditorWindow.cs.meta
diff --git a/Python/tools/material_tools.py b/Python/tools/material_tools.py
index 47cbf2d..01df0f8 100644
--- a/Python/tools/material_tools.py
+++ b/Python/tools/material_tools.py
@@ -1,5 +1,5 @@
from mcp.server.fastmcp import FastMCP, Context
-from typing import List
+from typing import List, Optional
from unity_connection import get_unity_connection
def register_material_tools(mcp: FastMCP):
@@ -9,18 +9,22 @@ def register_material_tools(mcp: FastMCP):
def set_material(
ctx: Context,
object_name: str,
- material_name: str = None,
- color: List[float] = None,
+ material_name: Optional[str] = None,
+ color: Optional[List[float]] = None,
create_if_missing: bool = True
) -> str:
"""
- Apply or create a material for a game object.
+ Apply or create a material for a game object. If material_name is provided,
+ the material will be saved as a shared asset in the Materials folder.
Args:
object_name: Target game object.
- material_name: Optional material name.
- color: Optional [R, G, B] values (0.0-1.0).
+ material_name: Optional material name. If provided, creates/uses a shared material asset.
+ color: Optional [R, G, B] or [R, G, B, A] values (0.0-1.0).
create_if_missing: Whether to create the material if it doesn't exist (default: True).
+
+ Returns:
+ str: Status message indicating success or failure.
"""
try:
unity = get_unity_connection()
@@ -63,14 +67,23 @@ def register_material_tools(mcp: FastMCP):
return f"Error: Color {channel} value must be in the range 0.0-1.0, but got {value}."
# Set up parameters for the command
- params = {"object_name": object_name}
+ params = {
+ "object_name": object_name,
+ "create_if_missing": create_if_missing
+ }
if material_name:
params["material_name"] = material_name
- params["create_if_missing"] = create_if_missing
if color:
params["color"] = color
result = unity.send_command("SET_MATERIAL", params)
- return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}"
+ material_name = result.get("material_name", "unknown")
+ material_path = result.get("path")
+
+ if material_path:
+ return f"Applied shared material '{material_name}' to {object_name} (saved at {material_path})"
+ else:
+ return f"Applied instance material '{material_name}' to {object_name}"
+
except Exception as e:
return f"Error setting material: {str(e)}"
\ No newline at end of file
diff --git a/Python/unity_mcp.egg-info.meta b/Python/unity_mcp.egg-info.meta
new file mode 100644
index 0000000..21940ff
--- /dev/null
+++ b/Python/unity_mcp.egg-info.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 714de9c710feb1a42878a16b7a4e7a6f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/README.md b/README.md
index b997a98..68251f8 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
A Unity package that enables seamless communication between Unity and Large Language Models (LLMs) like Claude Desktop via the **Model Context Protocol (MCP)**. This server acts as a bridge, allowing Unity to send commands to and receive responses from MCP-compliant tools, empowering developers to automate workflows, manipulate assets, and control the Unity Editor programmatically.
-Welcome to the initial release of this open-source project! Whether you're looking to integrate LLMs into your Unity workflow or contribute to an exciting new tool, we're thrilled to have you here.
+Welcome to the initial release of this open-source project! Whether you're looking to integrate LLMs into your Unity workflow or contribute to an exciting new tool, I appreciate you taking the time to check out my project.
## Overview
@@ -20,7 +20,7 @@ This project is perfect for developers who want to leverage LLMs to enhance thei
### Prerequisites
-- Unity 2020.3 LTS or newer
+- Unity 2020.3 LTS or newer (⚠️ only works in URP projects currently)
- Python 3.7 or newer
- uv package manager
@@ -70,16 +70,17 @@ Otherwise, installation instructions are on their website: [Install uv](https://
uv pip install -e .
```
-### Claude Desktop Integration
+### MCP Client Integration
1. Open the Unity MCP window (`Window > Unity MCP`)
-2. Click the "Configure Claude" button
-3. Follow the on-screen instructions to set up the integration
+2. Click the "Auto Configure" button for your desired MCP client
+3. Status indicator should show green and a "Configured" message
-Alternatively, manually configure Claude Desktop:
+Alternatively, manually configure your MCP client:
-1. Go to Claude > Settings > Developer > Edit Config
-2. Edit `claude_desktop_config.json` to include:
+1. Open the Unity MCP window (`Window > Unity MCP`)
+2. Click the "Manually Configure" button for your desired MCP client
+3. Copy the JSON code below to the config file
```json
{
@@ -99,100 +100,15 @@ Alternatively, manually configure Claude Desktop:
Replace `/path/to/your/unity-mcp/Python` with the actual path to the Unity MCP Python directory.
-### Cursor Integration
-
-1. Open the Unity MCP window (`Window > Unity MCP`)
-2. Click the "Configure Cursor" button
-3. Follow the on-screen instructions to set up the integration
-
-Alternatively, go to Cursor Settings > MCP and paste this as a command:
-
-```bash
-uv --directory "/path/to/your/unity-mcp/Python" run server.py
-```
-
-Replace `/path/to/your/unity-mcp/Python` with the actual path to the Unity MCP Python directory.
-
**⚠️ Only run one instance of the MCP server (either on Cursor or Claude Desktop), not both**
4. **Start Claude Desktop or Cursor**
- Launch your preferred tool
- - The Unity MCP Server will automatically connect
-
-## Configuration
-
-To connect the MCP Server to tools like Claude Desktop or Cursor:
-
-1. **Open the Unity MCP Window**
- In Unity, go to `Window > Unity MCP` to open the editor window.
-
-2. **Configure Your Tools**
-
- - In the Unity MCP window, you'll see buttons to configure **Claude Desktop** or **Cursor**.
- - Click the appropriate button and follow the on-screen instructions to set up the integration.
-
-3. **Verify Server Status**
- - Check the server status in the Unity MCP window. It will display:
- - **Unity Bridge**: Should show "Running" when active.
- - **Python Server**: Should show "Connected" (green) when successfully linked.
-
-## Manual Configuration for MCP Clients
-
-If you prefer to manually configure your MCP client (like Claude Desktop or Cursor), you can create the configuration file yourself:
-
-1. **Locate the Configuration Directory**
-
- - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
-
-2. **Create the Configuration File**
- Create a JSON file with the following structure:
-
- ```json
- {
- "mcpServers": {
- "unityMCP": {
- "command": "uv",
- "args": [
- "--directory",
- "/path/to/your/unity-mcp/Python",
- "run",
- "server.py"
- ]
- }
- }
- }
- ```
-
-3. **Find the Correct Python Path**
-
- - If installed as a package: Look in `Library/PackageCache/com.justinpbarnett.unity-mcp/Python`
- - If installed locally: Look in `Assets/unity-mcp/Python`
-
-4. **Verify Configuration**
- - Ensure the Python path points to the correct directory containing `server.py`
- - Make sure the `uv` command is available in your system PATH
- - Test the connection using the Unity MCP window
+ - The Unity MCP Server will automatically start and connect
## Usage
-Once configured, you can use the MCP Server to interact with LLMs directly from Unity or Python. Here are a couple of examples:
-
-### Creating a Cube in the Scene
-
-```python
-# Send a command to create a cube at position (0, 0, 0)
-create_primitive(primitive_type="Cube", position=[0, 0, 0])
-```
-
-### Changing a Material's Color
-
-```python
-# Set a material's color to red (RGBA: 1, 0, 0, 1)
-set_material_color(material_name="MyMaterial", color=[1, 0, 0, 1])
-```
-
-Explore more commands in the [HOW_TO_ADD_A_TOOL.md](HOW_TO_ADD_A_TOOL.md) file for detailed examples and instructions on extending functionality.
+Once configured, you can use the MCP Client to interact with Unity directly through their chat interface.
## Features
@@ -205,7 +121,7 @@ Explore more commands in the [HOW_TO_ADD_A_TOOL.md](HOW_TO_ADD_A_TOOL.md) file f
## Contributing
-We'd love your help to make the Unity MCP Server even better! Here's how to contribute:
+I'd love your help to make the Unity MCP Server even better! Here's how to contribute:
1. **Fork the Repository**
Fork [github.com/justinpbarnett/unity-mcp](https://github.com/justinpbarnett/unity-mcp) to your GitHub account.
@@ -216,8 +132,14 @@ We'd love your help to make the Unity MCP Server even better! Here's how to cont
git checkout -b feature/your-feature-name
```
+ OR
+
+ ```bash
+ git checkout -b bugfix/your-bugfix-name
+ ```
+
3. **Make Changes**
- Implement your feature or fix, following the project's coding standards (see [HOW_TO_ADD_A_TOOL.md](HOW_TO_ADD_A_TOOL.md) for guidance).
+ Implement your feature or fix.
4. **Commit and Push**
Use clear, descriptive commit messages:
@@ -230,8 +152,6 @@ We'd love your help to make the Unity MCP Server even better! Here's how to cont
5. **Submit a Pull Request**
Open a pull request to the `master` branch. Include a description of your changes and any relevant details.
-For more details, check out [CONTRIBUTING.md](CONTRIBUTING.md) (to be created).
-
## License
This project is licensed under the **MIT License**. Feel free to use, modify, and distribute it as you see fit. See the full license [here](https://github.com/justinpbarnett/unity-mcp/blob/master/LICENSE).
@@ -259,11 +179,9 @@ For additional help, check the [issue tracker](https://github.com/justinpbarnett
Have questions or want to chat about the project? Reach out!
- **X**: [@justinpbarnett](https://x.com/justinpbarnett)
-- **GitHub**: [justinpbarnett](https://github.com/justinpbarnett)
-- **Discord**: Join our community (link coming soon!).
## Acknowledgments
-A huge thanks to everyone who's supported this project's initial release. Special shoutout to Unity Technologies for inspiring tools that push creative boundaries, and to the open-source community for making projects like this possible.
+A huge thanks to everyone who's supported this project's initial release. Special shoutout to Unity Technologies for having an excellent Editor API.
Happy coding, and enjoy integrating LLMs with Unity!
diff --git a/package.json b/package.json
index 8425784..adc4a9b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.justinpbarnett.unity-mcp",
"version": "0.1.4",
- "displayName": "Unity MCP Server",
+ "displayName": "Unity MCP",
"description": "A Unity package to communicate with a local MCP Client via a Python server.",
"unity": "2022.3",
"dependencies": {