Skip to content

Commit 00b3944

Browse files
committed
chore: Delete obsolete .windsurf/workflows/git-commits.md
fix: Fix issue # reported on GitHub by allowing properties to be changed with UpdateComponent call feat(tools): support GameObject selection by name, path, or instance ID. This improves flexibility for tool-driven GameObject selection in Unity. - Update `selectGameObjectTool.ts` and C# counterpart to allow selecting GameObjects by instance ID, name, or hierarchical path. - Refactor parameter schemas and validation logic to use `idOrName` for flexible identification. - Update prompt and resource logic to match new selection method.
1 parent 2d8d853 commit 00b3944

File tree

8 files changed

+68
-102
lines changed

8 files changed

+68
-102
lines changed

.windsurf/workflows/git-commits.md

Lines changed: 0 additions & 60 deletions
This file was deleted.

Editor/Resources/GetGameObjectResource.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ public class GetGameObjectResource : McpResourceBase
1515
public GetGameObjectResource()
1616
{
1717
Name = "get_gameobject";
18-
Description = "Retrieves detailed information about a specific GameObject by instance ID";
19-
Uri = "unity://gameobject/{id}";
18+
Description = "Retrieves detailed information about a specific GameObject by instance ID or object name or path";
19+
Uri = "unity://gameobject/{idOrName}";
2020
}
2121

2222
/// <summary>
@@ -27,28 +27,39 @@ public GetGameObjectResource()
2727
public override JObject Fetch(JObject parameters)
2828
{
2929
// Validate parameters
30-
if (parameters == null || !parameters.ContainsKey("objectPathId"))
30+
if (parameters == null || !parameters.ContainsKey("idOrName"))
3131
{
3232
return new JObject
3333
{
3434
["success"] = false,
35-
["message"] = "Missing required parameter: objectPathId"
35+
["message"] = "Missing required parameter: idOrName"
36+
};
37+
}
38+
39+
string idOrName = parameters["idOrName"]?.ToObject<string>();
40+
41+
if (string.IsNullOrEmpty(idOrName))
42+
{
43+
return new JObject
44+
{
45+
["success"] = false,
46+
["message"] = "Parameter 'objectPathId' cannot be null or empty"
3647
};
3748
}
3849

39-
string objectPathId = parameters["objectPathId"]?.ToObject<string>();
4050
GameObject gameObject = null;
4151

4252
// Try to parse as an instance ID first
43-
if (int.TryParse(objectPathId, out int instanceId))
53+
if (int.TryParse(idOrName, out int instanceId))
4454
{
45-
// If it's a valid integer, try to find by instance ID
46-
gameObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
55+
// Unity Instance IDs are typically negative, but we'll accept any integer
56+
UnityEngine.Object unityObject = EditorUtility.InstanceIDToObject(instanceId);
57+
gameObject = unityObject as GameObject;
4758
}
4859
else
4960
{
50-
// Otherwise, treat it as a path
51-
gameObject = GameObject.Find(objectPathId);
61+
// Otherwise, treat it as a name or hierarchical path
62+
gameObject = GameObject.Find(idOrName);
5263
}
5364

5465
// Check if the GameObject was found
@@ -57,7 +68,7 @@ public override JObject Fetch(JObject parameters)
5768
return new JObject
5869
{
5970
["success"] = false,
60-
["message"] = $"GameObject with path '{objectPathId}' not found"
71+
["message"] = $"GameObject with '{idOrName}' reference not found. Make sure the GameObject exists and is loaded in the current scene(s)."
6172
};
6273
}
6374

@@ -69,7 +80,8 @@ public override JObject Fetch(JObject parameters)
6980
{
7081
["success"] = true,
7182
["message"] = $"Retrieved GameObject data for '{gameObject.name}'",
72-
["gameObject"] = gameObjectData
83+
["gameObject"] = gameObjectData,
84+
["instanceId"] = gameObject.GetInstanceID()
7385
};
7486
}
7587

Editor/Tools/SelectGameObjectTool.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class SelectGameObjectTool : McpToolBase
1616
public SelectGameObjectTool()
1717
{
1818
Name = "select_gameobject";
19-
Description = "Sets the selected GameObject in the Unity editor by path or instance ID";
19+
Description = "Sets the selected GameObject in the Unity editor by path, name or instance ID";
2020
}
2121

2222
/// <summary>
@@ -27,13 +27,14 @@ public override JObject Execute(JObject parameters)
2727
{
2828
// Extract parameters
2929
string objectPath = parameters["objectPath"]?.ToObject<string>();
30+
string objectName = parameters["objectName"]?.ToObject<string>();
3031
int? instanceId = parameters["instanceId"]?.ToObject<int?>();
3132

3233
// Validate parameters - require either objectPath or instanceId
33-
if (string.IsNullOrEmpty(objectPath) && !instanceId.HasValue)
34+
if (string.IsNullOrEmpty(objectPath) && string.IsNullOrEmpty(objectName) && !instanceId.HasValue)
3435
{
3536
return McpUnitySocketHandler.CreateErrorResponse(
36-
"Required parameter 'objectPath' or 'instanceId' not provided",
37+
"Required parameter 'objectPath', 'objectName' or 'instanceId' not provided",
3738
"validation_error"
3839
);
3940
}
@@ -44,26 +45,29 @@ public override JObject Execute(JObject parameters)
4445
Selection.activeGameObject = EditorUtility.InstanceIDToObject(instanceId.Value) as GameObject;
4546
}
4647
// Otherwise, try to find by object path/name if provided
47-
else
48+
else if (!string.IsNullOrEmpty(objectPath))
4849
{
4950
// Try to find the object by path in the hierarchy
5051
Selection.activeGameObject = GameObject.Find(objectPath);
5152
}
53+
else if (!string.IsNullOrEmpty(objectName))
54+
{
55+
// Try to find the object by name in the hierarchy
56+
Selection.activeGameObject = GameObject.Find(objectName);
57+
}
5258

5359
// Ping the selected object
5460
EditorGUIUtility.PingObject(Selection.activeGameObject);
5561

5662
// Log the selection
57-
McpLogger.LogInfo($"[MCP Unity] Selected GameObject: " +
58-
(instanceId.HasValue ? $"Instance ID {instanceId.Value}" : $"Path '{objectPath}'"));
63+
McpLogger.LogInfo($"[MCP Unity] Selected GameObject: {Selection.activeGameObject.name}"));
5964

6065
// Create the response
6166
return new JObject
6267
{
6368
["success"] = true,
6469
["type"] = "text",
65-
["message"] = $"Successfully selected GameObject" +
66-
(instanceId.HasValue ? $" with instance ID: {instanceId.Value}" : $": {objectPath}")
70+
["message"] = $"Successfully selected GameObject {Selection.activeGameObject.name}"
6771
};
6872
}
6973
}

Editor/Tools/UpdateComponentTool.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ private bool UpdateComponentData(Component component, JObject componentData)
249249
// Record object for undo
250250
Undo.RecordObject(component, $"Update {componentType.Name} fields");
251251

252-
// Process each field in the component data
252+
// Process each field or property in the component data
253253
foreach (var property in componentData.Properties())
254254
{
255255
string fieldName = property.Name;
@@ -272,10 +272,21 @@ private bool UpdateComponentData(Component component, JObject componentData)
272272
anySuccess = true;
273273
continue;
274274
}
275-
else
275+
276+
// Try to update property if not found as a field
277+
PropertyInfo propertyInfo = componentType.GetProperty(fieldName,
278+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
279+
280+
if (propertyInfo != null)
276281
{
277-
McpLogger.LogWarning($"Field '{fieldName}' not found on component '{componentType.Name}'");
282+
object value = ConvertJTokenToValue(fieldValue, propertyInfo.PropertyType);
283+
propertyInfo.SetValue(component, value);
284+
anySuccess = true;
285+
continue;
278286
}
287+
288+
// Try to update field
289+
McpLogger.LogWarning($"Field or Property with name '{fieldName}' not found on component '{componentType.Name}'");
279290
}
280291

281292
return anySuccess;

Editor/Utils/McpUtils.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,7 @@ private static bool AddToConfigFile(string configFilePath, bool useTabsIndentati
195195
// Check if the file exists
196196
if (File.Exists(configFilePath))
197197
{
198-
if (TryMergeMcpServers(configFilePath, mcpConfig, productName))
199-
{
200-
return true;
201-
}
198+
return TryMergeMcpServers(configFilePath, mcpConfig, productName);
202199
}
203200
else if(Directory.Exists(Path.GetDirectoryName(configFilePath)))
204201
{

Server~/src/prompts/gameobjectHandlingPrompt.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,28 @@ export function registerGameObjectHandlingPrompt(server: McpServer) {
1212
'gameobject_handling_strategy',
1313
'Defines the proper workflow for handling gameobjects in Unity',
1414
{
15-
gameObjectId: z.string().describe("The ID of the GameObject to handle. It can be the name of the GameObject or the path to the GameObject."),
15+
gameObjectIdOrName: z.string().describe("The resource to identify the GameObject intended to handle. It can be the **instance ID**, the **name** or the **path** to the GameObject."),
1616
},
17-
async ({ gameObjectId }) => ({
17+
async ({ gameObjectIdOrName }) => ({
1818
messages: [
1919
{
2020
role: 'user',
2121
content: {
2222
type: 'text',
2323
text: `You are an expert AI assistant integrated with Unity via MCP.
24+
2425
When working directly with GameObjects or any of their components in Unity scenes, you have access to the following resources and tools:
2526
- Resource "get_scenes_hierarchy" (unity://scenes_hierarchy) to list all GameObjects.
26-
- Resource "get_gameobject" (unity://gameobject/{id}) to fetch detailed GameObject info, with the id being the name of the GameObject or the path to the GameObject.
27-
- Tool "select_gameobject" to select a GameObject by ID or path.
27+
- Resource "get_gameobject" (unity://gameobject/{idOrName}) to fetch detailed GameObject info, with the *idOrName* being either the **instance ID**, the **name** or the **path** to the GameObject.
28+
- Tool "select_gameobject" to select a GameObject by **instance ID**, the **name** or the **path** of the GameObject.
2829
- Tool "update_gameobject" to update a GameObject's core properties (name, tag, layer, active state, static state), or create the GameObject if it does not exist.
2930
- Tool "update_component" to update or add a component on a GameObject, including common frequently used components (e.g. Transform, RectTransform, BoxCollider, Rigidbody, etc).
3031
3132
Workflow:
32-
1. Use "get_scenes_hierarchy" to confirm the GameObject ID or path for "${gameObjectId}".
33+
1. Use "get_scenes_hierarchy" to confirm the GameObject ID, name or path for "${gameObjectIdOrName}".
3334
2. If you need to update the GameObject's core properties (name, tag, layer, active state, static state), or create the GameObject if it does not exist, use "update_gameobject".
3435
3. To focus the Unity Editor on the target GameObject, invoke "select_gameobject".
35-
4. Optionally, use "unity://gameobject/${gameObjectId}" to retrieve detailed properties.
36+
4. Optionally, use "unity://gameobject/${gameObjectIdOrName}" to retrieve detailed properties.
3637
5. To update or add a component on the GameObject, use "update_component".
3738
6. Confirm success and report any errors.
3839
@@ -46,7 +47,7 @@ Guidance:
4647
role: 'user',
4748
content: {
4849
type: 'text',
49-
text: `Handle GameObject "${gameObjectId}" through the above workflow.`
50+
text: `Handle GameObject "${gameObjectIdOrName}" through the above workflow.`
5051
}
5152
}
5253
]

Server~/src/resources/getGameObjectResource.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { resourceName as hierarchyResourceName } from './getScenesHierarchyResou
88

99
// Constants for the resource
1010
const resourceName = 'get_gameobject';
11-
const resourceUri = 'unity://gameobject/{id}';
11+
const resourceUri = 'unity://gameobject/{idOrName}';
1212
const resourceMimeType = 'application/json';
1313

1414
/**
@@ -36,7 +36,7 @@ export function registerGetGameObjectResource(server: McpServer, mcpUnity: McpUn
3636
resourceName,
3737
resourceTemplate,
3838
{
39-
description: 'Retrieve a GameObject by ID or path',
39+
description: 'Retrieve a GameObject by instance ID, name, or hierarchical path (e.g., "Parent/Child/MyObject")',
4040
mimeType: resourceMimeType
4141
},
4242
async (uri, variables) => {
@@ -61,13 +61,13 @@ export function registerGetGameObjectResource(server: McpServer, mcpUnity: McpUn
6161
*/
6262
async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variables, logger: Logger): Promise<ReadResourceResult> {
6363
// Extract and convert the parameter from the template variables
64-
const id = decodeURIComponent(variables["id"] as string);
64+
const idOrName = decodeURIComponent(variables["idOrName"] as string);
6565

6666
// Send request to Unity
6767
const response = await mcpUnity.sendRequest({
6868
method: resourceName,
6969
params: {
70-
objectPathId: id
70+
idOrName: idOrName
7171
}
7272
});
7373

@@ -80,7 +80,7 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable
8080

8181
return {
8282
contents: [{
83-
uri: `unity://gameobject/${id}`,
83+
uri: `unity://gameobject/${idOrName}`,
8484
mimeType: resourceMimeType,
8585
text: JSON.stringify(response, null, 2)
8686
}]

Server~/src/tools/selectGameObjectTool.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
77

88
// Constants for the tool
99
const toolName = 'select_gameobject';
10-
const toolDescription = 'Sets the selected GameObject in the Unity editor by path or instance ID';
10+
const toolDescription = 'Sets the selected GameObject in the Unity editor by path, name or instance ID';
1111
const paramsSchema = z.object({
1212
objectPath: z.string().optional().describe('The path or name of the GameObject to select (e.g. "Main Camera")'),
13+
objectName: z.string().optional().describe('The name of the GameObject to select'),
1314
instanceId: z.number().optional().describe('The instance ID of the GameObject to select')
1415
});
1516

@@ -53,10 +54,10 @@ export function registerSelectGameObjectTool(server: McpServer, mcpUnity: McpUni
5354
*/
5455
async function toolHandler(mcpUnity: McpUnity, params: any): Promise<CallToolResult> {
5556
// Custom validation since we can't use refine/superRefine while maintaining ZodObject type
56-
if (params.objectPath === undefined && params.instanceId === undefined) {
57+
if (params.objectPath === undefined && params.objectName === undefined && params.instanceId === undefined) {
5758
throw new McpUnityError(
5859
ErrorType.VALIDATION,
59-
"Either 'objectPath' or 'instanceId' must be provided"
60+
"Either 'objectPath', 'objectName' or 'instanceId' must be provided"
6061
);
6162
}
6263

0 commit comments

Comments
 (0)