Skip to content

Commit 10d4e3b

Browse files
authored
Merge pull request #296 from dsarno/managegameobject-fix
fix: add parameter validation to manage_gameobject tool
2 parents 048b125 + a435973 commit 10d4e3b

File tree

2 files changed

+103
-7
lines changed

2 files changed

+103
-7
lines changed

UnityMcpBridge/Editor/Tools/ManageGameObject.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ public static object HandleCommand(JObject @params)
155155
);
156156
// Pass the includeNonPublicSerialized flag here
157157
return GetComponentsFromTarget(getCompTarget, searchMethod, includeNonPublicSerialized);
158+
case "get_component":
159+
string getSingleCompTarget = targetToken?.ToString();
160+
if (getSingleCompTarget == null)
161+
return Response.Error(
162+
"'target' parameter required for get_component."
163+
);
164+
string componentName = @params["componentName"]?.ToString();
165+
if (string.IsNullOrEmpty(componentName))
166+
return Response.Error(
167+
"'componentName' parameter required for get_component."
168+
);
169+
return GetSingleComponentFromTarget(getSingleCompTarget, searchMethod, componentName, includeNonPublicSerialized);
158170
case "add_component":
159171
return AddComponentToTarget(@params, targetToken, searchMethod);
160172
case "remove_component":
@@ -1008,6 +1020,70 @@ private static object GetComponentsFromTarget(string target, string searchMethod
10081020
}
10091021
}
10101022

1023+
private static object GetSingleComponentFromTarget(string target, string searchMethod, string componentName, bool includeNonPublicSerialized = true)
1024+
{
1025+
GameObject targetGo = FindObjectInternal(target, searchMethod);
1026+
if (targetGo == null)
1027+
{
1028+
return Response.Error(
1029+
$"Target GameObject ('{target}') not found using method '{searchMethod ?? "default"}'."
1030+
);
1031+
}
1032+
1033+
try
1034+
{
1035+
// Try to find the component by name
1036+
Component targetComponent = targetGo.GetComponent(componentName);
1037+
1038+
// If not found directly, try to find by type name (handle namespaces)
1039+
if (targetComponent == null)
1040+
{
1041+
Component[] allComponents = targetGo.GetComponents<Component>();
1042+
foreach (Component comp in allComponents)
1043+
{
1044+
if (comp != null)
1045+
{
1046+
string typeName = comp.GetType().Name;
1047+
string fullTypeName = comp.GetType().FullName;
1048+
1049+
if (typeName == componentName || fullTypeName == componentName)
1050+
{
1051+
targetComponent = comp;
1052+
break;
1053+
}
1054+
}
1055+
}
1056+
}
1057+
1058+
if (targetComponent == null)
1059+
{
1060+
return Response.Error(
1061+
$"Component '{componentName}' not found on GameObject '{targetGo.name}'."
1062+
);
1063+
}
1064+
1065+
var componentData = Helpers.GameObjectSerializer.GetComponentData(targetComponent, includeNonPublicSerialized);
1066+
1067+
if (componentData == null)
1068+
{
1069+
return Response.Error(
1070+
$"Failed to serialize component '{componentName}' on GameObject '{targetGo.name}'."
1071+
);
1072+
}
1073+
1074+
return Response.Success(
1075+
$"Retrieved component '{componentName}' from '{targetGo.name}'.",
1076+
componentData
1077+
);
1078+
}
1079+
catch (Exception e)
1080+
{
1081+
return Response.Error(
1082+
$"Error getting component '{componentName}' from '{targetGo.name}': {e.Message}"
1083+
);
1084+
}
1085+
}
1086+
10111087
private static object AddComponentToTarget(
10121088
JObject @params,
10131089
JToken targetToken,

UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
def register_manage_gameobject_tools(mcp: FastMCP):
1010
"""Register all GameObject management tools with the MCP server."""
1111

12-
@mcp.tool(name="manage_gameobject", description="Manage GameObjects. Note: for 'get_components', the `data` field contains a dictionary of component names and their serialized properties.")
12+
@mcp.tool(name="manage_gameobject", description="Manage GameObjects. Note: for 'get_components', the `data` field contains a dictionary of component names and their serialized properties. For 'get_component', specify 'component_name' to retrieve only that component's serialized data.")
1313
@telemetry_tool("manage_gameobject")
1414
def manage_gameobject(
1515
ctx: Context,
16-
action: Annotated[Literal["create", "modify", "delete", "find", "add_component", "remove_component", "set_component_property", "get_components"], "Perform CRUD operations on GameObjects and components."],
16+
action: Annotated[Literal["create", "modify", "delete", "find", "add_component", "remove_component", "set_component_property", "get_components", "get_component"], "Perform CRUD operations on GameObjects and components."],
1717
target: Annotated[str,
1818
"GameObject identifier by name or path for modify/delete/component actions"] | None = None,
19-
search_method: Annotated[str,
20-
"How to find objects ('by_name', 'by_id', 'by_path', etc.). Used with 'find' and some 'target' lookups."] | None = None,
19+
search_method: Annotated[Literal["by_id", "by_name", "by_path", "by_tag", "by_layer", "by_component"],
20+
"How to find objects. Used with 'find' and some 'target' lookups."] | None = None,
2121
name: Annotated[str,
22-
"GameObject name - used for both 'create' (initial name) and 'modify' (rename)"] | None = None,
22+
"GameObject name for 'create' (initial name) and 'modify' (rename) actions ONLY. For 'find' action, use 'search_term' instead."] | None = None,
2323
tag: Annotated[str,
2424
"Tag name - used for both 'create' (initial tag) and 'modify' (change tag)"] | None = None,
2525
parent: Annotated[str,
@@ -53,7 +53,7 @@ def manage_gameobject(
5353
- Access shared material: `{"MeshRenderer": {"sharedMaterial.color": [1, 0, 0, 1]}}`"""] | None = None,
5454
# --- Parameters for 'find' ---
5555
search_term: Annotated[str,
56-
"Search term for 'find' action"] | None = None,
56+
"Search term for 'find' action ONLY. Use this (not 'name') when searching for GameObjects."] | None = None,
5757
find_all: Annotated[bool,
5858
"If True, finds all GameObjects matching the search term"] | None = None,
5959
search_in_children: Annotated[bool,
@@ -69,6 +69,26 @@ def manage_gameobject(
6969
) -> dict[str, Any]:
7070
ctx.info(f"Processing manage_gameobject: {action}")
7171
try:
72+
# Validate parameter usage to prevent silent failures
73+
if action == "find":
74+
if name is not None:
75+
return {
76+
"success": False,
77+
"message": "For 'find' action, use 'search_term' parameter, not 'name'. Remove 'name' parameter. Example: search_term='Player', search_method='by_name'"
78+
}
79+
if search_term is None:
80+
return {
81+
"success": False,
82+
"message": "For 'find' action, 'search_term' parameter is required. Use search_term (not 'name') to specify what to find."
83+
}
84+
85+
if action in ["create", "modify"]:
86+
if search_term is not None:
87+
return {
88+
"success": False,
89+
"message": f"For '{action}' action, use 'name' parameter, not 'search_term'."
90+
}
91+
7292
# Prepare parameters, removing None values
7393
params = {
7494
"action": action,
@@ -125,4 +145,4 @@ def manage_gameobject(
125145
return response if isinstance(response, dict) else {"success": False, "message": str(response)}
126146

127147
except Exception as e:
128-
return {"success": False, "message": f"Python error managing GameObject: {str(e)}"}
148+
return {"success": False, "message": f"Python error managing GameObject: {str(e)}"}

0 commit comments

Comments
 (0)