chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 266967ec2e1df44209bf46ec6037d61d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor.SceneManagement;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns information about the currently open Prefab Stage (Isolation or
|
||||
/// In-Context mode), or { isOpen = false } when no prefab is being edited.
|
||||
///
|
||||
/// Wires up the existing <c>mcpforunity://editor/prefab-stage</c> resource
|
||||
/// (Server/src/services/resources/prefab_stage.py) which dispatches the
|
||||
/// <c>get_prefab_stage</c> command name expected by this handler.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_prefab_stage")]
|
||||
public static class GetPrefabStage
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
if (stage == null)
|
||||
return new SuccessResponse("No prefab stage open.", new { isOpen = false });
|
||||
|
||||
var root = stage.prefabContentsRoot;
|
||||
return new SuccessResponse("Retrieved prefab stage info.", new
|
||||
{
|
||||
isOpen = true,
|
||||
assetPath = stage.assetPath,
|
||||
prefabRootName = root != null ? root.name : null,
|
||||
mode = stage.mode.ToString(),
|
||||
isDirty = stage.scene.isDirty,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting prefab stage: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bbcba512cf6473184c038e6e3926136
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using MCPForUnity.Runtime.Helpers;
|
||||
|
||||
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.activeObject?.GetInstanceIDCompat() ?? 0,
|
||||
count = UnityEditor.Selection.count,
|
||||
objects = UnityEditor.Selection.objects
|
||||
.Select(obj => new
|
||||
{
|
||||
name = obj?.name,
|
||||
type = obj?.GetType().FullName,
|
||||
instanceID = obj?.GetInstanceIDCompat()
|
||||
})
|
||||
.ToList(),
|
||||
gameObjects = UnityEditor.Selection.gameObjects
|
||||
.Select(go => new
|
||||
{
|
||||
name = go?.name,
|
||||
instanceID = go?.GetInstanceIDCompat()
|
||||
})
|
||||
.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:
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the enabled/disabled state of all discovered tools, grouped by group name.
|
||||
/// Used by the Python server (especially in stdio mode) to sync tool visibility.
|
||||
/// </summary>
|
||||
[McpForUnityResource("get_tool_states")]
|
||||
public static class ToolStates
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discovery = MCPServiceLocator.ToolDiscovery;
|
||||
var allTools = discovery.DiscoverAllTools();
|
||||
|
||||
var toolsArray = new JArray();
|
||||
foreach (var tool in allTools)
|
||||
{
|
||||
var paramsArray = new JArray();
|
||||
if (tool.Parameters != null)
|
||||
{
|
||||
foreach (var p in tool.Parameters)
|
||||
{
|
||||
paramsArray.Add(new JObject
|
||||
{
|
||||
["name"] = p.Name,
|
||||
["description"] = p.Description,
|
||||
["type"] = p.Type,
|
||||
["required"] = p.Required,
|
||||
["default_value"] = p.DefaultValue
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toolsArray.Add(new JObject
|
||||
{
|
||||
["name"] = tool.Name,
|
||||
["group"] = tool.Group ?? "core",
|
||||
["enabled"] = discovery.IsToolEnabled(tool.Name),
|
||||
["description"] = tool.Description,
|
||||
["auto_register"] = tool.AutoRegister,
|
||||
["is_built_in"] = tool.IsBuiltIn,
|
||||
["structured_output"] = tool.StructuredOutput,
|
||||
["requires_polling"] = tool.RequiresPolling,
|
||||
["poll_action"] = tool.PollAction ?? "status",
|
||||
["max_poll_seconds"] = tool.MaxPollSeconds,
|
||||
["parameters"] = paramsArray
|
||||
});
|
||||
}
|
||||
|
||||
var groups = allTools
|
||||
.GroupBy(t => t.Group ?? "core")
|
||||
.Select(g => new JObject
|
||||
{
|
||||
["name"] = g.Key,
|
||||
["enabled_count"] = g.Count(t => discovery.IsToolEnabled(t.Name)),
|
||||
["total_count"] = g.Count()
|
||||
});
|
||||
|
||||
var result = new JObject
|
||||
{
|
||||
["tools"] = toolsArray,
|
||||
["groups"] = new JArray(groups)
|
||||
};
|
||||
|
||||
return new SuccessResponse("Retrieved tool states.", result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Failed to retrieve tool states: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f77d36b37ba4526ad30b3c84e3e752c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Runtime.Helpers;
|
||||
|
||||
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.GetInstanceIDCompat()
|
||||
});
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 538489f13d7914c4eba9a67e29001b43
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 959ee428299454ac19a636275208ca00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
|
||||
|
||||
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,
|
||||
renderPipeline = RenderPipelineUtility.GetActivePipeline().ToString(),
|
||||
activeInputHandler = GetActiveInputHandler(),
|
||||
packages = new
|
||||
{
|
||||
ugui = IsPackageInstalled("com.unity.ugui"),
|
||||
textmeshpro = IsPackageInstalled("com.unity.textmeshpro"),
|
||||
inputsystem = IsPackageInstalled("com.unity.inputsystem"),
|
||||
uiToolkit = true,
|
||||
screenCapture = true,
|
||||
}
|
||||
};
|
||||
|
||||
return new SuccessResponse("Retrieved project info.", info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error getting project info: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads PlayerSettings.activeInputHandler via reflection to avoid
|
||||
/// compile-time dependency on the Input System package.
|
||||
/// Returns "Old" (0), "New" (1), or "Both" (2).
|
||||
/// </summary>
|
||||
private static string GetActiveInputHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
var prop = typeof(PlayerSettings).GetProperty(
|
||||
"activeInputHandler",
|
||||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (prop == null)
|
||||
return "Old";
|
||||
|
||||
int value = (int)prop.GetValue(null);
|
||||
return value switch
|
||||
{
|
||||
0 => "Old",
|
||||
1 => "New",
|
||||
2 => "Both",
|
||||
_ => "Old"
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Old";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPackageInstalled(string packageName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return PackageInfo.FindForAssetPath("Packages/" + packageName) != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81b03415fcf93466e9ed667d19b58d43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2179ac5d98f264d1681e7d5c0d0ed341
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 563f6050485b445449a1db200bfba51c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools.Cameras;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Scene
|
||||
{
|
||||
[McpForUnityResource("get_cameras")]
|
||||
public static class CamerasResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
return CameraControl.ListCameras(@params ?? new JObject());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[CamerasResource] Error listing cameras: {e}");
|
||||
return new ErrorResponse($"Error listing cameras: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68c487cd2b284b09bcdce22f76127e95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,285 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using MCPForUnity.Runtime.Helpers;
|
||||
|
||||
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 = GameObjectLookup.ResolveInstanceID(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.GetInstanceIDCompat());
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
instanceID = go.GetInstanceIDCompat(),
|
||||
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.GetInstanceIDCompat() : (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 = GameObjectLookup.ResolveInstanceID(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.GetInstanceIDCompat()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 = GameObjectLookup.ResolveInstanceID(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:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools.Graphics;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Scene
|
||||
{
|
||||
[McpForUnityResource("get_renderer_features")]
|
||||
public static class RendererFeaturesResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
return RendererFeatureOps.ListFeatures(@params ?? new JObject());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[RendererFeaturesResource] Error: {e}");
|
||||
return new ErrorResponse($"Error listing renderer features: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91dffec0c5224fca9ea78f7d92bfc569
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools.Graphics;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Scene
|
||||
{
|
||||
[McpForUnityResource("get_rendering_stats")]
|
||||
public static class RenderingStatsResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
return RenderingStatsOps.GetStats(@params ?? new JObject());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[RenderingStatsResource] Error: {e}");
|
||||
return new ErrorResponse($"Error getting rendering stats: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6c0a7ee8d9443a9aec534f04dbee225
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools.Graphics;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnity.Editor.Resources.Scene
|
||||
{
|
||||
[McpForUnityResource("get_volumes")]
|
||||
public static class VolumesResource
|
||||
{
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
try
|
||||
{
|
||||
return VolumeOps.ListVolumes(@params ?? new JObject());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[VolumesResource] Error listing volumes: {e}");
|
||||
return new ErrorResponse($"Error listing volumes: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83cc61dc0e644cf2abd24ad611aa315c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 412726d2e774048939b0d2bd4f11a503
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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