🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
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)
|
|
|
|
|
{
|
2026-01-07 13:33:20 +08:00
|
|
|
McpLog.Error($"[GameObjectResource] Error getting GameObject: {e}");
|
🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
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)
|
|
|
|
|
{
|
2026-01-07 13:33:20 +08:00
|
|
|
McpLog.Error($"[GameObjectComponentsResource] Error getting components: {e}");
|
🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
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)
|
|
|
|
|
{
|
2026-01-07 13:33:20 +08:00
|
|
|
McpLog.Error($"[GameObjectComponentResource] Error getting component: {e}");
|
🎮 GameObject Toolset Redesign and Streamlining (#518)
* feat: Redesign GameObject API for better LLM ergonomics
## New Tools
- find_gameobjects: Search GameObjects, returns paginated instance IDs only
- manage_components: Component lifecycle (add, remove, set_property)
## New Resources
- unity://scene/gameobject/{id}: Single GameObject data (no component serialization)
- unity://scene/gameobject/{id}/components: All components (paginated)
- unity://scene/gameobject/{id}/component/{name}: Single component by type
## Updated
- manage_scene get_hierarchy: Now includes componentTypes array
- manage_gameobject: Slimmed to lifecycle only (create, modify, delete)
- Legacy actions (find, get_components, etc.) log deprecation warnings
## Extracted Utilities
- ParamCoercion: Centralized int/bool/float/string coercion
- VectorParsing: Vector3/Vector2/Quaternion/Color parsing
- GameObjectLookup: Centralized GameObject search logic
## Test Coverage
- 76 new Unity EditMode tests for ManageGameObject actions
- 21 new pytest tests for Python tools/resources
- New NL/T CI suite for GameObject API (GO-0 to GO-5)
Addresses LLM confusion with parameter overload by splitting into
focused tools and read-only resources.
* feat: Add static gameobject_api helper resource for UI discoverability
Adds unity://scene/gameobject-api resource that:
- Shows in Cursor's resource list UI (no parameters needed)
- Documents the parameterized gameobject resources
- Explains the workflow: find_gameobjects → read resource
- Lists examples and related tools
* feat: Add GO tests to main NL/T CI workflow
- Adds GO pass (GO-0 to GO-5) after T pass in claude-nl-suite.yml
- Includes retry logic for incomplete GO tests
- Updates all regex patterns to recognize GO-* test IDs
- Updates DESIRED lists to include all 21 tests (NL-0..4, T-A..J, GO-0..5)
- Updates default_titles for GO tests in markdown summary
- Keeps separate claude-gameobject-suite.yml for standalone runs
* feat: Add GameObject API stress tests and NL/T suite updates
Stress Tests (12 new tests):
- BulkCreate small/medium batches
- FindGameObjects pagination with by_component search
- AddComponents to single object
- GetComponents with full serialization
- SetComponentProperties (complex Rigidbody)
- Deep hierarchy creation and path lookup
- GetHierarchy with large scenes
- Resource read performance tests
- RapidFire create-modify-delete cycles
NL/T Suite Updates:
- Added GO-0..GO-10 tests in nl-gameobject-suite.md
- Fixed tool naming: mcp__unity__ → mcp__UnityMCP__
Other:
- Fixed LongUnityScriptClaudeTest.cs compilation errors
- Added reports/, .claude/local/, scripts/local-test/ to .gitignore
All 254 EditMode tests pass (250 run, 4 explicit skips)
* fix: Address code review feedback
- ParamCoercion: Use CultureInfo.InvariantCulture for float parsing
- ManageComponents: Move Transform removal check before GetComponent
- ManageGameObjectFindTests: Use try-finally for LogAssert.ignoreFailingMessages
- VectorParsing: Document that quaternions are not auto-normalized
- gameobject.py: Prefix unused ctx parameter with underscore
* fix: Address additional code review feedback
- ManageComponents: Reuse GameObjectLookup.FindComponentType instead of duplicate
- ManageComponents: Log warnings when SetPropertiesOnComponent fails
- GameObjectLookup: Make FindComponentType public for reuse
- gameobject.py: Extract _normalize_response helper to reduce duplication
- gameobject.py: Add TODO comment for unused typed response classes
* fix: Address more code review feedback
NL/T Prompt Fixes:
- nl-gameobject-suite.md: Remove non-existent list_resources/read_resource from AllowedTools
- nl-gameobject-suite.md: Fix parameter names (component_type, properties)
- nl-unity-suite-nl.md: Remove unused manage_editor from AllowedTools
Test Fixes:
- GameObjectAPIStressTests: Add null check to ToJObject helper
- GameObjectAPIStressTests: Clarify AudioSource usage comment
- ManageGameObjectFindTests: Use built-in 'UI' layer instead of 'Water'
- LongUnityScriptClaudeTest: Clean up NL/T test artifacts (Counte42 typo, HasTarget)
* docs: Add documentation for API limitations and behaviors
- GameObjectLookup.SearchByPath: Document and warn that includeInactive
has no effect (Unity API limitation)
- ManageComponents.TrySetProperty: Document case-insensitive lookup behavior
* More test fixes and tighten parameters on python tools
* fix: Align test expectation with implementation error message case
* docs: update README tools and resources lists
- Add missing tools: manage_components, batch_execute, find_gameobjects, refresh_unity
- Add missing resources: gameobject_api, editor_state_v2
- Make descriptions more concise across all tools and resources
- Ensure documentation matches current MCP server functionality
* fix: Address code review feedback
- ParamCoercion: Use InvariantCulture for int/double parsing consistency
- ManageComponents: Remove redundant Undo.RecordObject (AddComponent handles undo)
- ManageScene: Replace deprecated FindObjectsOfType with FindObjectsByType
- GameObjectLookup: Add explanatory comment to empty catch block
- gameobject.py: Extract _validate_instance_id helper to reduce duplication
- Tests: Fix assertion for instanceID (Unity IDs can be negative)
* chore: Remove accidentally committed test artifacts
- Remove Materials folder (40 .mat files from interactive testing)
- Remove Shaders folder (5 noise shaders from testing)
- Remove test scripts (Bounce*, CylinderBounce* from testing)
- Remove Temp.meta and commit.sh
* test: Improve delete tests to verify actual deletion
- Delete_ByTag_DeletesMatchingObjects: Verify objects are actually destroyed
- Delete_ByLayer_DeletesMatchingObjects: Assert deletion using Unity null check
- Delete_MultipleObjectsSameName_DeletesCorrectly: Document first-match behavior
- Delete_Success_ReturnsDeletedCount: Verify count value if present
All tests now verify deletion occurred rather than just checking for a result.
* refactor: remove deprecated manage_gameobject actions
- Remove deprecated switch cases: find, get_components, get_component, add_component, remove_component, set_component_property
- Remove deprecated wrapper methods (423 lines deleted from ManageGameObject.cs)
- Delete ManageGameObjectFindTests.cs (tests deprecated 'find' action)
- Remove deprecated test methods from ManageGameObjectTests.cs
- Add GameObject resource URIs to README documentation
- Add batch_execute performance tips to README, tool description, and gameobject_api resource
- Enhance batch_execute description to emphasize 10-100x performance gains
Total: ~1200 lines removed. New API (find_gameobjects, manage_components, resources) is the recommended path forward.
* fix: Remove starlette stubs from conftest.py
Starlette is now a proper dependency via the mcp package, so we don't need
to stub it anymore. The real package handles all HTTP transport needs.
2026-01-07 02:13:45 +08:00
|
|
|
return new ErrorResponse($"Error getting component: {e.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|