Skip to content

Commit 67be840

Browse files
authored
Merge branch 'CoplayDev:main' into main
2 parents 83b16ea + 0397887 commit 67be840

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1704
-266
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ TestProjects/UnityMCPTests/Packages/packages-lock.json
3939
# Backup artifacts
4040
*.backup
4141
*.backup.meta
42+
43+
.wt-origin-main/

MCPForUnity/Editor/Dependencies/PlatformDetectors.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Helpers/CodexConfigHelper.cs

Lines changed: 68 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5-
using System.Text;
6-
using System.Text.RegularExpressions;
75
using MCPForUnity.External.Tommy;
8-
using Newtonsoft.Json;
96

107
namespace MCPForUnity.Editor.Helpers
118
{
@@ -42,108 +39,107 @@ public static bool IsCodexConfigured(string pythonDir)
4239

4340
public static string BuildCodexServerBlock(string uvPath, string serverSrc)
4441
{
45-
string argsArray = FormatTomlStringArray(new[] { "run", "--directory", serverSrc, "server.py" });
46-
return $"[mcp_servers.unityMCP]{Environment.NewLine}" +
47-
$"command = \"{EscapeTomlString(uvPath)}\"{Environment.NewLine}" +
48-
$"args = {argsArray}";
42+
var table = new TomlTable();
43+
var mcpServers = new TomlTable();
44+
45+
mcpServers["unityMCP"] = CreateUnityMcpTable(uvPath, serverSrc);
46+
table["mcp_servers"] = mcpServers;
47+
48+
using var writer = new StringWriter();
49+
table.WriteTo(writer);
50+
return writer.ToString();
4951
}
5052

51-
public static string UpsertCodexServerBlock(string existingToml, string newBlock)
53+
public static string UpsertCodexServerBlock(string existingToml, string uvPath, string serverSrc)
5254
{
53-
if (string.IsNullOrWhiteSpace(existingToml))
55+
// Parse existing TOML or create new root table
56+
var root = TryParseToml(existingToml) ?? new TomlTable();
57+
58+
// Ensure mcp_servers table exists
59+
if (!root.TryGetNode("mcp_servers", out var mcpServersNode) || !(mcpServersNode is TomlTable))
5460
{
55-
return newBlock.TrimEnd() + Environment.NewLine;
61+
root["mcp_servers"] = new TomlTable();
5662
}
63+
var mcpServers = root["mcp_servers"] as TomlTable;
5764

58-
StringBuilder sb = new StringBuilder();
59-
using StringReader reader = new StringReader(existingToml);
60-
string line;
61-
bool inTarget = false;
62-
bool replaced = false;
63-
while ((line = reader.ReadLine()) != null)
64-
{
65-
string trimmed = line.Trim();
66-
bool isSection = trimmed.StartsWith("[") && trimmed.EndsWith("]") && !trimmed.StartsWith("[[");
67-
if (isSection)
68-
{
69-
bool isTarget = string.Equals(trimmed, "[mcp_servers.unityMCP]", StringComparison.OrdinalIgnoreCase);
70-
if (isTarget)
71-
{
72-
if (!replaced)
73-
{
74-
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
75-
sb.AppendLine(newBlock.TrimEnd());
76-
replaced = true;
77-
}
78-
inTarget = true;
79-
continue;
80-
}
65+
// Create or update unityMCP table
66+
mcpServers["unityMCP"] = CreateUnityMcpTable(uvPath, serverSrc);
8167

82-
if (inTarget)
83-
{
84-
inTarget = false;
85-
}
86-
}
68+
// Serialize back to TOML
69+
using var writer = new StringWriter();
70+
root.WriteTo(writer);
71+
return writer.ToString();
72+
}
8773

88-
if (inTarget)
89-
{
90-
continue;
91-
}
74+
public static bool TryParseCodexServer(string toml, out string command, out string[] args)
75+
{
76+
command = null;
77+
args = null;
9278

93-
sb.AppendLine(line);
79+
var root = TryParseToml(toml);
80+
if (root == null) return false;
81+
82+
if (!TryGetTable(root, "mcp_servers", out var servers)
83+
&& !TryGetTable(root, "mcpServers", out servers))
84+
{
85+
return false;
9486
}
9587

96-
if (!replaced)
88+
if (!TryGetTable(servers, "unityMCP", out var unity))
9789
{
98-
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
99-
sb.AppendLine(newBlock.TrimEnd());
90+
return false;
10091
}
10192

102-
return sb.ToString().TrimEnd() + Environment.NewLine;
93+
command = GetTomlString(unity, "command");
94+
args = GetTomlStringArray(unity, "args");
95+
96+
return !string.IsNullOrEmpty(command) && args != null;
10397
}
10498

105-
public static bool TryParseCodexServer(string toml, out string command, out string[] args)
99+
/// <summary>
100+
/// Safely parses TOML string, returning null on failure
101+
/// </summary>
102+
private static TomlTable TryParseToml(string toml)
106103
{
107-
command = null;
108-
args = null;
109-
if (string.IsNullOrWhiteSpace(toml)) return false;
104+
if (string.IsNullOrWhiteSpace(toml)) return null;
110105

111106
try
112107
{
113108
using var reader = new StringReader(toml);
114-
TomlTable root = TOML.Parse(reader);
115-
if (root == null) return false;
116-
117-
if (!TryGetTable(root, "mcp_servers", out var servers)
118-
&& !TryGetTable(root, "mcpServers", out servers))
119-
{
120-
return false;
121-
}
122-
123-
if (!TryGetTable(servers, "unityMCP", out var unity))
124-
{
125-
return false;
126-
}
127-
128-
command = GetTomlString(unity, "command");
129-
args = GetTomlStringArray(unity, "args");
130-
131-
return !string.IsNullOrEmpty(command) && args != null;
109+
return TOML.Parse(reader);
132110
}
133111
catch (TomlParseException)
134112
{
135-
return false;
113+
return null;
136114
}
137115
catch (TomlSyntaxException)
138116
{
139-
return false;
117+
return null;
140118
}
141119
catch (FormatException)
142120
{
143-
return false;
121+
return null;
144122
}
145123
}
146124

125+
/// <summary>
126+
/// Creates a TomlTable for the unityMCP server configuration
127+
/// </summary>
128+
private static TomlTable CreateUnityMcpTable(string uvPath, string serverSrc)
129+
{
130+
var unityMCP = new TomlTable();
131+
unityMCP["command"] = new TomlString { Value = uvPath };
132+
133+
var argsArray = new TomlArray();
134+
argsArray.Add(new TomlString { Value = "run" });
135+
argsArray.Add(new TomlString { Value = "--directory" });
136+
argsArray.Add(new TomlString { Value = serverSrc });
137+
argsArray.Add(new TomlString { Value = "server.py" });
138+
unityMCP["args"] = argsArray;
139+
140+
return unityMCP;
141+
}
142+
147143
private static bool TryGetTable(TomlTable parent, string key, out TomlTable table)
148144
{
149145
table = null;
@@ -211,33 +207,5 @@ private static string[] GetTomlStringArray(TomlTable table, string key)
211207

212208
return null;
213209
}
214-
215-
private static string FormatTomlStringArray(IEnumerable<string> values)
216-
{
217-
if (values == null) return "[]";
218-
StringBuilder sb = new StringBuilder();
219-
sb.Append('[');
220-
bool first = true;
221-
foreach (string value in values)
222-
{
223-
if (!first)
224-
{
225-
sb.Append(", ");
226-
}
227-
sb.Append('"').Append(EscapeTomlString(value ?? string.Empty)).Append('"');
228-
first = false;
229-
}
230-
sb.Append(']');
231-
return sb.ToString();
232-
}
233-
234-
private static string EscapeTomlString(string value)
235-
{
236-
if (string.IsNullOrEmpty(value)) return string.Empty;
237-
return value
238-
.Replace("\\", "\\\\")
239-
.Replace("\"", "\\\"");
240-
}
241-
242210
}
243211
}

MCPForUnity/Editor/Helpers/GameObjectSerializer.cs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -255,24 +255,25 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ
255255
var declaredFields = currentType.GetFields(fieldFlags);
256256

257257
// Process the declared Fields for caching
258-
foreach (var fieldInfo in declaredFields)
258+
foreach (var fieldInfo in declaredFields)
259259
{
260260
if (fieldInfo.Name.EndsWith("k__BackingField")) continue; // Skip backing fields
261261

262262
// Add if not already added (handles hiding - keep the most derived version)
263263
if (fieldsToCache.Any(f => f.Name == fieldInfo.Name)) continue;
264264

265-
bool shouldInclude = false;
266-
if (includeNonPublicSerializedFields)
267-
{
268-
// If TRUE, include Public OR NonPublic with [SerializeField]
269-
shouldInclude = fieldInfo.IsPublic || (fieldInfo.IsPrivate && fieldInfo.IsDefined(typeof(SerializeField), inherit: false));
270-
}
271-
else // includeNonPublicSerializedFields is FALSE
272-
{
273-
// If FALSE, include ONLY if it is explicitly Public.
274-
shouldInclude = fieldInfo.IsPublic;
275-
}
265+
bool shouldInclude = false;
266+
if (includeNonPublicSerializedFields)
267+
{
268+
// If TRUE, include Public OR any NonPublic with [SerializeField] (private/protected/internal)
269+
var hasSerializeField = fieldInfo.IsDefined(typeof(SerializeField), inherit: true);
270+
shouldInclude = fieldInfo.IsPublic || (!fieldInfo.IsPublic && hasSerializeField);
271+
}
272+
else // includeNonPublicSerializedFields is FALSE
273+
{
274+
// If FALSE, include ONLY if it is explicitly Public.
275+
shouldInclude = fieldInfo.IsPublic;
276+
}
276277

277278
if (shouldInclude)
278279
{
@@ -357,7 +358,35 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ
357358
// --- Add detailed logging ---
358359
// Debug.Log($"[GetComponentData] Accessing: {componentType.Name}.{propName}");
359360
// --- End detailed logging ---
360-
object value = propInfo.GetValue(c);
361+
362+
// --- Special handling for material/mesh properties in edit mode ---
363+
object value;
364+
if (!Application.isPlaying && (propName == "material" || propName == "materials" || propName == "mesh"))
365+
{
366+
// In edit mode, use sharedMaterial/sharedMesh to avoid instantiation warnings
367+
if ((propName == "material" || propName == "materials") && c is Renderer renderer)
368+
{
369+
if (propName == "material")
370+
value = renderer.sharedMaterial;
371+
else // materials
372+
value = renderer.sharedMaterials;
373+
}
374+
else if (propName == "mesh" && c is MeshFilter meshFilter)
375+
{
376+
value = meshFilter.sharedMesh;
377+
}
378+
else
379+
{
380+
// Fallback to normal property access if type doesn't match
381+
value = propInfo.GetValue(c);
382+
}
383+
}
384+
else
385+
{
386+
value = propInfo.GetValue(c);
387+
}
388+
// --- End special handling ---
389+
361390
Type propType = propInfo.PropertyType;
362391
AddSerializableValue(serializablePropertiesOutput, propName, propType, value);
363392
}

MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,7 @@ public static string ConfigureCodexClient(string pythonDir, string configPath, M
205205
return "Configured successfully";
206206
}
207207

208-
string codexBlock = CodexConfigHelper.BuildCodexServerBlock(uvPath, serverSrc);
209-
string updatedToml = CodexConfigHelper.UpsertCodexServerBlock(existingToml, codexBlock);
208+
string updatedToml = CodexConfigHelper.UpsertCodexServerBlock(existingToml, uvPath, serverSrc);
210209

211210
McpConfigFileHelper.WriteAtomicFile(configPath, updatedToml);
212211

MCPForUnity/Editor/Helpers/PortManager.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
namespace MCPForUnity.Editor.Services
2+
{
3+
/// <summary>
4+
/// Service for checking package updates and version information
5+
/// </summary>
6+
public interface IPackageUpdateService
7+
{
8+
/// <summary>
9+
/// Checks if a newer version of the package is available
10+
/// </summary>
11+
/// <param name="currentVersion">The current package version</param>
12+
/// <returns>Update check result containing availability and latest version info</returns>
13+
UpdateCheckResult CheckForUpdate(string currentVersion);
14+
15+
/// <summary>
16+
/// Compares two version strings to determine if the first is newer than the second
17+
/// </summary>
18+
/// <param name="version1">First version string</param>
19+
/// <param name="version2">Second version string</param>
20+
/// <returns>True if version1 is newer than version2</returns>
21+
bool IsNewerVersion(string version1, string version2);
22+
23+
/// <summary>
24+
/// Determines if the package was installed via Git or Asset Store
25+
/// </summary>
26+
/// <returns>True if installed via Git, false if Asset Store or unknown</returns>
27+
bool IsGitInstallation();
28+
29+
/// <summary>
30+
/// Clears the cached update check data, forcing a fresh check on next request
31+
/// </summary>
32+
void ClearCache();
33+
}
34+
35+
/// <summary>
36+
/// Result of an update check operation
37+
/// </summary>
38+
public class UpdateCheckResult
39+
{
40+
/// <summary>
41+
/// Whether an update is available
42+
/// </summary>
43+
public bool UpdateAvailable { get; set; }
44+
45+
/// <summary>
46+
/// The latest version available (null if check failed or no update)
47+
/// </summary>
48+
public string LatestVersion { get; set; }
49+
50+
/// <summary>
51+
/// Whether the check was successful (false if network error, etc.)
52+
/// </summary>
53+
public bool CheckSucceeded { get; set; }
54+
55+
/// <summary>
56+
/// Optional message about the check result
57+
/// </summary>
58+
public string Message { get; set; }
59+
}
60+
}

MCPForUnity/Editor/Services/IPackageUpdateService.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.

0 commit comments

Comments
 (0)