Skip to content

Commit 83b9e47

Browse files
authored
Merge branch 'CoplayDev:main' into main
2 parents f7ce27d + 9796f8e commit 83b9e47

26 files changed

+860
-25
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Newtonsoft.Json.Linq;
2+
using UnityEngine;
3+
4+
namespace MCPForUnity.Editor.Tools
5+
{
6+
internal static class JsonUtil
7+
{
8+
/// <summary>
9+
/// If @params[paramName] is a JSON string, parse it to a JObject in-place.
10+
/// Logs a warning on parse failure and leaves the original value.
11+
/// </summary>
12+
internal static void CoerceJsonStringParameter(JObject @params, string paramName)
13+
{
14+
if (@params == null || string.IsNullOrEmpty(paramName)) return;
15+
var token = @params[paramName];
16+
if (token != null && token.Type == JTokenType.String)
17+
{
18+
try
19+
{
20+
var parsed = JObject.Parse(token.ToString());
21+
@params[paramName] = parsed;
22+
}
23+
catch (Newtonsoft.Json.JsonReaderException e)
24+
{
25+
Debug.LogWarning($"[MCP] Could not parse '{paramName}' JSON string: {e.Message}");
26+
}
27+
}
28+
}
29+
}
30+
}
31+
32+

MCPForUnity/Editor/Tools/JsonUtil.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Tools/ManageAsset.cs

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ public static object HandleCommand(JObject @params)
6363
// Common parameters
6464
string path = @params["path"]?.ToString();
6565

66+
// Coerce string JSON to JObject for 'properties' if provided as a JSON string
67+
var propertiesToken = @params["properties"];
68+
if (propertiesToken != null && propertiesToken.Type == JTokenType.String)
69+
{
70+
try
71+
{
72+
var parsed = JObject.Parse(propertiesToken.ToString());
73+
@params["properties"] = parsed;
74+
}
75+
catch (Exception e)
76+
{
77+
Debug.LogWarning($"[ManageAsset] Could not parse 'properties' JSON string: {e.Message}");
78+
}
79+
}
80+
6681
try
6782
{
6883
switch (action)
@@ -999,7 +1014,108 @@ private static bool ApplyMaterialProperties(Material mat, JObject properties)
9991014
}
10001015
}
10011016

1002-
// TODO: Add handlers for other property types (Vectors, Ints, Keywords, RenderQueue, etc.)
1017+
// --- Flexible direct property assignment ---
1018+
// Allow payloads like: { "_Color": [r,g,b,a] }, { "_Glossiness": 0.5 }, { "_MainTex": "Assets/.." }
1019+
// while retaining backward compatibility with the structured keys above.
1020+
// This iterates all top-level keys except the reserved structured ones and applies them
1021+
// if they match known shader properties.
1022+
var reservedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "shader", "color", "float", "texture" };
1023+
1024+
// Helper resolves common URP/Standard aliasing (e.g., _Color <-> _BaseColor, _MainTex <-> _BaseMap, _Glossiness <-> _Smoothness)
1025+
string ResolvePropertyName(string name)
1026+
{
1027+
if (string.IsNullOrEmpty(name)) return name;
1028+
string[] candidates;
1029+
switch (name)
1030+
{
1031+
case "_Color": candidates = new[] { "_Color", "_BaseColor" }; break;
1032+
case "_BaseColor": candidates = new[] { "_BaseColor", "_Color" }; break;
1033+
case "_MainTex": candidates = new[] { "_MainTex", "_BaseMap" }; break;
1034+
case "_BaseMap": candidates = new[] { "_BaseMap", "_MainTex" }; break;
1035+
case "_Glossiness": candidates = new[] { "_Glossiness", "_Smoothness" }; break;
1036+
case "_Smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
1037+
default: candidates = new[] { name }; break;
1038+
}
1039+
foreach (var candidate in candidates)
1040+
{
1041+
if (mat.HasProperty(candidate)) return candidate;
1042+
}
1043+
return name; // fall back to original
1044+
}
1045+
1046+
foreach (var prop in properties.Properties())
1047+
{
1048+
if (reservedKeys.Contains(prop.Name)) continue;
1049+
string shaderProp = ResolvePropertyName(prop.Name);
1050+
JToken v = prop.Value;
1051+
1052+
// Color: numeric array [r,g,b,(a)]
1053+
if (v is JArray arr && arr.Count >= 3 && arr.All(t => t.Type == JTokenType.Float || t.Type == JTokenType.Integer))
1054+
{
1055+
if (mat.HasProperty(shaderProp))
1056+
{
1057+
try
1058+
{
1059+
var c = new Color(
1060+
arr[0].ToObject<float>(),
1061+
arr[1].ToObject<float>(),
1062+
arr[2].ToObject<float>(),
1063+
arr.Count > 3 ? arr[3].ToObject<float>() : 1f
1064+
);
1065+
if (mat.GetColor(shaderProp) != c)
1066+
{
1067+
mat.SetColor(shaderProp, c);
1068+
modified = true;
1069+
}
1070+
}
1071+
catch (Exception ex)
1072+
{
1073+
Debug.LogWarning($"Error setting color '{shaderProp}': {ex.Message}");
1074+
}
1075+
}
1076+
continue;
1077+
}
1078+
1079+
// Float: single number
1080+
if (v.Type == JTokenType.Float || v.Type == JTokenType.Integer)
1081+
{
1082+
if (mat.HasProperty(shaderProp))
1083+
{
1084+
try
1085+
{
1086+
float f = v.ToObject<float>();
1087+
if (!Mathf.Approximately(mat.GetFloat(shaderProp), f))
1088+
{
1089+
mat.SetFloat(shaderProp, f);
1090+
modified = true;
1091+
}
1092+
}
1093+
catch (Exception ex)
1094+
{
1095+
Debug.LogWarning($"Error setting float '{shaderProp}': {ex.Message}");
1096+
}
1097+
}
1098+
continue;
1099+
}
1100+
1101+
// Texture: string path
1102+
if (v.Type == JTokenType.String)
1103+
{
1104+
string texPath = v.ToString();
1105+
if (!string.IsNullOrEmpty(texPath) && mat.HasProperty(shaderProp))
1106+
{
1107+
var tex = AssetDatabase.LoadAssetAtPath<Texture>(AssetPathUtility.SanitizeAssetPath(texPath));
1108+
if (tex != null && mat.GetTexture(shaderProp) != tex)
1109+
{
1110+
mat.SetTexture(shaderProp, tex);
1111+
modified = true;
1112+
}
1113+
}
1114+
continue;
1115+
}
1116+
}
1117+
1118+
// TODO: Add handlers for other property types (Vectors, Ints, Keywords, RenderQueue, etc.)
10031119
return modified;
10041120
}
10051121

MCPForUnity/Editor/Tools/ManageGameObject.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ public static object HandleCommand(JObject @params)
6666
bool includeNonPublicSerialized = @params["includeNonPublicSerialized"]?.ToObject<bool>() ?? true; // Default to true
6767
// --- End add parameter ---
6868

69+
// Coerce string JSON to JObject for 'componentProperties' if provided as a JSON string
70+
var componentPropsToken = @params["componentProperties"];
71+
if (componentPropsToken != null && componentPropsToken.Type == JTokenType.String)
72+
{
73+
try
74+
{
75+
var parsed = JObject.Parse(componentPropsToken.ToString());
76+
@params["componentProperties"] = parsed;
77+
}
78+
catch (Exception e)
79+
{
80+
Debug.LogWarning($"[ManageGameObject] Could not parse 'componentProperties' JSON string: {e.Message}");
81+
}
82+
}
83+
6984
// --- Prefab Redirection Check ---
7085
string targetPath =
7186
targetToken?.Type == JTokenType.String ? targetToken.ToString() : null;

MCPForUnity/UnityMcpServer~/src/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "MCPForUnityServer"
3-
version = "6.2.4"
3+
version = "6.2.5"
44
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
55
readme = "README.md"
66
requires-python = ">=3.10"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6.2.4
1+
6.2.5

MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Defines the manage_asset tool for interacting with Unity assets.
33
"""
44
import asyncio
5+
import json
56
from typing import Annotated, Any, Literal
67

78
from fastmcp import Context
@@ -33,6 +34,14 @@ async def manage_asset(
3334
page_number: Annotated[int | float | str, "Page number for pagination"] | None = None
3435
) -> dict[str, Any]:
3536
ctx.info(f"Processing manage_asset: {action}")
37+
# Coerce 'properties' from JSON string to dict for client compatibility
38+
if isinstance(properties, str):
39+
try:
40+
properties = json.loads(properties)
41+
ctx.info("manage_asset: coerced properties from JSON string to dict")
42+
except Exception as e:
43+
ctx.warn(f"manage_asset: failed to parse properties JSON string: {e}")
44+
# Leave properties as-is; Unity side may handle defaults
3645
# Ensure properties is a dict if None
3746
if properties is None:
3847
properties = {}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from typing import Annotated, Any, Literal
23

34
from fastmcp import Context
@@ -42,7 +43,7 @@ def manage_gameobject(
4243
layer: Annotated[str, "Layer name"] | None = None,
4344
components_to_remove: Annotated[list[str],
4445
"List of component names to remove"] | None = None,
45-
component_properties: Annotated[dict[str, dict[str, Any]],
46+
component_properties: Annotated[dict[str, dict[str, Any]] | str,
4647
"""Dictionary of component names to their properties to set. For example:
4748
`{"MyScript": {"otherObject": {"find": "Player", "method": "by_name"}}}` assigns GameObject
4849
`{"MyScript": {"playerHealth": {"find": "Player", "component": "HealthComponent"}}}` assigns Component
@@ -65,7 +66,7 @@ def manage_gameobject(
6566
"Controls whether serialization of private [SerializeField] fields is included (accepts true/false or 'true'/'false')"] | None = None,
6667
) -> dict[str, Any]:
6768
ctx.info(f"Processing manage_gameobject: {action}")
68-
69+
6970
# Coercers to tolerate stringified booleans and vectors
7071
def _coerce_bool(value, default=None):
7172
if value is None:
@@ -113,6 +114,16 @@ def _to_vec3(parts):
113114
search_inactive = _coerce_bool(search_inactive)
114115
includeNonPublicSerialized = _coerce_bool(includeNonPublicSerialized)
115116

117+
# Coerce 'component_properties' from JSON string to dict for client compatibility
118+
if isinstance(component_properties, str):
119+
try:
120+
component_properties = json.loads(component_properties)
121+
ctx.info("manage_gameobject: coerced component_properties from JSON string to dict")
122+
except json.JSONDecodeError as e:
123+
return {"success": False, "message": f"Invalid JSON in component_properties: {e}"}
124+
# Ensure final type is a dict (object) if provided
125+
if component_properties is not None and not isinstance(component_properties, dict):
126+
return {"success": False, "message": "component_properties must be a JSON object (dict)."}
116127
try:
117128
# Map tag to search_term when search_method is by_tag for backward compatibility
118129
if action == "find" and search_method == "by_tag" and tag is not None and search_term is None:

0 commit comments

Comments
 (0)