diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
new file mode 100644
index 00000000..8364d1ba
--- /dev/null
+++ b/.github/workflows/python-tests.yml
@@ -0,0 +1,45 @@
+name: Python Tests
+
+on:
+ push:
+ branches: ["**"]
+ paths:
+ - MCPForUnity/UnityMcpServer~/src/**
+ - .github/workflows/python-tests.yml
+ workflow_dispatch: {}
+
+jobs:
+ test:
+ name: Run Python Tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v4
+ with:
+ version: "latest"
+
+ - name: Set up Python
+ run: uv python install 3.10
+
+ - name: Install dependencies
+ run: |
+ cd MCPForUnity/UnityMcpServer~/src
+ uv sync
+ uv pip install -e ".[dev]"
+
+ - name: Run tests
+ run: |
+ cd MCPForUnity/UnityMcpServer~/src
+ uv run pytest tests/ -v --tb=short
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: pytest-results
+ path: |
+ MCPForUnity/UnityMcpServer~/src/.pytest_cache/
+ MCPForUnity/UnityMcpServer~/src/tests/
diff --git a/.github/workflows/unity-tests.yml b/.github/workflows/unity-tests.yml
index bfd04055..954fff30 100644
--- a/.github/workflows/unity-tests.yml
+++ b/.github/workflows/unity-tests.yml
@@ -3,7 +3,7 @@ name: Unity Tests
on:
workflow_dispatch: {}
push:
- branches: [main]
+ branches: ["**"]
paths:
- TestProjects/UnityMCPTests/**
- MCPForUnity/Editor/**
diff --git a/TestProjects/UnityMCPTests/Assets/Materials.meta b/MCPForUnity/Editor/Resources/Editor.meta
similarity index 77%
rename from TestProjects/UnityMCPTests/Assets/Materials.meta
rename to MCPForUnity/Editor/Resources/Editor.meta
index 7ad588cf..5c252d17 100644
--- a/TestProjects/UnityMCPTests/Assets/Materials.meta
+++ b/MCPForUnity/Editor/Resources/Editor.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: bacdb2f03a45d448888245e6ac9cca1b
+guid: 266967ec2e1df44209bf46ec6037d61d
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs b/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs
new file mode 100644
index 00000000..0a3fa860
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs
@@ -0,0 +1,64 @@
+using System;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor;
+
+namespace MCPForUnity.Editor.Resources.Editor
+{
+ ///
+ /// Provides information about the currently active editor tool.
+ ///
+ [McpForUnityResource("get_active_tool")]
+ public static class ActiveTool
+ {
+ public static object HandleCommand(JObject @params)
+ {
+ try
+ {
+ Tool currentTool = UnityEditor.Tools.current;
+ string toolName = currentTool.ToString();
+ bool customToolActive = UnityEditor.Tools.current == Tool.Custom;
+ string activeToolName = customToolActive ? EditorTools.GetActiveToolName() : toolName;
+
+ var toolInfo = new
+ {
+ activeTool = activeToolName,
+ isCustom = customToolActive,
+ pivotMode = UnityEditor.Tools.pivotMode.ToString(),
+ pivotRotation = UnityEditor.Tools.pivotRotation.ToString(),
+ handleRotation = new
+ {
+ x = UnityEditor.Tools.handleRotation.eulerAngles.x,
+ y = UnityEditor.Tools.handleRotation.eulerAngles.y,
+ z = UnityEditor.Tools.handleRotation.eulerAngles.z
+ },
+ handlePosition = new
+ {
+ x = UnityEditor.Tools.handlePosition.x,
+ y = UnityEditor.Tools.handlePosition.y,
+ z = UnityEditor.Tools.handlePosition.z
+ }
+ };
+
+ return Response.Success("Retrieved active tool information.", toolInfo);
+ }
+ catch (Exception e)
+ {
+ return Response.Error($"Error getting active tool: {e.Message}");
+ }
+ }
+ }
+
+ // Helper class for custom tool names
+ internal static class EditorTools
+ {
+ public static string GetActiveToolName()
+ {
+ if (UnityEditor.Tools.current == Tool.Custom)
+ {
+ return "Unknown Custom Tool";
+ }
+ return UnityEditor.Tools.current.ToString();
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs.meta b/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs.meta
new file mode 100644
index 00000000..a2f03abd
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6e78b6227ab7742a8a4f679ee6a8a212
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Resources/Editor/EditorState.cs b/MCPForUnity/Editor/Resources/Editor/EditorState.cs
new file mode 100644
index 00000000..fdcff7e6
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/EditorState.cs
@@ -0,0 +1,40 @@
+using System;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+
+namespace MCPForUnity.Editor.Resources.Editor
+{
+ ///
+ /// Provides dynamic editor state information that changes frequently.
+ ///
+ [McpForUnityResource("get_editor_state")]
+ public static class EditorState
+ {
+ public static object HandleCommand(JObject @params)
+ {
+ try
+ {
+ var activeScene = EditorSceneManager.GetActiveScene();
+ var state = new
+ {
+ isPlaying = EditorApplication.isPlaying,
+ isPaused = EditorApplication.isPaused,
+ isCompiling = EditorApplication.isCompiling,
+ isUpdating = EditorApplication.isUpdating,
+ timeSinceStartup = EditorApplication.timeSinceStartup,
+ activeSceneName = activeScene.name ?? "",
+ selectionCount = UnityEditor.Selection.count,
+ activeObjectName = UnityEditor.Selection.activeObject?.name
+ };
+
+ return Response.Success("Retrieved editor state.", state);
+ }
+ catch (Exception e)
+ {
+ return Response.Error($"Error getting editor state: {e.Message}");
+ }
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Resources/Editor/EditorState.cs.meta b/MCPForUnity/Editor/Resources/Editor/EditorState.cs.meta
new file mode 100644
index 00000000..c6c5efa1
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/EditorState.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f7c6df54e014c44fdb0cd3f65a479e37
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs b/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs
new file mode 100644
index 00000000..2f66a01f
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs
@@ -0,0 +1,42 @@
+using System;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor.SceneManagement;
+
+namespace MCPForUnity.Editor.Resources.Editor
+{
+ ///
+ /// Provides information about the current prefab editing context.
+ ///
+ [McpForUnityResource("get_prefab_stage")]
+ public static class PrefabStage
+ {
+ public static object HandleCommand(JObject @params)
+ {
+ try
+ {
+ var stage = PrefabStageUtility.GetCurrentPrefabStage();
+
+ if (stage == null)
+ {
+ return Response.Success("No prefab stage is currently open.", new { isOpen = false });
+ }
+
+ var stageInfo = new
+ {
+ isOpen = true,
+ assetPath = stage.assetPath,
+ prefabRootName = stage.prefabContentsRoot?.name,
+ mode = stage.mode.ToString(),
+ isDirty = stage.scene.isDirty
+ };
+
+ return Response.Success("Prefab stage info retrieved.", stageInfo);
+ }
+ catch (Exception e)
+ {
+ return Response.Error($"Error getting prefab stage info: {e.Message}");
+ }
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs.meta b/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs.meta
new file mode 100644
index 00000000..31bc264c
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/PrefabStage.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7a30b083e68bd4ae3b3d1ce5a45a9414
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Resources/Editor/Selection.cs b/MCPForUnity/Editor/Resources/Editor/Selection.cs
new file mode 100644
index 00000000..07bb34d8
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/Selection.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor;
+
+namespace MCPForUnity.Editor.Resources.Editor
+{
+ ///
+ /// Provides detailed information about the current editor selection.
+ ///
+ [McpForUnityResource("get_selection")]
+ public static class Selection
+ {
+ public static object HandleCommand(JObject @params)
+ {
+ try
+ {
+ var selectionInfo = new
+ {
+ activeObject = UnityEditor.Selection.activeObject?.name,
+ activeGameObject = UnityEditor.Selection.activeGameObject?.name,
+ activeTransform = UnityEditor.Selection.activeTransform?.name,
+ activeInstanceID = UnityEditor.Selection.activeInstanceID,
+ count = UnityEditor.Selection.count,
+ objects = UnityEditor.Selection.objects
+ .Select(obj => new
+ {
+ name = obj?.name,
+ type = obj?.GetType().FullName,
+ instanceID = obj?.GetInstanceID()
+ })
+ .ToList(),
+ gameObjects = UnityEditor.Selection.gameObjects
+ .Select(go => new
+ {
+ name = go?.name,
+ instanceID = go?.GetInstanceID()
+ })
+ .ToList(),
+ assetGUIDs = UnityEditor.Selection.assetGUIDs
+ };
+
+ return Response.Success("Retrieved current selection details.", selectionInfo);
+ }
+ catch (Exception e)
+ {
+ return Response.Error($"Error getting selection: {e.Message}");
+ }
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Resources/Editor/Selection.cs.meta b/MCPForUnity/Editor/Resources/Editor/Selection.cs.meta
new file mode 100644
index 00000000..2066f11a
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/Selection.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c7ea869623e094599a70be086ab4fc0e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/MCPForUnity/Editor/Resources/Editor/Windows.cs b/MCPForUnity/Editor/Resources/Editor/Windows.cs
new file mode 100644
index 00000000..a637c1e2
--- /dev/null
+++ b/MCPForUnity/Editor/Resources/Editor/Windows.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using MCPForUnity.Editor.Helpers;
+using Newtonsoft.Json.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace MCPForUnity.Editor.Resources.Editor
+{
+ ///
+ /// Provides list of all open editor windows.
+ ///
+ [McpForUnityResource("get_windows")]
+ public static class Windows
+ {
+ public static object HandleCommand(JObject @params)
+ {
+ try
+ {
+ EditorWindow[] allWindows = UnityEngine.Resources.FindObjectsOfTypeAll();
+ var openWindows = new List