升级XR插件版本
This commit is contained in:
8
Packages/MCPForUnity/Editor/Resources/Editor.meta
Normal file
8
Packages/MCPForUnity/Editor/Resources/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 266967ec2e1df44209bf46ec6037d61d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
64
Packages/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs
Normal file
64
Packages/MCPForUnity/Editor/Resources/Editor/ActiveTool.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about the currently active editor tool.
|
||||
/// </summary>
|
||||
[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 new SuccessResponse("Retrieved active tool information.", toolInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e78b6227ab7742a8a4f679ee6a8a212
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Packages/MCPForUnity/Editor/Resources/Editor/EditorState.cs
Normal file
27
Packages/MCPForUnity/Editor/Resources/Editor/EditorState.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides dynamic editor state information that changes frequently.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_editor_state")]
|
||||
public static class EditorState
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshot = EditorStateCache.GetSnapshot();
|
||||
return new SuccessResponse("Retrieved editor state.", snapshot);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting editor state: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7c6df54e014c44fdb0cd3f65a479e37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
52
Packages/MCPForUnity/Editor/Resources/Editor/Selection.cs
Normal file
52
Packages/MCPForUnity/Editor/Resources/Editor/Selection.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides detailed information about the current editor selection.
|
||||
/// </summary>
|
||||
[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 new SuccessResponse("Retrieved current selection details.", selectionInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting selection: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7ea869623e094599a70be086ab4fc0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
Packages/MCPForUnity/Editor/Resources/Editor/Windows.cs
Normal file
59
Packages/MCPForUnity/Editor/Resources/Editor/Windows.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides list of all open editor windows.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_windows")]
|
||||
public static class Windows
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
EditorWindow[] allWindows = UnityEngine.Resources.FindObjectsOfTypeAll<EditorWindow>();
|
||||
var openWindows = new List<object>();
|
||||
|
||||
foreach (EditorWindow window in allWindows)
|
||||
{
|
||||
if (window == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
openWindows.Add(new
|
||||
{
|
||||
title = window.titleContent.text,
|
||||
typeName = window.GetType().FullName,
|
||||
isFocused = EditorWindow.focusedWindow == window,
|
||||
position = new
|
||||
{
|
||||
x = window.position.x,
|
||||
y = window.position.y,
|
||||
width = window.position.width,
|
||||
height = window.position.height
|
||||
},
|
||||
instanceID = window.GetInstanceID()
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Warn($"Could not get info for window {window.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return new SuccessResponse("Retrieved list of open editor windows.", openWindows);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting editor windows: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/MCPForUnity/Editor/Resources/Editor/Windows.cs.meta
Normal file
11
Packages/MCPForUnity/Editor/Resources/Editor/Windows.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58a341e64bea440b29deaf859aaea552
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a class as an MCP resource handler for auto-discovery.
|
||||
/// The class must have a public static HandleCommand(JObject) method.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class McpForUnityResourceAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The resource name used to route requests to this resource.
|
||||
/// If not specified, defaults to the PascalCase class name converted to snake_case.
|
||||
/// </summary>
|
||||
public string ResourceName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable description of what this resource provides.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an MCP resource attribute with auto-generated resource name.
|
||||
/// The resource name will be derived from the class name (PascalCase → snake_case).
|
||||
/// Example: ManageAsset → manage_asset
|
||||
/// </summary>
|
||||
public McpForUnityResourceAttribute()
|
||||
{
|
||||
ResourceName = null; // Will be auto-generated
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an MCP resource attribute with explicit resource name.
|
||||
/// </summary>
|
||||
/// <param name="resourceName">The resource name (e.g., "manage_asset")</param>
|
||||
public McpForUnityResourceAttribute(string resourceName)
|
||||
{
|
||||
ResourceName = resourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c2d60f570f3d4bd2a6a2c1293094be3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/MCPForUnity/Editor/Resources/MenuItems.meta
Normal file
8
Packages/MCPForUnity/Editor/Resources/MenuItems.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bca79cd3ef8ed466f9e50e2dc7850e46
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.MenuItems
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simple read-only resource that returns Unity menu items.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_menu_items")]
|
||||
public static class GetMenuItems
|
||||
{
|
||||
private static List<string> _cached;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void BuildCache() => Refresh();
|
||||
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
bool forceRefresh = @params?["refresh"]?.ToObject<bool>() ?? false;
|
||||
string search = @params?["search"]?.ToString();
|
||||
|
||||
var items = GetMenuItemsInternal(forceRefresh);
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
items = items
|
||||
.Where(item => item.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
string message = $"Retrieved {items.Count} menu items";
|
||||
return new SuccessResponse(message, items);
|
||||
}
|
||||
|
||||
internal static List<string> GetMenuItemsInternal(bool forceRefresh)
|
||||
{
|
||||
if (forceRefresh || _cached == null)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
return (_cached ?? new List<string>()).ToList();
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
try
|
||||
{
|
||||
var methods = TypeCache.GetMethodsWithAttribute<MenuItem>();
|
||||
_cached = methods
|
||||
.SelectMany(m => m
|
||||
.GetCustomAttributes(typeof(MenuItem), false)
|
||||
.OfType<MenuItem>()
|
||||
.Select(attr => attr.menuItem))
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(s => s, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Error($"[GetMenuItems] Failed to scan menu items: {ex}");
|
||||
_cached ??= new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04eeea61eb5c24033a88013845d25f23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/MCPForUnity/Editor/Resources/Project.meta
Normal file
8
Packages/MCPForUnity/Editor/Resources/Project.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 538489f13d7914c4eba9a67e29001b43
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Packages/MCPForUnity/Editor/Resources/Project/Layers.cs
Normal file
39
Packages/MCPForUnity/Editor/Resources/Project/Layers.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Project
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides dictionary of layer indices to layer names.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_layers")]
|
||||
public static class Layers
|
||||
{
|
||||
private const int TotalLayerCount = 32;
|
||||
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
var layers = new Dictionary<int, string>();
|
||||
for (int i = 0; i < TotalLayerCount; i++)
|
||||
{
|
||||
string layerName = LayerMask.LayerToName(i);
|
||||
if (!string.IsNullOrEmpty(layerName))
|
||||
{
|
||||
layers.Add(i, layerName);
|
||||
}
|
||||
}
|
||||
|
||||
return new SuccessResponse("Retrieved current named layers.", layers);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Failed to retrieve layers: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/MCPForUnity/Editor/Resources/Project/Layers.cs.meta
Normal file
11
Packages/MCPForUnity/Editor/Resources/Project/Layers.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 959ee428299454ac19a636275208ca00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Packages/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs
Normal file
41
Packages/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Project
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static project configuration information.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_project_info")]
|
||||
public static class ProjectInfo
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
string assetsPath = Application.dataPath.Replace('\\', '/');
|
||||
string projectRoot = Directory.GetParent(assetsPath)?.FullName.Replace('\\', '/');
|
||||
string projectName = Path.GetFileName(projectRoot);
|
||||
|
||||
var info = new
|
||||
{
|
||||
projectRoot = projectRoot ?? "",
|
||||
projectName = projectName ?? "",
|
||||
unityVersion = Application.unityVersion,
|
||||
platform = EditorUserBuildSettings.activeBuildTarget.ToString(),
|
||||
assetsPath = assetsPath
|
||||
};
|
||||
|
||||
return new SuccessResponse("Retrieved project info.", info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting project info: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81b03415fcf93466e9ed667d19b58d43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Packages/MCPForUnity/Editor/Resources/Project/Tags.cs
Normal file
27
Packages/MCPForUnity/Editor/Resources/Project/Tags.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Project
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides list of all tags in the project.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_tags")]
|
||||
public static class Tags
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] tags = InternalEditorUtility.tags;
|
||||
return new SuccessResponse("Retrieved current tags.", tags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Failed to retrieve tags: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/MCPForUnity/Editor/Resources/Project/Tags.cs.meta
Normal file
11
Packages/MCPForUnity/Editor/Resources/Project/Tags.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2179ac5d98f264d1681e7d5c0d0ed341
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/MCPForUnity/Editor/Resources/Scene.meta
Normal file
8
Packages/MCPForUnity/Editor/Resources/Scene.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 563f6050485b445449a1db200bfba51c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Scene
|
||||
{
|
||||
/// <summary>
|
||||
/// Resource handler for reading GameObject data.
|
||||
/// Provides read-only access to GameObject information without component serialization.
|
||||
///
|
||||
/// URI: unity://scene/gameobject/{instanceID}
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_gameobject")]
|
||||
public static class GameObjectResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
{
|
||||
return new ErrorResponse("Parameters cannot be null.");
|
||||
}
|
||||
|
||||
// Get instance ID from params
|
||||
int? instanceID = null;
|
||||
|
||||
var idToken = @params["instanceID"] ?? @params["instance_id"] ?? @params["id"];
|
||||
if (idToken != null)
|
||||
{
|
||||
instanceID = ParamCoercion.CoerceInt(idToken, -1);
|
||||
if (instanceID == -1)
|
||||
{
|
||||
instanceID = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceID.HasValue)
|
||||
{
|
||||
return new ErrorResponse("'instanceID' parameter is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var go = EditorUtility.InstanceIDToObject(instanceID.Value) as GameObject;
|
||||
if (go == null)
|
||||
{
|
||||
return new ErrorResponse($"GameObject with instance ID {instanceID} not found.");
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = SerializeGameObject(go)
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[GameObjectResource] Error getting GameObject: {e}");
|
||||
return new ErrorResponse($"Error getting GameObject: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a GameObject without component details.
|
||||
/// For component data, use GetComponents or GetComponent resources.
|
||||
/// </summary>
|
||||
public static object SerializeGameObject(GameObject go)
|
||||
{
|
||||
if (go == null)
|
||||
return null;
|
||||
|
||||
var transform = go.transform;
|
||||
|
||||
// Get component type names (not full serialization)
|
||||
var componentTypes = go.GetComponents<Component>()
|
||||
.Where(c => c != null)
|
||||
.Select(c => c.GetType().Name)
|
||||
.ToList();
|
||||
|
||||
// Get children instance IDs (not full serialization)
|
||||
var childrenIds = new List<int>();
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
childrenIds.Add(child.gameObject.GetInstanceID());
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
instanceID = go.GetInstanceID(),
|
||||
name = go.name,
|
||||
tag = go.tag,
|
||||
layer = go.layer,
|
||||
layerName = LayerMask.LayerToName(go.layer),
|
||||
active = go.activeSelf,
|
||||
activeInHierarchy = go.activeInHierarchy,
|
||||
isStatic = go.isStatic,
|
||||
transform = new
|
||||
{
|
||||
position = SerializeVector3(transform.position),
|
||||
localPosition = SerializeVector3(transform.localPosition),
|
||||
rotation = SerializeVector3(transform.eulerAngles),
|
||||
localRotation = SerializeVector3(transform.localEulerAngles),
|
||||
scale = SerializeVector3(transform.localScale),
|
||||
lossyScale = SerializeVector3(transform.lossyScale)
|
||||
},
|
||||
parent = transform.parent != null ? transform.parent.gameObject.GetInstanceID() : (int?)null,
|
||||
children = childrenIds,
|
||||
componentTypes = componentTypes,
|
||||
path = GameObjectLookup.GetGameObjectPath(go)
|
||||
};
|
||||
}
|
||||
|
||||
private static object SerializeVector3(Vector3 v)
|
||||
{
|
||||
return new { x = v.x, y = v.y, z = v.z };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resource handler for reading all components on a GameObject.
|
||||
///
|
||||
/// URI: unity://scene/gameobject/{instanceID}/components
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_gameobject_components")]
|
||||
public static class GameObjectComponentsResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
{
|
||||
return new ErrorResponse("Parameters cannot be null.");
|
||||
}
|
||||
|
||||
var idToken = @params["instanceID"] ?? @params["instance_id"] ?? @params["id"];
|
||||
int instanceID = ParamCoercion.CoerceInt(idToken, -1);
|
||||
if (instanceID == -1)
|
||||
{
|
||||
return new ErrorResponse("'instanceID' parameter is required.");
|
||||
}
|
||||
|
||||
// Pagination parameters
|
||||
int pageSize = ParamCoercion.CoerceInt(@params["pageSize"] ?? @params["page_size"], 25);
|
||||
int cursor = ParamCoercion.CoerceInt(@params["cursor"], 0);
|
||||
bool includeProperties = ParamCoercion.CoerceBool(@params["includeProperties"] ?? @params["include_properties"], true);
|
||||
|
||||
pageSize = Mathf.Clamp(pageSize, 1, 100);
|
||||
|
||||
try
|
||||
{
|
||||
var go = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
|
||||
if (go == null)
|
||||
{
|
||||
return new ErrorResponse($"GameObject with instance ID {instanceID} not found.");
|
||||
}
|
||||
|
||||
var allComponents = go.GetComponents<Component>().Where(c => c != null).ToList();
|
||||
int total = allComponents.Count;
|
||||
|
||||
var pagedComponents = allComponents.Skip(cursor).Take(pageSize).ToList();
|
||||
|
||||
var componentData = new List<object>();
|
||||
foreach (var component in pagedComponents)
|
||||
{
|
||||
if (includeProperties)
|
||||
{
|
||||
componentData.Add(GameObjectSerializer.GetComponentData(component));
|
||||
}
|
||||
else
|
||||
{
|
||||
componentData.Add(new
|
||||
{
|
||||
typeName = component.GetType().FullName,
|
||||
instanceID = component.GetInstanceID()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int? nextCursor = cursor + pagedComponents.Count < total ? cursor + pagedComponents.Count : (int?)null;
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
gameObjectID = instanceID,
|
||||
gameObjectName = go.name,
|
||||
components = componentData,
|
||||
cursor = cursor,
|
||||
pageSize = pageSize,
|
||||
nextCursor = nextCursor,
|
||||
totalCount = total,
|
||||
hasMore = nextCursor.HasValue,
|
||||
includeProperties = includeProperties
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[GameObjectComponentsResource] Error getting components: {e}");
|
||||
return new ErrorResponse($"Error getting components: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resource handler for reading a single component on a GameObject.
|
||||
///
|
||||
/// URI: unity://scene/gameobject/{instanceID}/component/{componentName}
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_gameobject_component")]
|
||||
public static class GameObjectComponentResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
{
|
||||
return new ErrorResponse("Parameters cannot be null.");
|
||||
}
|
||||
|
||||
var idToken = @params["instanceID"] ?? @params["instance_id"] ?? @params["id"];
|
||||
int instanceID = ParamCoercion.CoerceInt(idToken, -1);
|
||||
if (instanceID == -1)
|
||||
{
|
||||
return new ErrorResponse("'instanceID' parameter is required.");
|
||||
}
|
||||
|
||||
string componentName = ParamCoercion.CoerceString(@params["componentName"] ?? @params["component_name"] ?? @params["component"], null);
|
||||
if (string.IsNullOrEmpty(componentName))
|
||||
{
|
||||
return new ErrorResponse("'componentName' parameter is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var go = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
|
||||
if (go == null)
|
||||
{
|
||||
return new ErrorResponse($"GameObject with instance ID {instanceID} not found.");
|
||||
}
|
||||
|
||||
// Find the component by type name
|
||||
Component targetComponent = null;
|
||||
foreach (var component in go.GetComponents<Component>())
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
var typeName = component.GetType().Name;
|
||||
var fullTypeName = component.GetType().FullName;
|
||||
|
||||
if (string.Equals(typeName, componentName, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(fullTypeName, componentName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
targetComponent = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetComponent == null)
|
||||
{
|
||||
return new ErrorResponse($"Component '{componentName}' not found on GameObject '{go.name}'.");
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
gameObjectID = instanceID,
|
||||
gameObjectName = go.name,
|
||||
component = GameObjectSerializer.GetComponentData(targetComponent)
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[GameObjectComponentResource] Error getting component: {e}");
|
||||
return new ErrorResponse($"Error getting component: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ee79050d9f6d42798a0757cc7672517
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/MCPForUnity/Editor/Resources/Tests.meta
Normal file
8
Packages/MCPForUnity/Editor/Resources/Tests.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 412726d2e774048939b0d2bd4f11a503
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
217
Packages/MCPForUnity/Editor/Resources/Tests/GetTests.cs
Normal file
217
Packages/MCPForUnity/Editor/Resources/Tests/GetTests.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to Unity tests from the Test Framework with pagination and filtering support.
|
||||
/// This is a read-only resource that can be queried by MCP clients.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - mode (optional): Filter by "EditMode" or "PlayMode"
|
||||
/// - filter (optional): Filter test names by pattern (case-insensitive contains)
|
||||
/// - page_size (optional): Number of tests per page (default: 50, max: 200)
|
||||
/// - cursor (optional): 0-based cursor for pagination
|
||||
/// - page_number (optional): 1-based page number (converted to cursor)
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_tests")]
|
||||
public static class GetTests
|
||||
{
|
||||
private const int DEFAULT_PAGE_SIZE = 50;
|
||||
private const int MAX_PAGE_SIZE = 200;
|
||||
|
||||
public static async Task<object> HandleCommand(JObject @params)
|
||||
{
|
||||
// Parse mode filter
|
||||
TestMode? modeFilter = null;
|
||||
string modeStr = @params?["mode"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(modeStr))
|
||||
{
|
||||
if (!ModeParser.TryParse(modeStr, out modeFilter, out var parseError))
|
||||
{
|
||||
return new ErrorResponse(parseError);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse name filter
|
||||
string nameFilter = @params?["filter"]?.ToString();
|
||||
|
||||
McpLog.Info($"[GetTests] Retrieving tests (mode={modeFilter?.ToString() ?? "all"}, filter={nameFilter ?? "none"})");
|
||||
|
||||
IReadOnlyList<Dictionary<string, string>> allTests;
|
||||
try
|
||||
{
|
||||
allTests = await MCPServiceLocator.Tests.GetTestsAsync(modeFilter).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Error($"[GetTests] Error retrieving tests: {ex.Message}\n{ex.StackTrace}");
|
||||
return new ErrorResponse("Failed to retrieve tests");
|
||||
}
|
||||
|
||||
// Apply name filter if provided and convert to List for pagination
|
||||
List<Dictionary<string, string>> filteredTests;
|
||||
if (!string.IsNullOrEmpty(nameFilter))
|
||||
{
|
||||
filteredTests = allTests
|
||||
.Where(t =>
|
||||
(t.ContainsKey("name") && t["name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0) ||
|
||||
(t.ContainsKey("full_name") && t["full_name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredTests = allTests.ToList();
|
||||
}
|
||||
|
||||
// Clamp page_size before parsing pagination to ensure cursor is computed correctly
|
||||
int requestedPageSize = ParamCoercion.CoerceInt(
|
||||
@params?["page_size"] ?? @params?["pageSize"],
|
||||
DEFAULT_PAGE_SIZE
|
||||
);
|
||||
int clampedPageSize = System.Math.Min(requestedPageSize, MAX_PAGE_SIZE);
|
||||
if (clampedPageSize <= 0) clampedPageSize = DEFAULT_PAGE_SIZE;
|
||||
|
||||
// Create modified params with clamped page_size for cursor calculation
|
||||
var paginationParams = new JObject(@params);
|
||||
paginationParams["page_size"] = clampedPageSize;
|
||||
|
||||
// Parse pagination with clamped page size
|
||||
var pagination = PaginationRequest.FromParams(paginationParams, DEFAULT_PAGE_SIZE);
|
||||
|
||||
// Create paginated response
|
||||
var response = PaginationResponse<Dictionary<string, string>>.Create(filteredTests, pagination);
|
||||
|
||||
string message = !string.IsNullOrEmpty(nameFilter)
|
||||
? $"Retrieved {response.Items.Count} of {response.TotalCount} tests matching '{nameFilter}' (cursor {response.Cursor})"
|
||||
: $"Retrieved {response.Items.Count} of {response.TotalCount} tests (cursor {response.Cursor})";
|
||||
|
||||
return new SuccessResponse(message, response);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DEPRECATED: Use get_tests with mode parameter instead.
|
||||
/// Provides access to Unity tests for a specific mode (EditMode or PlayMode).
|
||||
/// This is a read-only resource that can be queried by MCP clients.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - mode (required): "EditMode" or "PlayMode"
|
||||
/// - filter (optional): Filter test names by pattern (case-insensitive contains)
|
||||
/// - page_size (optional): Number of tests per page (default: 50, max: 200)
|
||||
/// - cursor (optional): 0-based cursor for pagination
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_tests_for_mode")]
|
||||
public static class GetTestsForMode
|
||||
{
|
||||
private const int DEFAULT_PAGE_SIZE = 50;
|
||||
private const int MAX_PAGE_SIZE = 200;
|
||||
|
||||
public static async Task<object> HandleCommand(JObject @params)
|
||||
{
|
||||
string modeStr = @params?["mode"]?.ToString();
|
||||
if (string.IsNullOrEmpty(modeStr))
|
||||
{
|
||||
return new ErrorResponse("'mode' parameter is required");
|
||||
}
|
||||
|
||||
if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
|
||||
{
|
||||
return new ErrorResponse(parseError);
|
||||
}
|
||||
|
||||
// Parse name filter
|
||||
string nameFilter = @params?["filter"]?.ToString();
|
||||
|
||||
McpLog.Info($"[GetTestsForMode] Retrieving tests for mode: {parsedMode.Value} (filter={nameFilter ?? "none"})");
|
||||
|
||||
IReadOnlyList<Dictionary<string, string>> allTests;
|
||||
try
|
||||
{
|
||||
allTests = await MCPServiceLocator.Tests.GetTestsAsync(parsedMode).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Error($"[GetTestsForMode] Error retrieving tests: {ex.Message}\n{ex.StackTrace}");
|
||||
return new ErrorResponse("Failed to retrieve tests");
|
||||
}
|
||||
|
||||
// Apply name filter if provided and convert to List for pagination
|
||||
List<Dictionary<string, string>> filteredTests;
|
||||
if (!string.IsNullOrEmpty(nameFilter))
|
||||
{
|
||||
filteredTests = allTests
|
||||
.Where(t =>
|
||||
(t.ContainsKey("name") && t["name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0) ||
|
||||
(t.ContainsKey("full_name") && t["full_name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredTests = allTests.ToList();
|
||||
}
|
||||
|
||||
// Clamp page_size before parsing pagination to ensure cursor is computed correctly
|
||||
int requestedPageSize = ParamCoercion.CoerceInt(
|
||||
@params?["page_size"] ?? @params?["pageSize"],
|
||||
DEFAULT_PAGE_SIZE
|
||||
);
|
||||
int clampedPageSize = System.Math.Min(requestedPageSize, MAX_PAGE_SIZE);
|
||||
if (clampedPageSize <= 0) clampedPageSize = DEFAULT_PAGE_SIZE;
|
||||
|
||||
// Create modified params with clamped page_size for cursor calculation
|
||||
var paginationParams = new JObject(@params);
|
||||
paginationParams["page_size"] = clampedPageSize;
|
||||
|
||||
// Parse pagination with clamped page size
|
||||
var pagination = PaginationRequest.FromParams(paginationParams, DEFAULT_PAGE_SIZE);
|
||||
|
||||
// Create paginated response
|
||||
var response = PaginationResponse<Dictionary<string, string>>.Create(filteredTests, pagination);
|
||||
|
||||
string message = nameFilter != null
|
||||
? $"Retrieved {response.Items.Count} of {response.TotalCount} {parsedMode.Value} tests matching '{nameFilter}'"
|
||||
: $"Retrieved {response.Items.Count} of {response.TotalCount} {parsedMode.Value} tests";
|
||||
|
||||
return new SuccessResponse(message, response);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ModeParser
|
||||
{
|
||||
internal static bool TryParse(string modeStr, out TestMode? mode, out string error)
|
||||
{
|
||||
error = null;
|
||||
mode = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(modeStr))
|
||||
{
|
||||
error = "'mode' parameter cannot be empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modeStr.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mode = TestMode.EditMode;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (modeStr.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mode = TestMode.PlayMode;
|
||||
return true;
|
||||
}
|
||||
|
||||
error = $"Unknown test mode: '{modeStr}'. Use 'EditMode' or 'PlayMode'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/MCPForUnity/Editor/Resources/Tests/GetTests.cs.meta
Normal file
11
Packages/MCPForUnity/Editor/Resources/Tests/GetTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84183aaed077e4f25968269c952db2d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user