Skip to content

Commit 3d9860e

Browse files
committed
Update to support Codex CLI
Fix the JSON installation on Codex in Wizard; Adding the lines to support Codex CLI on windows temporarily, thanks to openai/codex#4180
1 parent 90911aa commit 3d9860e

File tree

2 files changed

+126
-10
lines changed

2 files changed

+126
-10
lines changed

UnityMcpBridge/Editor/Helpers/CodexConfigHelper.cs

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,84 @@ public static bool IsCodexConfigured(string pythonDir)
4343
public static string BuildCodexServerBlock(string uvPath, string serverSrc)
4444
{
4545
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}";
46+
47+
var sb = new StringBuilder();
48+
sb.AppendLine("[mcp_servers.unityMCP]");
49+
sb.AppendLine($"command = \"{EscapeTomlString(uvPath)}\"");
50+
sb.AppendLine($"args = {argsArray}");
51+
sb.AppendLine($"startup_timeout_sec = 30");
52+
53+
// Windows-specific environment block to help Codex locate needed paths
54+
try
55+
{
56+
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
57+
{
58+
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty;
59+
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty; // Roaming
60+
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty;
61+
string programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) ?? string.Empty;
62+
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) ?? string.Empty;
63+
string systemDrive = Environment.GetEnvironmentVariable("SystemDrive") ?? (Path.GetPathRoot(userProfile)?.TrimEnd('\\', '/') ?? "C:");
64+
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? Path.Combine(systemDrive + "\\", "Windows");
65+
string comspec = Environment.GetEnvironmentVariable("COMSPEC") ?? Path.Combine(Environment.SystemDirectory ?? (systemRoot + "\\System32"), "cmd.exe");
66+
string homeDrive = Environment.GetEnvironmentVariable("HOMEDRIVE");
67+
string homePath = Environment.GetEnvironmentVariable("HOMEPATH");
68+
if (string.IsNullOrEmpty(homeDrive))
69+
{
70+
homeDrive = systemDrive;
71+
}
72+
if (string.IsNullOrEmpty(homePath) && !string.IsNullOrEmpty(userProfile))
73+
{
74+
// Derive HOMEPATH from USERPROFILE (e.g., C:\\Users\\name -> \\Users\\name)
75+
if (userProfile.StartsWith(homeDrive + "\\", StringComparison.OrdinalIgnoreCase))
76+
{
77+
homePath = userProfile.Substring(homeDrive.Length);
78+
}
79+
else
80+
{
81+
try
82+
{
83+
var root = Path.GetPathRoot(userProfile) ?? string.Empty; // e.g., C:\\
84+
homePath = userProfile.Substring(root.Length - 1); // keep leading backslash
85+
}
86+
catch { homePath = "\\"; }
87+
}
88+
}
89+
90+
string powershell = Path.Combine(Environment.SystemDirectory ?? (systemRoot + "\\System32"), "WindowsPowerShell\\v1.0\\powershell.exe");
91+
string pwsh = Path.Combine(programFiles, "PowerShell\\7\\pwsh.exe");
92+
93+
string tempDir = Path.Combine(localAppData, "Temp");
94+
95+
sb.AppendLine();
96+
sb.AppendLine("[mcp_servers.unityMCP.env]");
97+
sb.AppendLine($"SystemRoot = \"{EscapeTomlString(systemRoot)}\"");
98+
sb.AppendLine($"APPDATA = \"{EscapeTomlString(appData)}\"");
99+
sb.AppendLine($"COMSPEC = \"{EscapeTomlString(comspec)}\"");
100+
sb.AppendLine($"HOMEDRIVE = \"{EscapeTomlString(homeDrive?.TrimEnd('\\') ?? string.Empty)}\"");
101+
sb.AppendLine($"HOMEPATH = \"{EscapeTomlString(homePath ?? string.Empty)}\"");
102+
sb.AppendLine($"LOCALAPPDATA = \"{EscapeTomlString(localAppData)}\"");
103+
sb.AppendLine($"POWERSHELL = \"{EscapeTomlString(powershell)}\"");
104+
sb.AppendLine($"PROGRAMDATA = \"{EscapeTomlString(programData)}\"");
105+
sb.AppendLine($"PROGRAMFILES = \"{EscapeTomlString(programFiles)}\"");
106+
sb.AppendLine($"PWSH = \"{EscapeTomlString(pwsh)}\"");
107+
sb.AppendLine($"SYSTEMDRIVE = \"{EscapeTomlString(systemDrive)}\"");
108+
sb.AppendLine($"SYSTEMROOT = \"{EscapeTomlString(systemRoot)}\"");
109+
sb.AppendLine($"TEMP = \"{EscapeTomlString(tempDir)}\"");
110+
sb.AppendLine($"TMP = \"{EscapeTomlString(tempDir)}\"");
111+
sb.AppendLine($"USERPROFILE = \"{EscapeTomlString(userProfile)}\"");
112+
}
113+
}
114+
catch { /* best effort */ }
115+
116+
return sb.ToString();
49117
}
50118

51119
public static string UpsertCodexServerBlock(string existingToml, string newBlock)
52120
{
53121
if (string.IsNullOrWhiteSpace(existingToml))
54122
{
123+
// Default to snake_case section when creating new files
55124
return newBlock.TrimEnd() + Environment.NewLine;
56125
}
57126

@@ -60,25 +129,62 @@ public static string UpsertCodexServerBlock(string existingToml, string newBlock
60129
string line;
61130
bool inTarget = false;
62131
bool replaced = false;
132+
133+
// Support both TOML section casings and nested subtables (e.g., env)
134+
// Prefer the casing already present in the user's file; fall back to snake_case
135+
bool hasCamelSection = existingToml.IndexOf("[mcpServers.unityMCP]", StringComparison.OrdinalIgnoreCase) >= 0
136+
|| existingToml.IndexOf("[mcpServers.unityMCP.", StringComparison.OrdinalIgnoreCase) >= 0;
137+
bool hasSnakeSection = existingToml.IndexOf("[mcp_servers.unityMCP]", StringComparison.OrdinalIgnoreCase) >= 0
138+
|| existingToml.IndexOf("[mcp_servers.unityMCP.", StringComparison.OrdinalIgnoreCase) >= 0;
139+
bool preferCamel = hasCamelSection || (!hasSnakeSection && existingToml.IndexOf("[mcpServers]", StringComparison.OrdinalIgnoreCase) >= 0);
140+
141+
// Prepare block variants matching the chosen casing, including nested tables
142+
string newBlockCamel = newBlock
143+
.Replace("[mcp_servers.unityMCP.env]", "[mcpServers.unityMCP.env]")
144+
.Replace("[mcp_servers.unityMCP]", "[mcpServers.unityMCP]");
145+
string newBlockEffective = preferCamel ? newBlockCamel : newBlock;
146+
147+
static bool IsSection(string s)
148+
{
149+
string t = s.Trim();
150+
return t.StartsWith("[") && t.EndsWith("]") && !t.StartsWith("[[");
151+
}
152+
153+
static string SectionName(string header)
154+
{
155+
string t = header.Trim();
156+
if (t.StartsWith("[") && t.EndsWith("]")) t = t.Substring(1, t.Length - 2);
157+
return t;
158+
}
159+
160+
bool TargetOrChild(string section)
161+
{
162+
// Compare case-insensitively; accept both snake and camel as the same logical table
163+
string name = SectionName(section);
164+
return name.StartsWith("mcp_servers.unityMCP", StringComparison.OrdinalIgnoreCase)
165+
|| name.StartsWith("mcpServers.unityMCP", StringComparison.OrdinalIgnoreCase);
166+
}
167+
63168
while ((line = reader.ReadLine()) != null)
64169
{
65170
string trimmed = line.Trim();
66-
bool isSection = trimmed.StartsWith("[") && trimmed.EndsWith("]") && !trimmed.StartsWith("[[");
171+
bool isSection = IsSection(trimmed);
67172
if (isSection)
68173
{
69-
bool isTarget = string.Equals(trimmed, "[mcp_servers.unityMCP]", StringComparison.OrdinalIgnoreCase);
70-
if (isTarget)
174+
// If we encounter the target section or any of its nested tables, mark/keep in-target
175+
if (TargetOrChild(trimmed))
71176
{
72177
if (!replaced)
73178
{
74179
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
75-
sb.AppendLine(newBlock.TrimEnd());
180+
sb.AppendLine(newBlockEffective.TrimEnd());
76181
replaced = true;
77182
}
78183
inTarget = true;
79184
continue;
80185
}
81186

187+
// A new unrelated section ends the target region
82188
if (inTarget)
83189
{
84190
inTarget = false;
@@ -96,7 +202,7 @@ public static string UpsertCodexServerBlock(string existingToml, string newBlock
96202
if (!replaced)
97203
{
98204
if (sb.Length > 0 && sb[^1] != '\n') sb.AppendLine();
99-
sb.AppendLine(newBlock.TrimEnd());
205+
sb.AppendLine(newBlockEffective.TrimEnd());
100206
}
101207

102208
return sb.ToString().TrimEnd() + Environment.NewLine;

UnityMcpBridge/Editor/Setup/SetupWizardWindow.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,15 @@ private string PerformClientConfiguration(McpClient client)
626626
}
627627

628628
McpConfigurationHelper.EnsureConfigDirectoryExists(configPath);
629-
return McpConfigurationHelper.WriteMcpConfiguration(pythonDir, configPath, client);
629+
// Use TOML writer for Codex; JSON writer for others
630+
if (client != null && client.mcpType == McpTypes.Codex)
631+
{
632+
return McpConfigurationHelper.ConfigureCodexClient(pythonDir, configPath, client);
633+
}
634+
else
635+
{
636+
return McpConfigurationHelper.WriteMcpConfiguration(pythonDir, configPath, client);
637+
}
630638
}
631639

632640
private void ShowManualSetupInWizard(McpClient client)
@@ -642,7 +650,9 @@ private void ShowManualSetupInWizard(McpClient client)
642650
}
643651

644652
// Build manual configuration using the sophisticated helper logic
645-
string result = McpConfigurationHelper.WriteMcpConfiguration(pythonDir, configPath, client);
653+
string result = (client != null && client.mcpType == McpTypes.Codex)
654+
? McpConfigurationHelper.ConfigureCodexClient(pythonDir, configPath, client)
655+
: McpConfigurationHelper.WriteMcpConfiguration(pythonDir, configPath, client);
646656
string manualConfig;
647657

648658
if (result == "Configured successfully")

0 commit comments

Comments
 (0)