Add Codex to autoconfig options (#288)
* feat: add Codex CLI client support with config.toml handling * feat: add config helpers for managing Codex and MCP server configurations * feat: add TOML array parsing support for multi-line and trailing comma formats * fix: handle TOML inline comments in section headers during parsing * fix: strip TOML comments before processing section headers * fix: improve JSON parsing to handle escaped single quotes in config strings * Use Tommy for TOML parsing It's a single file and OSS, easy to integrate into Unity * fix: patched Tommy’s literal-string handling so doubled single quotes inside literal strings are treated as embedded apostrophes instead of prematurely ending the value * Don't overwrite MCP configs while testing Seeing random JSON in my codex config was pretty annoying * PR Feedback * Keep Tommy compatible with Unity 2021 * Re-include Tommy's license Probably a good habit to keep all 3rd party licenses and copyrights, even if they're also MIT licensesmain
parent
da91f256a2
commit
549ac1eb0c
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d539787bf8f6a426e94bfffb32a36d4f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
using NUnit.Framework;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Helpers
|
||||
{
|
||||
public class CodexConfigHelperTests
|
||||
{
|
||||
[Test]
|
||||
public void TryParseCodexServer_SingleLineArgs_ParsesSuccessfully()
|
||||
{
|
||||
string toml = string.Join("\n", new[]
|
||||
{
|
||||
"[mcp_servers.unityMCP]",
|
||||
"command = \"uv\"",
|
||||
"args = [\"run\", \"--directory\", \"/abs/path\", \"server.py\"]"
|
||||
});
|
||||
|
||||
bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args);
|
||||
|
||||
Assert.IsTrue(result, "Parser should detect server definition");
|
||||
Assert.AreEqual("uv", command);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseCodexServer_MultiLineArgsWithTrailingComma_ParsesSuccessfully()
|
||||
{
|
||||
string toml = string.Join("\n", new[]
|
||||
{
|
||||
"[mcp_servers.unityMCP]",
|
||||
"command = \"uv\"",
|
||||
"args = [",
|
||||
" \"run\",",
|
||||
" \"--directory\",",
|
||||
" \"/abs/path\",",
|
||||
" \"server.py\",",
|
||||
"]"
|
||||
});
|
||||
|
||||
bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args);
|
||||
|
||||
Assert.IsTrue(result, "Parser should handle multi-line arrays with trailing comma");
|
||||
Assert.AreEqual("uv", command);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseCodexServer_MultiLineArgsWithComments_IgnoresComments()
|
||||
{
|
||||
string toml = string.Join("\n", new[]
|
||||
{
|
||||
"[mcp_servers.unityMCP]",
|
||||
"command = \"uv\"",
|
||||
"args = [",
|
||||
" \"run\", # launch command",
|
||||
" \"--directory\",",
|
||||
" \"/abs/path\",",
|
||||
" \"server.py\"",
|
||||
"]"
|
||||
});
|
||||
|
||||
bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args);
|
||||
|
||||
Assert.IsTrue(result, "Parser should tolerate comments within the array block");
|
||||
Assert.AreEqual("uv", command);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseCodexServer_HeaderWithComment_StillDetected()
|
||||
{
|
||||
string toml = string.Join("\n", new[]
|
||||
{
|
||||
"[mcp_servers.unityMCP] # annotated header",
|
||||
"command = \"uv\"",
|
||||
"args = [\"run\", \"--directory\", \"/abs/path\", \"server.py\"]"
|
||||
});
|
||||
|
||||
bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args);
|
||||
|
||||
Assert.IsTrue(result, "Parser should recognize section headers even with inline comments");
|
||||
Assert.AreEqual("uv", command);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/abs/path", "server.py" }, args);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseCodexServer_SingleQuotedArgsWithApostrophes_ParsesSuccessfully()
|
||||
{
|
||||
string toml = string.Join("\n", new[]
|
||||
{
|
||||
"[mcp_servers.unityMCP]",
|
||||
"command = 'uv'",
|
||||
"args = ['run', '--directory', '/Users/O''Connor/codex', 'server.py']"
|
||||
});
|
||||
|
||||
bool result = CodexConfigHelper.TryParseCodexServer(toml, out string command, out string[] args);
|
||||
|
||||
Assert.IsTrue(result, "Parser should accept single-quoted arrays with escaped apostrophes");
|
||||
Assert.AreEqual("uv", command);
|
||||
CollectionAssert.AreEqual(new[] { "run", "--directory", "/Users/O'Connor/codex", "server.py" }, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 013424dea29744a98b3dc01618f4e95e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -46,6 +46,8 @@ namespace MCPForUnityTests.Editor.Windows
|
|||
EditorPrefs.SetString("MCPForUnity.ServerSrc", _serverSrcDir);
|
||||
// Ensure no lock is enabled
|
||||
EditorPrefs.SetBool("MCPForUnity.LockCursorConfig", false);
|
||||
// Disable auto-registration to avoid hitting user configs during tests
|
||||
EditorPrefs.SetBool("MCPForUnity.AutoRegisterEnabled", false);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
@ -54,6 +56,7 @@ namespace MCPForUnityTests.Editor.Windows
|
|||
// Clean up editor preferences set during SetUp
|
||||
EditorPrefs.DeleteKey("MCPForUnity.ServerSrc");
|
||||
EditorPrefs.DeleteKey("MCPForUnity.LockCursorConfig");
|
||||
EditorPrefs.DeleteKey("MCPForUnity.AutoRegisterEnabled");
|
||||
|
||||
// Remove temp files
|
||||
try { if (Directory.Exists(_tempRoot)) Directory.Delete(_tempRoot, true); } catch { }
|
||||
|
|
|
|||
|
|
@ -159,6 +159,28 @@ namespace MCPForUnity.Editor.Data
|
|||
mcpType = McpTypes.Kiro,
|
||||
configStatus = "Not Configured",
|
||||
},
|
||||
// 4) Codex CLI
|
||||
new()
|
||||
{
|
||||
name = "Codex CLI",
|
||||
windowsConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".codex",
|
||||
"config.toml"
|
||||
),
|
||||
macConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".codex",
|
||||
"config.toml"
|
||||
),
|
||||
linuxConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".codex",
|
||||
"config.toml"
|
||||
),
|
||||
mcpType = McpTypes.Codex,
|
||||
configStatus = "Not Configured",
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize status enums after construction
|
||||
|
|
@ -174,4 +196,3 @@ namespace MCPForUnity.Editor.Data
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c11944bcfb9ec4576bab52874b7df584
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea652131dcdaa44ca8cb35cd1191be3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using MCPForUnity.External.Tommy;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MCPForUnity.Editor.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Codex CLI specific configuration helpers. Handles TOML snippet
|
||||
/// generation and lightweight parsing so Codex can join the auto-setup
|
||||
/// flow alongside JSON-based clients.
|
||||
/// </summary>
|
||||
public static class CodexConfigHelper
|
||||
{
|
||||
public static bool IsCodexConfigured(string pythonDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
string basePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
if (string.IsNullOrEmpty(basePath)) return false;
|
||||
|
||||
string configPath = Path.Combine(basePath, ".codex", "config.toml");
|
||||
if (!File.Exists(configPath)) return false;
|
||||
|
||||
string toml = File.ReadAllText(configPath);
|
||||
if (!TryParseCodexServer(toml, out _, out var args)) return false;
|
||||
|
||||
string dir = McpConfigFileHelper.ExtractDirectoryArg(args);
|
||||
if (string.IsNullOrEmpty(dir)) return false;
|
||||
|
||||
return McpConfigFileHelper.PathsEqual(dir, pythonDir);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string BuildCodexServerBlock(string uvPath, string serverSrc)
|
||||
{
|
||||
string argsArray = FormatTomlStringArray(new[] { "run", "--directory", serverSrc, "server.py" });
|
||||
return $"[mcp_servers.unityMCP]{Environment.NewLine}" +
|
||||
$"command = \"{EscapeTomlString(uvPath)}\"{Environment.NewLine}" +
|
||||
$"args = {argsArray}";
|
||||
}
|
||||
|
||||
public static string UpsertCodexServerBlock(string existingToml, string newBlock)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(existingToml))
|
||||
{
|
||||
return newBlock.TrimEnd() + Environment.NewLine;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
using StringReader reader = new StringReader(existingToml);
|
||||
string line;
|
||||
bool inTarget = false;
|
||||
bool replaced = false;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
string trimmed = line.Trim();
|
||||
bool isSection = trimmed.StartsWith("[") && trimmed.EndsWith("]") && !trimmed.StartsWith("[[");
|
||||
if (isSection)
|
||||
{
|
||||
bool isTarget = string.Equals(trimmed, "[mcp_servers.unityMCP]", StringComparison.OrdinalIgnoreCase);
|
||||
if (isTarget)
|
||||
{
|
||||
if (!replaced)
|
||||
{
|
||||
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
|
||||
sb.AppendLine(newBlock.TrimEnd());
|
||||
replaced = true;
|
||||
}
|
||||
inTarget = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inTarget)
|
||||
{
|
||||
inTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (inTarget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine(line);
|
||||
}
|
||||
|
||||
if (!replaced)
|
||||
{
|
||||
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
|
||||
sb.AppendLine(newBlock.TrimEnd());
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd() + Environment.NewLine;
|
||||
}
|
||||
|
||||
public static bool TryParseCodexServer(string toml, out string command, out string[] args)
|
||||
{
|
||||
command = null;
|
||||
args = null;
|
||||
if (string.IsNullOrWhiteSpace(toml)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new StringReader(toml);
|
||||
TomlTable root = TOML.Parse(reader);
|
||||
if (root == null) return false;
|
||||
|
||||
if (!TryGetTable(root, "mcp_servers", out var servers)
|
||||
&& !TryGetTable(root, "mcpServers", out servers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetTable(servers, "unityMCP", out var unity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
command = GetTomlString(unity, "command");
|
||||
args = GetTomlStringArray(unity, "args");
|
||||
|
||||
return !string.IsNullOrEmpty(command) && args != null;
|
||||
}
|
||||
catch (TomlParseException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (TomlSyntaxException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetTable(TomlTable parent, string key, out TomlTable table)
|
||||
{
|
||||
table = null;
|
||||
if (parent == null) return false;
|
||||
|
||||
if (parent.TryGetNode(key, out var node))
|
||||
{
|
||||
if (node is TomlTable tbl)
|
||||
{
|
||||
table = tbl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node is TomlArray array)
|
||||
{
|
||||
var firstTable = array.Children.OfType<TomlTable>().FirstOrDefault();
|
||||
if (firstTable != null)
|
||||
{
|
||||
table = firstTable;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetTomlString(TomlTable table, string key)
|
||||
{
|
||||
if (table != null && table.TryGetNode(key, out var node))
|
||||
{
|
||||
if (node is TomlString str) return str.Value;
|
||||
if (node.HasValue) return node.ToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] GetTomlStringArray(TomlTable table, string key)
|
||||
{
|
||||
if (table == null) return null;
|
||||
if (!table.TryGetNode(key, out var node)) return null;
|
||||
|
||||
if (node is TomlArray array)
|
||||
{
|
||||
List<string> values = new List<string>();
|
||||
foreach (TomlNode element in array.Children)
|
||||
{
|
||||
if (element is TomlString str)
|
||||
{
|
||||
values.Add(str.Value);
|
||||
}
|
||||
else if (element.HasValue)
|
||||
{
|
||||
values.Add(element.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return values.Count > 0 ? values.ToArray() : Array.Empty<string>();
|
||||
}
|
||||
|
||||
if (node is TomlString single)
|
||||
{
|
||||
return new[] { single.Value };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string FormatTomlStringArray(IEnumerable<string> values)
|
||||
{
|
||||
if (values == null) return "[]";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
bool first = true;
|
||||
foreach (string value in values)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append('"').Append(EscapeTomlString(value ?? string.Empty)).Append('"');
|
||||
first = false;
|
||||
}
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string EscapeTomlString(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return string.Empty;
|
||||
return value
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b3e68082ffc0b4cd39d3747673a4cc22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
|
||||
namespace MCPForUnity.Editor.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared helpers for reading and writing MCP client configuration files.
|
||||
/// Consolidates file atomics and server directory resolution so the editor
|
||||
/// window can focus on UI concerns only.
|
||||
/// </summary>
|
||||
public static class McpConfigFileHelper
|
||||
{
|
||||
public static string ExtractDirectoryArg(string[] args)
|
||||
{
|
||||
if (args == null) return null;
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return args[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool PathsEqual(string a, string b)
|
||||
{
|
||||
if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
|
||||
try
|
||||
{
|
||||
string na = Path.GetFullPath(a.Trim());
|
||||
string nb = Path.GetFullPath(b.Trim());
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
return string.Equals(na, nb, StringComparison.Ordinal);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the server directory to use for MCP tools, preferring
|
||||
/// existing config values and falling back to installed/embedded copies.
|
||||
/// </summary>
|
||||
public static string ResolveServerDirectory(string pythonDir, string[] existingArgs)
|
||||
{
|
||||
string serverSrc = ExtractDirectoryArg(existingArgs);
|
||||
bool serverValid = !string.IsNullOrEmpty(serverSrc)
|
||||
&& File.Exists(Path.Combine(serverSrc, "server.py"));
|
||||
if (!serverValid)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pythonDir)
|
||||
&& File.Exists(Path.Combine(pythonDir, "server.py")))
|
||||
{
|
||||
serverSrc = pythonDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
serverSrc = ResolveServerSource();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !string.IsNullOrEmpty(serverSrc))
|
||||
{
|
||||
string norm = serverSrc.Replace('\\', '/');
|
||||
int idx = norm.IndexOf("/.local/share/UnityMCP/", StringComparison.Ordinal);
|
||||
if (idx >= 0)
|
||||
{
|
||||
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||
string suffix = norm.Substring(idx + "/.local/share/".Length);
|
||||
serverSrc = Path.Combine(home, "Library", "Application Support", suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures and fall back to the original path.
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
&& !string.IsNullOrEmpty(serverSrc)
|
||||
&& serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
&& !EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false))
|
||||
{
|
||||
serverSrc = ServerInstaller.GetServerPath();
|
||||
}
|
||||
|
||||
return serverSrc;
|
||||
}
|
||||
|
||||
public static void WriteAtomicFile(string path, string contents)
|
||||
{
|
||||
string tmp = path + ".tmp";
|
||||
string backup = path + ".backup";
|
||||
bool writeDone = false;
|
||||
try
|
||||
{
|
||||
File.WriteAllText(tmp, contents, new UTF8Encoding(false));
|
||||
try
|
||||
{
|
||||
File.Replace(tmp, path, backup);
|
||||
writeDone = true;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
File.Move(tmp, path);
|
||||
writeDone = true;
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(backup)) File.Delete(backup);
|
||||
}
|
||||
catch { }
|
||||
File.Move(path, backup);
|
||||
}
|
||||
File.Move(tmp, path);
|
||||
writeDone = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!writeDone && File.Exists(backup))
|
||||
{
|
||||
try { File.Copy(backup, path, true); } catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
throw new Exception($"Failed to write config file '{path}': {ex.Message}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (File.Exists(tmp)) File.Delete(tmp); } catch { }
|
||||
try { if (writeDone && File.Exists(backup)) File.Delete(backup); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static string ResolveServerSource()
|
||||
{
|
||||
try
|
||||
{
|
||||
string remembered = EditorPrefs.GetString("MCPForUnity.ServerSrc", string.Empty);
|
||||
if (!string.IsNullOrEmpty(remembered)
|
||||
&& File.Exists(Path.Combine(remembered, "server.py")))
|
||||
{
|
||||
return remembered;
|
||||
}
|
||||
|
||||
ServerInstaller.EnsureServerInstalled();
|
||||
string installed = ServerInstaller.GetServerPath();
|
||||
if (File.Exists(Path.Combine(installed, "server.py")))
|
||||
{
|
||||
return installed;
|
||||
}
|
||||
|
||||
bool useEmbedded = EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false);
|
||||
if (useEmbedded
|
||||
&& ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)
|
||||
&& File.Exists(Path.Combine(embedded, "server.py")))
|
||||
{
|
||||
return embedded;
|
||||
}
|
||||
|
||||
return installed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ServerInstaller.GetServerPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f69ad468942b74c0ea24e3e8e5f21a4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -4,10 +4,10 @@ namespace MCPForUnity.Editor.Models
|
|||
{
|
||||
ClaudeCode,
|
||||
ClaudeDesktop,
|
||||
Codex,
|
||||
Cursor,
|
||||
Kiro,
|
||||
VSCode,
|
||||
Windsurf,
|
||||
Kiro,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -568,8 +568,9 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
else
|
||||
{
|
||||
// For Cursor/others, skip if already configured
|
||||
if (!IsCursorConfigured(pythonDir))
|
||||
CheckMcpConfiguration(client);
|
||||
bool alreadyConfigured = client.status == McpStatus.Configured;
|
||||
if (!alreadyConfigured)
|
||||
{
|
||||
ConfigureMcpClient(client);
|
||||
anyRegistered = true;
|
||||
|
|
@ -581,7 +582,10 @@ namespace MCPForUnity.Editor.Windows
|
|||
MCPForUnity.Editor.Helpers.McpLog.Warn($"Auto-setup client '{client.name}' failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
lastClientRegisteredOk = anyRegistered || IsCursorConfigured(pythonDir) || IsClaudeConfigured();
|
||||
lastClientRegisteredOk = anyRegistered
|
||||
|| IsCursorConfigured(pythonDir)
|
||||
|| CodexConfigHelper.IsCodexConfigured(pythonDir)
|
||||
|| IsClaudeConfigured();
|
||||
}
|
||||
|
||||
// Ensure the bridge is listening and has a fresh saved port
|
||||
|
|
@ -658,7 +662,9 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
else
|
||||
{
|
||||
if (!IsCursorConfigured(pythonDir))
|
||||
CheckMcpConfiguration(client);
|
||||
bool alreadyConfigured = client.status == McpStatus.Configured;
|
||||
if (!alreadyConfigured)
|
||||
{
|
||||
ConfigureMcpClient(client);
|
||||
anyRegistered = true;
|
||||
|
|
@ -670,7 +676,10 @@ namespace MCPForUnity.Editor.Windows
|
|||
UnityEngine.Debug.LogWarning($"Setup client '{client.name}' failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
lastClientRegisteredOk = anyRegistered || IsCursorConfigured(pythonDir) || IsClaudeConfigured();
|
||||
lastClientRegisteredOk = anyRegistered
|
||||
|| IsCursorConfigured(pythonDir)
|
||||
|| CodexConfigHelper.IsCodexConfigured(pythonDir)
|
||||
|| IsClaudeConfigured();
|
||||
|
||||
// Restart/ensure bridge
|
||||
MCPForUnityBridge.StartAutoConnect();
|
||||
|
|
@ -686,11 +695,11 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
}
|
||||
|
||||
private static bool IsCursorConfigured(string pythonDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
private static bool IsCursorConfigured(string pythonDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".cursor", "mcp.json")
|
||||
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
|
|
@ -708,24 +717,9 @@ namespace MCPForUnity.Editor.Windows
|
|||
string[] strArgs = ((System.Collections.Generic.IEnumerable<object>)args)
|
||||
.Select(x => x?.ToString() ?? string.Empty)
|
||||
.ToArray();
|
||||
string dir = ExtractDirectoryArg(strArgs);
|
||||
string dir = McpConfigFileHelper.ExtractDirectoryArg(strArgs);
|
||||
if (string.IsNullOrEmpty(dir)) return false;
|
||||
return PathsEqual(dir, pythonDir);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
private static bool PathsEqual(string a, string b)
|
||||
{
|
||||
if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
|
||||
try
|
||||
{
|
||||
string na = System.IO.Path.GetFullPath(a.Trim());
|
||||
string nb = System.IO.Path.GetFullPath(b.Trim());
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||
return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase);
|
||||
// Default to ordinal on Unix; optionally detect FS case-sensitivity at runtime if needed
|
||||
return string.Equals(na, nb, StringComparison.Ordinal);
|
||||
return McpConfigFileHelper.PathsEqual(dir, pythonDir);
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
|
@ -1136,19 +1130,6 @@ namespace MCPForUnity.Editor.Windows
|
|||
catch { return false; }
|
||||
}
|
||||
|
||||
private static string ExtractDirectoryArg(string[] args)
|
||||
{
|
||||
if (args == null) return null;
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return args[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ArgsEqual(string[] a, string[] b)
|
||||
{
|
||||
if (a == null || b == null) return a == b;
|
||||
|
|
@ -1236,48 +1217,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
catch { }
|
||||
if (uvPath == null) return "UV package manager not found. Please install UV first.";
|
||||
string serverSrc = ExtractDirectoryArg(existingArgs);
|
||||
bool serverValid = !string.IsNullOrEmpty(serverSrc)
|
||||
&& System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py"));
|
||||
if (!serverValid)
|
||||
{
|
||||
// Prefer the provided pythonDir if valid; fall back to resolver
|
||||
if (!string.IsNullOrEmpty(pythonDir) && System.IO.File.Exists(System.IO.Path.Combine(pythonDir, "server.py")))
|
||||
{
|
||||
serverSrc = pythonDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
serverSrc = ResolveServerSrc();
|
||||
}
|
||||
}
|
||||
|
||||
// macOS normalization: map XDG-style ~/.local/share to canonical Application Support
|
||||
try
|
||||
{
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)
|
||||
&& !string.IsNullOrEmpty(serverSrc))
|
||||
{
|
||||
string norm = serverSrc.Replace('\\', '/');
|
||||
int idx = norm.IndexOf("/.local/share/UnityMCP/", StringComparison.Ordinal);
|
||||
if (idx >= 0)
|
||||
{
|
||||
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
|
||||
string suffix = norm.Substring(idx + "/.local/share/".Length); // UnityMCP/...
|
||||
serverSrc = System.IO.Path.Combine(home, "Library", "Application Support", suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Hard-block PackageCache on Windows unless dev override is set
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
&& !string.IsNullOrEmpty(serverSrc)
|
||||
&& serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
&& !UnityEditor.EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false))
|
||||
{
|
||||
serverSrc = ServerInstaller.GetServerPath();
|
||||
}
|
||||
string serverSrc = McpConfigFileHelper.ResolveServerDirectory(pythonDir, existingArgs);
|
||||
|
||||
// 2) Canonical args order
|
||||
var newArgs = new[] { "run", "--directory", serverSrc, "server.py" };
|
||||
|
|
@ -1301,60 +1241,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
|
||||
string mergedJson = JsonConvert.SerializeObject(existingRoot, jsonSettings);
|
||||
|
||||
// Robust atomic write without redundant backup or race on existence
|
||||
string tmp = configPath + ".tmp";
|
||||
string backup = configPath + ".backup";
|
||||
bool writeDone = false;
|
||||
try
|
||||
{
|
||||
// Write to temp file first (in same directory for atomicity)
|
||||
System.IO.File.WriteAllText(tmp, mergedJson, new System.Text.UTF8Encoding(false));
|
||||
|
||||
try
|
||||
{
|
||||
// Try atomic replace; creates 'backup' only on success (platform-dependent)
|
||||
System.IO.File.Replace(tmp, configPath, backup);
|
||||
writeDone = true;
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// Destination didn't exist; fall back to move
|
||||
System.IO.File.Move(tmp, configPath);
|
||||
writeDone = true;
|
||||
}
|
||||
catch (System.PlatformNotSupportedException)
|
||||
{
|
||||
// Fallback: rename existing to backup, then move tmp into place
|
||||
if (System.IO.File.Exists(configPath))
|
||||
{
|
||||
try { if (System.IO.File.Exists(backup)) System.IO.File.Delete(backup); } catch { }
|
||||
System.IO.File.Move(configPath, backup);
|
||||
}
|
||||
System.IO.File.Move(tmp, configPath);
|
||||
writeDone = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
// If write did not complete, attempt restore from backup without deleting current file first
|
||||
try
|
||||
{
|
||||
if (!writeDone && System.IO.File.Exists(backup))
|
||||
{
|
||||
try { System.IO.File.Copy(backup, configPath, true); } catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
throw new Exception($"Failed to write config file '{configPath}': {ex.Message}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Best-effort cleanup of temp
|
||||
try { if (System.IO.File.Exists(tmp)) System.IO.File.Delete(tmp); } catch { }
|
||||
// Only remove backup after a confirmed successful write
|
||||
try { if (writeDone && System.IO.File.Exists(backup)) System.IO.File.Delete(backup); } catch { }
|
||||
}
|
||||
McpConfigFileHelper.WriteAtomicFile(configPath, mergedJson);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -1377,54 +1264,27 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
|
||||
// New method to show manual instructions without changing status
|
||||
private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient)
|
||||
{
|
||||
// Get the Python directory path using Package Manager API
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
// Build manual JSON centrally using the shared builder
|
||||
string uvPathForManual = FindUvPath();
|
||||
if (uvPathForManual == null)
|
||||
{
|
||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot generate manual configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
string manualConfigJson = ConfigJsonBuilder.BuildManualConfigJson(uvPathForManual, pythonDir, mcpClient);
|
||||
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
|
||||
}
|
||||
|
||||
private static string ResolveServerSrc()
|
||||
private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient)
|
||||
{
|
||||
try
|
||||
// Get the Python directory path using Package Manager API
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
// Build manual JSON centrally using the shared builder
|
||||
string uvPathForManual = FindUvPath();
|
||||
if (uvPathForManual == null)
|
||||
{
|
||||
string remembered = UnityEditor.EditorPrefs.GetString("MCPForUnity.ServerSrc", string.Empty);
|
||||
if (!string.IsNullOrEmpty(remembered) && File.Exists(Path.Combine(remembered, "server.py")))
|
||||
{
|
||||
return remembered;
|
||||
}
|
||||
|
||||
ServerInstaller.EnsureServerInstalled();
|
||||
string installed = ServerInstaller.GetServerPath();
|
||||
if (File.Exists(Path.Combine(installed, "server.py")))
|
||||
{
|
||||
return installed;
|
||||
}
|
||||
|
||||
bool useEmbedded = UnityEditor.EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false);
|
||||
if (useEmbedded && ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)
|
||||
&& File.Exists(Path.Combine(embedded, "server.py")))
|
||||
{
|
||||
return embedded;
|
||||
}
|
||||
|
||||
return installed;
|
||||
UnityEngine.Debug.LogError("UV package manager not found. Cannot generate manual configuration.");
|
||||
return;
|
||||
}
|
||||
catch { return ServerInstaller.GetServerPath(); }
|
||||
|
||||
string manualConfig = mcpClient?.mcpType == McpTypes.Codex
|
||||
? CodexConfigHelper.BuildCodexServerBlock(uvPathForManual, McpConfigFileHelper.ResolveServerDirectory(pythonDir, null)).TrimEnd() + Environment.NewLine
|
||||
: ConfigJsonBuilder.BuildManualConfigJson(uvPathForManual, pythonDir, mcpClient);
|
||||
ManualConfigEditorWindow.ShowWindow(configPath, manualConfig, mcpClient);
|
||||
}
|
||||
|
||||
private string FindPackagePythonDirectory()
|
||||
private string FindPackagePythonDirectory()
|
||||
{
|
||||
string pythonDir = ResolveServerSrc();
|
||||
string pythonDir = McpConfigFileHelper.ResolveServerSource();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -1508,12 +1368,12 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
}
|
||||
|
||||
private string ConfigureMcpClient(McpClient mcpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine the config file path based on OS
|
||||
string configPath;
|
||||
private string ConfigureMcpClient(McpClient mcpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Determine the config file path based on OS
|
||||
string configPath;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
|
|
@ -1541,21 +1401,23 @@ namespace MCPForUnity.Editor.Windows
|
|||
// Create directory if it doesn't exist
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(configPath));
|
||||
|
||||
// Find the server.py file location using the same logic as FindPackagePythonDirectory
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
// Find the server.py file location using the same logic as FindPackagePythonDirectory
|
||||
string pythonDir = FindPackagePythonDirectory();
|
||||
|
||||
if (pythonDir == null || !File.Exists(Path.Combine(pythonDir, "server.py")))
|
||||
{
|
||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||
return "Manual Configuration Required";
|
||||
}
|
||||
if (pythonDir == null || !File.Exists(Path.Combine(pythonDir, "server.py")))
|
||||
{
|
||||
ShowManualInstructionsWindow(configPath, mcpClient);
|
||||
return "Manual Configuration Required";
|
||||
}
|
||||
|
||||
string result = WriteToConfig(pythonDir, configPath, mcpClient);
|
||||
string result = mcpClient.mcpType == McpTypes.Codex
|
||||
? ConfigureCodexClient(pythonDir, configPath, mcpClient)
|
||||
: WriteToConfig(pythonDir, configPath, mcpClient);
|
||||
|
||||
// Update the client status after successful configuration
|
||||
if (result == "Configured successfully")
|
||||
{
|
||||
mcpClient.SetStatus(McpStatus.Configured);
|
||||
// Update the client status after successful configuration
|
||||
if (result == "Configured successfully")
|
||||
{
|
||||
mcpClient.SetStatus(McpStatus.Configured);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -1588,8 +1450,82 @@ namespace MCPForUnity.Editor.Windows
|
|||
$"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}"
|
||||
);
|
||||
return $"Failed to configure {mcpClient.name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ConfigureCodexClient(string pythonDir, string configPath, McpClient mcpClient)
|
||||
{
|
||||
try { if (EditorPrefs.GetBool("MCPForUnity.LockCursorConfig", false)) return "Skipped (locked)"; } catch { }
|
||||
|
||||
string existingToml = string.Empty;
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
existingToml = File.ReadAllText(configPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (debugLogsEnabled)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"UnityMCP: Failed to read Codex config '{configPath}': {e.Message}");
|
||||
}
|
||||
existingToml = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
string existingCommand = null;
|
||||
string[] existingArgs = null;
|
||||
if (!string.IsNullOrWhiteSpace(existingToml))
|
||||
{
|
||||
CodexConfigHelper.TryParseCodexServer(existingToml, out existingCommand, out existingArgs);
|
||||
}
|
||||
|
||||
string uvPath = ServerInstaller.FindUvPath();
|
||||
try
|
||||
{
|
||||
var name = Path.GetFileName((existingCommand ?? string.Empty).Trim()).ToLowerInvariant();
|
||||
if ((name == "uv" || name == "uv.exe") && ValidateUvBinarySafe(existingCommand))
|
||||
{
|
||||
uvPath = existingCommand;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (uvPath == null)
|
||||
{
|
||||
return "UV package manager not found. Please install UV first.";
|
||||
}
|
||||
|
||||
string serverSrc = McpConfigFileHelper.ResolveServerDirectory(pythonDir, existingArgs);
|
||||
var newArgs = new[] { "run", "--directory", serverSrc, "server.py" };
|
||||
|
||||
bool changed = true;
|
||||
if (!string.IsNullOrEmpty(existingCommand) && existingArgs != null)
|
||||
{
|
||||
changed = !string.Equals(existingCommand, uvPath, StringComparison.Ordinal)
|
||||
|| !ArgsEqual(existingArgs, newArgs);
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
{
|
||||
return "Configured successfully";
|
||||
}
|
||||
|
||||
string codexBlock = CodexConfigHelper.BuildCodexServerBlock(uvPath, serverSrc);
|
||||
string updatedToml = CodexConfigHelper.UpsertCodexServerBlock(existingToml, codexBlock);
|
||||
|
||||
McpConfigFileHelper.WriteAtomicFile(configPath, updatedToml);
|
||||
|
||||
try
|
||||
{
|
||||
if (IsValidUv(uvPath)) EditorPrefs.SetString("MCPForUnity.UvPath", uvPath);
|
||||
EditorPrefs.SetString("MCPForUnity.ServerSrc", serverSrc);
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "Configured successfully";
|
||||
}
|
||||
|
||||
private void ShowCursorManualConfigurationInstructions(
|
||||
string configPath,
|
||||
|
|
@ -1721,28 +1657,36 @@ namespace MCPForUnity.Editor.Windows
|
|||
string[] args = null;
|
||||
bool configExists = false;
|
||||
|
||||
switch (mcpClient.mcpType)
|
||||
{
|
||||
case McpTypes.VSCode:
|
||||
dynamic config = JsonConvert.DeserializeObject(configJson);
|
||||
switch (mcpClient.mcpType)
|
||||
{
|
||||
case McpTypes.VSCode:
|
||||
dynamic config = JsonConvert.DeserializeObject(configJson);
|
||||
|
||||
// New schema: top-level servers
|
||||
if (config?.servers?.unityMCP != null)
|
||||
{
|
||||
args = config.servers.unityMCP.args.ToObject<string[]>();
|
||||
configExists = true;
|
||||
}
|
||||
// Back-compat: legacy mcp.servers
|
||||
else if (config?.mcp?.servers?.unityMCP != null)
|
||||
{
|
||||
args = config.mcp.servers.unityMCP.args.ToObject<string[]>();
|
||||
configExists = true;
|
||||
}
|
||||
break;
|
||||
// New schema: top-level servers
|
||||
if (config?.servers?.unityMCP != null)
|
||||
{
|
||||
args = config.servers.unityMCP.args.ToObject<string[]>();
|
||||
configExists = true;
|
||||
}
|
||||
// Back-compat: legacy mcp.servers
|
||||
else if (config?.mcp?.servers?.unityMCP != null)
|
||||
{
|
||||
args = config.mcp.servers.unityMCP.args.ToObject<string[]>();
|
||||
configExists = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Standard MCP configuration check for Claude Desktop, Cursor, etc.
|
||||
McpConfig standardConfig = JsonConvert.DeserializeObject<McpConfig>(configJson);
|
||||
case McpTypes.Codex:
|
||||
if (CodexConfigHelper.TryParseCodexServer(configJson, out _, out var codexArgs))
|
||||
{
|
||||
args = codexArgs;
|
||||
configExists = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Standard MCP configuration check for Claude Desktop, Cursor, etc.
|
||||
McpConfig standardConfig = JsonConvert.DeserializeObject<McpConfig>(configJson);
|
||||
|
||||
if (standardConfig?.mcpServers?.unityMCP != null)
|
||||
{
|
||||
|
|
@ -1755,8 +1699,8 @@ namespace MCPForUnity.Editor.Windows
|
|||
// Common logic for checking configuration status
|
||||
if (configExists)
|
||||
{
|
||||
string configuredDir = ExtractDirectoryArg(args);
|
||||
bool matches = !string.IsNullOrEmpty(configuredDir) && PathsEqual(configuredDir, pythonDir);
|
||||
string configuredDir = McpConfigFileHelper.ExtractDirectoryArg(args);
|
||||
bool matches = !string.IsNullOrEmpty(configuredDir) && McpConfigFileHelper.PathsEqual(configuredDir, pythonDir);
|
||||
if (matches)
|
||||
{
|
||||
mcpClient.SetStatus(McpStatus.Configured);
|
||||
|
|
@ -1766,7 +1710,9 @@ namespace MCPForUnity.Editor.Windows
|
|||
// Attempt auto-rewrite once if the package path changed
|
||||
try
|
||||
{
|
||||
string rewriteResult = WriteToConfig(pythonDir, configPath, mcpClient);
|
||||
string rewriteResult = mcpClient.mcpType == McpTypes.Codex
|
||||
? ConfigureCodexClient(pythonDir, configPath, mcpClient)
|
||||
: WriteToConfig(pythonDir, configPath, mcpClient);
|
||||
if (rewriteResult == "Configured successfully")
|
||||
{
|
||||
if (debugLogsEnabled)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,13 @@ namespace MCPForUnity.Editor.Windows
|
|||
instructionStyle
|
||||
);
|
||||
}
|
||||
else if (mcpClient?.mcpType == McpTypes.Codex)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
" a) Running `codex config edit` in a terminal",
|
||||
instructionStyle
|
||||
);
|
||||
}
|
||||
EditorGUILayout.LabelField(" OR", instructionStyle);
|
||||
EditorGUILayout.LabelField(
|
||||
" b) Opening the configuration file at:",
|
||||
|
|
@ -201,10 +208,10 @@ namespace MCPForUnity.Editor.Windows
|
|||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
"2. Paste the following JSON configuration:",
|
||||
instructionStyle
|
||||
);
|
||||
string configLabel = mcpClient?.mcpType == McpTypes.Codex
|
||||
? "2. Paste the following TOML configuration:"
|
||||
: "2. Paste the following JSON configuration:";
|
||||
EditorGUILayout.LabelField(configLabel, instructionStyle);
|
||||
|
||||
// JSON section with improved styling
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
|
|
|
|||
Loading…
Reference in New Issue