diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
index 7b7f9b8cd..4deb4135a 100644
--- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
+++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
@@ -353,8 +353,13 @@ public override string GetManualSnippet()
/// Codex (TOML) configurator.
public abstract class CodexMcpConfigurator : McpClientConfiguratorBase
{
+ private const string CodexServerName = "unityMCP";
+ private const string LegacyCodexServerName = "UnityMCP";
+
public CodexMcpConfigurator(McpClient client) : base(client) { }
+ public override string GetConfigureActionLabel() => client.status == McpStatus.Configured ? "Unregister" : "Configure";
+
public override string GetConfigPath() => CurrentOsPath();
public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
@@ -403,6 +408,16 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
{
// Match against the active scope's URL
matches = UrlsEqual(url, HttpEndpointUtility.GetMcpRpcUrl());
+ if (matches && ShouldUseTomlForRemoteAuth())
+ {
+ string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);
+ if (!CodexConfigHelper.HasCodexHttpHeader(toml, AuthConstants.ApiKeyHeader, apiKey))
+ {
+ matches = false;
+ hasVersionMismatch = true;
+ mismatchReason = "Remote auth header is missing or stale. Re-configure to update Codex.";
+ }
+ }
}
else if (args != null && args.Length > 0)
{
@@ -451,13 +466,8 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
{
if (attemptAutoRewrite)
{
- string result = McpConfigurationHelper.ConfigureCodexClient(path, client);
- if (result == "Configured successfully")
- {
- client.SetStatus(McpStatus.Configured);
- client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
- return client.status;
- }
+ Register();
+ return client.status;
}
client.SetStatus(McpStatus.VersionMismatch, mismatchReason);
return client.status;
@@ -470,16 +480,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
if (attemptAutoRewrite)
{
- string result = McpConfigurationHelper.ConfigureCodexClient(path, client);
- if (result == "Configured successfully")
- {
- client.SetStatus(McpStatus.Configured);
- client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
- }
- else
- {
- client.SetStatus(McpStatus.IncorrectPath);
- }
+ Register();
}
else
{
@@ -497,17 +498,13 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
public override void Configure()
{
- string path = GetConfigPath();
- McpConfigurationHelper.EnsureConfigDirectoryExists(path);
- string result = McpConfigurationHelper.ConfigureCodexClient(path, client);
- if (result == "Configured successfully")
+ if (client.status == McpStatus.Configured)
{
- client.SetStatus(McpStatus.Configured);
- client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
+ Unregister();
}
else
{
- throw new InvalidOperationException(result);
+ Register();
}
}
@@ -515,8 +512,36 @@ public override string GetManualSnippet()
{
try
{
- string uvx = GetUvxPathOrError();
- return CodexConfigHelper.BuildCodexServerBlock(uvx);
+ if (ShouldUseTomlForRemoteAuth())
+ {
+ return "# Codex CLI does not currently expose an arbitrary HTTP header flag.\n" +
+ "# For remote-hosted servers with X-API-Key auth, add this TOML to ~/.codex/config.toml:\n" +
+ CodexConfigHelper.BuildCodexServerBlock(null);
+ }
+
+ bool useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport;
+ if (useHttpTransport)
+ {
+ string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
+ return "# Register the MCP server with Codex:\n" +
+ $"codex mcp add {CodexServerName} --url {QuoteCliArg(httpUrl)}\n\n" +
+ "# Unregister the MCP server:\n" +
+ $"codex mcp remove {CodexServerName}\n\n" +
+ "# List configured servers:\n" +
+ "codex mcp list";
+ }
+
+ var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
+ string devFlags = AssetPathUtility.GetUvxDevFlags();
+ string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);
+ string envArg = GetCodexStdioEnvArg();
+
+ return "# Register the MCP server with Codex:\n" +
+ $"codex mcp add {CodexServerName}{envArg} -- {QuoteCliArg(uvxPath)} {devFlags}{fromArgs} {packageName} --transport stdio\n\n" +
+ "# Unregister the MCP server:\n" +
+ $"codex mcp remove {CodexServerName}\n\n" +
+ "# List configured servers:\n" +
+ "codex mcp list";
}
catch (Exception ex)
{
@@ -526,10 +551,229 @@ public override string GetManualSnippet()
public override IList GetInstallationSteps() => new List
{
- "Run 'codex config edit' or open the config path",
- "Paste the TOML",
- "Save and restart Codex"
+ "Ensure the Codex CLI is installed",
+ "Click Configure to add Unity MCP via 'codex mcp add'",
+ "Codex reads the configuration from ~/.codex/config.toml",
+ "Use Unregister to remove it via 'codex mcp remove'"
};
+
+ private void Register()
+ {
+ if (ShouldUseTomlForRemoteAuth())
+ {
+ RegisterWithToml();
+ return;
+ }
+
+ WarnIfRemoteHttpHasNoApiKey();
+
+ string codexPath = ResolveCodexCliPath();
+ if (string.IsNullOrEmpty(codexPath))
+ {
+ McpLog.Warn("Codex CLI not found. Falling back to ~/.codex/config.toml.");
+ RegisterWithToml();
+ return;
+ }
+
+ string args = BuildCodexAddArgs();
+
+ RemoveCodexRegistrations(codexPath);
+
+ if (!ExecPath.TryRun(codexPath, args, null, out var stdout, out var stderr, 15000, GetCodexPathPrepend(codexPath)))
+ {
+ McpLog.Warn($"Codex CLI registration failed. Falling back to ~/.codex/config.toml.\n{stderr}\n{stdout}");
+ RegisterWithToml();
+ return;
+ }
+
+ client.SetStatus(McpStatus.Configured);
+ client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
+ McpLog.Info($"Successfully registered with Codex using {(EditorConfigurationCache.Instance.UseHttpTransport ? "HTTP" : "stdio")} transport.");
+ }
+
+ private void RegisterWithToml()
+ {
+ string path = GetConfigPath();
+ McpConfigurationHelper.EnsureConfigDirectoryExists(path);
+ string result = McpConfigurationHelper.ConfigureCodexClient(path, client);
+ if (result != "Configured successfully")
+ {
+ throw new InvalidOperationException(result);
+ }
+
+ client.SetStatus(McpStatus.Configured);
+ client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
+ }
+
+ private void Unregister()
+ {
+ string codexPath = ResolveCodexCliPath();
+ if (!string.IsNullOrEmpty(codexPath))
+ {
+ RemoveCodexRegistrations(codexPath);
+ }
+
+ RemoveCodexTomlRegistration();
+ client.SetStatus(McpStatus.NotConfigured);
+ client.configuredTransport = Models.ConfiguredTransport.Unknown;
+ McpLog.Info("MCP server successfully unregistered from Codex.");
+ }
+
+ private string BuildCodexAddArgs()
+ {
+ bool useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport;
+ if (useHttpTransport)
+ {
+ string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
+ return $"mcp add {CodexServerName} --url {QuoteCliArg(httpUrl)}";
+ }
+
+ var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
+ string devFlags = AssetPathUtility.GetUvxDevFlags();
+ string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);
+ string envArg = GetCodexStdioEnvArg();
+
+ return $"mcp add {CodexServerName}{envArg} -- {QuoteCliArg(uvxPath)} {devFlags}{fromArgs} {packageName} --transport stdio";
+ }
+
+ private static bool ShouldUseTomlForRemoteAuth()
+ {
+ return EditorConfigurationCache.Instance.UseHttpTransport
+ && HttpEndpointUtility.IsRemoteScope()
+ && !string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty));
+ }
+
+ private static void WarnIfRemoteHttpHasNoApiKey()
+ {
+ if (!EditorConfigurationCache.Instance.UseHttpTransport || !HttpEndpointUtility.IsRemoteScope())
+ {
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty)))
+ {
+ return;
+ }
+
+ McpLog.Warn("Codex is being configured for a remote HTTP MCP server without an API key. If that server requires X-API-Key authentication, add the key in MCP for Unity before configuring Codex.");
+ }
+
+ private static string GetCodexStdioEnvArg()
+ {
+ if (Application.platform != RuntimePlatform.WindowsEditor)
+ {
+ return string.Empty;
+ }
+
+ string systemRoot = MCPServiceLocator.Platform.GetSystemRoot();
+ return string.IsNullOrEmpty(systemRoot)
+ ? string.Empty
+ : $" --env {QuoteCliArg($"SystemRoot={systemRoot}")}";
+ }
+
+ private void RemoveCodexRegistrations(string codexPath)
+ {
+ string pathPrepend = GetCodexPathPrepend(codexPath);
+ ExecPath.TryRun(codexPath, $"mcp remove {CodexServerName}", null, out _, out _, 5000, pathPrepend);
+ ExecPath.TryRun(codexPath, $"mcp remove {LegacyCodexServerName}", null, out _, out _, 5000, pathPrepend);
+ }
+
+ private void RemoveCodexTomlRegistration()
+ {
+ string path = GetConfigPath();
+ if (!File.Exists(path)) return;
+
+ string existingToml = File.ReadAllText(path);
+ string updatedToml = CodexConfigHelper.RemoveCodexServerBlock(existingToml);
+ if (!string.Equals(existingToml, updatedToml, StringComparison.Ordinal))
+ {
+ McpConfigurationHelper.WriteAtomicFile(path, updatedToml);
+ }
+ }
+
+ private static string ResolveCodexCliPath()
+ {
+ string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty;
+ string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty;
+ string[] candidates =
+ {
+ Path.Combine(appData, "npm", "codex.cmd"),
+ Path.Combine(appData, "npm", "codex.ps1"),
+ Path.Combine(localAppData, "npm", "codex.cmd"),
+ Path.Combine(localAppData, "npm", "codex.ps1"),
+ Path.Combine(home, ".local", "bin", "codex.exe"),
+ };
+
+ foreach (string candidate in candidates)
+ {
+ if (File.Exists(candidate)) return candidate;
+ }
+
+ foreach (string name in new[] { "codex.exe", "codex.cmd", "codex.ps1", "codex" })
+ {
+ string fromPath = ExecPath.FindInPath(name);
+ if (!string.IsNullOrEmpty(fromPath)) return fromPath;
+ }
+ }
+ else
+ {
+ string[] candidates =
+ {
+ "/opt/homebrew/bin/codex",
+ "/usr/local/bin/codex",
+ "/usr/bin/codex",
+ Path.Combine(home, ".local", "bin", "codex"),
+ Path.Combine(home, ".npm-global", "bin", "codex"),
+ };
+
+ foreach (string candidate in candidates)
+ {
+ if (File.Exists(candidate)) return candidate;
+ }
+
+ string fromPath = ExecPath.FindInPath("codex", GetDefaultCliPathPrepend());
+ if (!string.IsNullOrEmpty(fromPath)) return fromPath;
+ }
+
+ return null;
+ }
+
+ private static string GetCodexPathPrepend(string codexPath)
+ {
+ string pathPrepend = GetDefaultCliPathPrepend();
+ try
+ {
+ string codexDir = Path.GetDirectoryName(codexPath);
+ if (!string.IsNullOrEmpty(codexDir))
+ {
+ pathPrepend = string.IsNullOrEmpty(pathPrepend)
+ ? codexDir
+ : $"{codexDir}{Path.PathSeparator}{pathPrepend}";
+ }
+ }
+ catch { }
+
+ return pathPrepend;
+ }
+
+ private static string GetDefaultCliPathPrepend()
+ {
+ if (Application.platform == RuntimePlatform.OSXEditor)
+ return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
+ if (Application.platform == RuntimePlatform.LinuxEditor)
+ return "/usr/local/bin:/usr/bin:/bin";
+ return null;
+ }
+
+ private static string QuoteCliArg(string value)
+ {
+ if (string.IsNullOrEmpty(value)) return "\"\"";
+ return "\"" + value.Replace("\"", "\\\"") + "\"";
+ }
}
/// CLI-based configurator (Claude Code).
diff --git a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
index 45ae5a39e..e0c0f220a 100644
--- a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
+++ b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
@@ -39,6 +39,8 @@ public static string BuildCodexServerBlock(string uvPath)
string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
unityMCP["url"] = new TomlString { Value = httpUrl };
+ AddRemoteAuthHeaderIfNeeded(unityMCP);
+
// Enable Codex's Rust MCP client for HTTP/SSE transport
EnsureRmcpClientFeature(table);
}
@@ -83,6 +85,26 @@ public static string BuildCodexServerBlock(string uvPath)
return writer.ToString();
}
+ public static string RemoveCodexServerBlock(string existingToml)
+ {
+ var root = TryParseToml(existingToml);
+ if (root == null)
+ {
+ if (!string.IsNullOrWhiteSpace(existingToml))
+ {
+ McpLog.Warn("Codex config.toml could not be parsed; leaving it unchanged. Manual cleanup of the [mcp_servers.unityMCP] block may be required.");
+ }
+ return existingToml ?? string.Empty;
+ }
+
+ RemoveUnityServer(root, "mcp_servers");
+ RemoveUnityServer(root, "mcpServers");
+
+ using var writer = new StringWriter();
+ root.WriteTo(writer);
+ return writer.ToString();
+ }
+
public static string UpsertCodexServerBlock(string existingToml, string uvPath)
{
// Parse existing TOML or create new root table
@@ -151,6 +173,34 @@ public static bool TryParseCodexServer(string toml, out string command, out stri
return !string.IsNullOrEmpty(command) && args != null;
}
+ public static bool HasCodexHttpHeader(string toml, string headerName, string expectedValue)
+ {
+ if (string.IsNullOrEmpty(headerName)) return false;
+
+ var root = TryParseToml(toml);
+ 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)
+ && !TryGetTable(servers, "UnityMCP", out unity))
+ {
+ return false;
+ }
+
+ if (!TryGetTable(unity, "http_headers", out var headers))
+ {
+ return false;
+ }
+
+ string configuredValue = GetTomlString(headers, headerName);
+ return string.Equals(configuredValue, expectedValue, StringComparison.Ordinal);
+ }
+
///
/// Safely parses TOML string, returning null on failure
///
@@ -193,6 +243,7 @@ private static TomlTable CreateUnityMcpTable(string uvPath)
// HTTP mode: Use url field
string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
unityMCP["url"] = new TomlString { Value = httpUrl };
+ AddRemoteAuthHeaderIfNeeded(unityMCP);
}
else
{
@@ -229,6 +280,48 @@ private static TomlTable CreateUnityMcpTable(string uvPath)
return unityMCP;
}
+ private static void AddRemoteAuthHeaderIfNeeded(TomlTable unityMCP)
+ {
+ if (unityMCP == null) return;
+
+ if (!HttpEndpointUtility.IsRemoteScope())
+ {
+ unityMCP.Delete("http_headers");
+ return;
+ }
+
+ string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);
+ if (string.IsNullOrEmpty(apiKey))
+ {
+ unityMCP.Delete("http_headers");
+ return;
+ }
+
+ var headers = new TomlTable { IsInline = true };
+ headers[AuthConstants.ApiKeyHeader] = new TomlString { Value = apiKey };
+ unityMCP["http_headers"] = headers;
+ }
+
+ private static void RemoveUnityServer(TomlTable root, string serverTableName)
+ {
+ if (root == null) return;
+ if (!TryGetTable(root, serverTableName, out var servers)) return;
+
+ foreach (string key in servers.Keys.ToList())
+ {
+ if (string.Equals(key, "unityMCP", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(key, "UnityMCP", StringComparison.OrdinalIgnoreCase))
+ {
+ servers.Delete(key);
+ }
+ }
+
+ if (servers.ChildrenCount == 0)
+ {
+ root.Delete(serverTableName);
+ }
+ }
+
///
/// Ensures the features table contains the rmcp_client flag for HTTP/SSE transport.
///
diff --git a/MCPForUnity/README.md b/MCPForUnity/README.md
index 3631f60f6..91ee0640d 100644
--- a/MCPForUnity/README.md
+++ b/MCPForUnity/README.md
@@ -16,8 +16,9 @@ The window has four areas: Server Status, Unity Bridge, MCP Client Configuration
- Select the packaged server folder (`Server`) if you want to run the bundled implementation.
- Install Python and/or uv/uvx if missing so the server can be managed locally.
- For Claude Code, ensure the `claude` CLI is installed.
+ - For Codex, ensure the `codex` CLI is installed.
4. Click “Start Bridge” if the Unity Bridge shows “Stopped”.
-5. Use your MCP client (Cursor, VS Code, OpenClaw, Claude Code) to connect.
+5. Use your MCP client (Cursor, VS Code, OpenClaw, Claude Code, Codex) to connect.
---
@@ -47,7 +48,7 @@ The window has four areas: Server Status, Unity Bridge, MCP Client Configuration
---
## MCP Client Configuration
-- Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code).
+- Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code, Codex).
- Per-client actions:
- Cursor / VS Code / Windsurf:
- Auto Configure: Writes/updates your config to launch the server via `uvx` with the current package version:
@@ -60,6 +61,11 @@ The window has four areas: Server Status, Unity Bridge, MCP Client Configuration
- Register with Claude Code / Unregister MCP for Unity with Claude Code.
- If the CLI isn’t found, click “Choose Claude Install Location”.
- The window displays the resolved Claude CLI path when detected.
+ - Codex:
+ - Configure / Unregister MCP for Unity with Codex.
+ - Local HTTP and stdio setup use `codex mcp add` / `codex mcp remove` when the Codex CLI is available.
+ - If the CLI is not found, MCP for Unity writes `~/.codex/config.toml` directly.
+ - Remote-hosted setup with `X-API-Key` auth uses TOML because the Codex CLI does not currently expose an arbitrary HTTP header flag.
- OpenClaw:
- Uses `~/.openclaw/openclaw.json` and the `openclaw-mcp-bridge` plugin.
- MCP for Unity writes `plugins.entries.openclaw-mcp-bridge.config.servers.unityMCP`.
@@ -87,6 +93,8 @@ Notes:
- Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf)
- Claude CLI not found:
- Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code)
+- Codex not connecting:
+ - Help: [Codex setup help](../docs/guides/CODEX_HELP.md)
---
diff --git a/README.md b/README.md
index 5a47a6f87..af201c9fd 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
[](https://modelcontextprotocol.io/introduction)
[](https://opensource.org/licenses/MIT)
-**Create your Unity apps with LLMs!** MCP for Unity bridges AI assistants (Claude, Claude Code, Cursor, VS Code, etc.) with your Unity Editor via the [Model Context Protocol](https://modelcontextprotocol.io/introduction). Give your LLM the tools to manage assets, control scenes, edit scripts, and automate tasks.
+**Create your Unity apps with LLMs!** MCP for Unity bridges AI assistants (Claude, Claude Code, Codex, Cursor, VS Code, etc.) with your Unity Editor via the [Model Context Protocol](https://modelcontextprotocol.io/introduction). Give your LLM the tools to manage assets, control scenes, edit scripts, and automate tasks.
@@ -47,7 +47,7 @@
* **Unity 2021.3 LTS+** — [Download Unity](https://unity.com/download)
* **Python 3.10+** and **uv** — [Install uv](https://docs.astral.sh/uv/getting-started/installation/)
-* **An MCP Client** — [Claude Desktop](https://claude.ai/download) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai)
+* **An MCP Client** — [Claude Desktop](https://claude.ai/download) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | [Codex](https://developers.openai.com/codex/mcp) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai)
### 1. Install the Unity Package
@@ -82,7 +82,7 @@ openupm add com.coplaydev.unity-mcp
2. Click **Start Server** (launches HTTP server on `localhost:8080`)
3. Select your MCP Client from the dropdown and click **Configure**
4. Look for 🟢 "Connected ✓"
-5. **Connect your client:** Some clients (Cursor, Antigravity, OpenClaw) require enabling an MCP toggle or plugin in settings. OpenClaw also needs the `openclaw-mcp-bridge` plugin enabled and follows the currently selected MCP for Unity transport (`HTTP` or `stdio`). Others (Claude Desktop, Claude Code) auto-connect after configuration.
+5. **Connect your client:** Some clients (Cursor, Antigravity, OpenClaw) require enabling an MCP toggle or plugin in settings. OpenClaw also needs the `openclaw-mcp-bridge` plugin enabled and follows the currently selected MCP for Unity transport (`HTTP` or `stdio`). Others (Claude Desktop, Claude Code, Codex) pick up the server after configuration or in the next session.
**That's it!** Try a prompt like: *"Create a red, blue and yellow cube"* or *"Build a simple player controller"*
@@ -134,6 +134,14 @@ If auto-setup doesn't work, add this to your MCP client's config file:
}
```
+**Codex CLI:**
+```bash
+codex mcp add unityMCP --url http://127.0.0.1:8080/mcp
+codex mcp list
+```
+
+Codex stores MCP servers in `~/.codex/config.toml`. If you need remote-hosted mode with `X-API-Key` auth, use the Unity Editor's Configure button or see [CODEX_HELP.md](docs/guides/CODEX_HELP.md) for the TOML form.
+
Stdio configuration (uvx)
@@ -213,6 +221,7 @@ For **Strict** validation that catches undefined namespaces, types, and methods:
**Detailed setup guides:**
* [Fix Unity MCP and Cursor, VSCode & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf) — uv/Python installation, PATH issues
* [Fix Unity MCP and Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code) — Claude CLI installation
+* [Codex setup help](docs/guides/CODEX_HELP.md) — Codex CLI registration, TOML fallback, and verification
* [Common Setup Problems](https://github.com/CoplayDev/unity-mcp/wiki/3.-Common-Setup-Problems) — macOS dyld errors, FAQ
Still stuck? [Open an Issue](https://github.com/CoplayDev/unity-mcp/issues) or [Join Discord](https://discord.gg/y4p8KfzrN4)
diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs
index 32a5aef37..69ddbac32 100644
--- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs
+++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs
@@ -59,6 +59,12 @@ public MockPlatformService(bool isWindows, string systemRoot = "C:\\Windows")
private string _originalGitOverride;
private bool _hadHttpTransport;
private bool _originalHttpTransport;
+ private bool _hadHttpTransportScope;
+ private string _originalHttpTransportScope;
+ private bool _hadHttpRemoteBaseUrl;
+ private string _originalHttpRemoteBaseUrl;
+ private bool _hadApiKey;
+ private string _originalApiKey;
private bool _hadDevForceRefresh;
private bool _originalDevForceRefresh;
private IPlatformService _originalPlatformService;
@@ -70,6 +76,12 @@ public void OneTimeSetUp()
_originalGitOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, string.Empty);
_hadHttpTransport = EditorPrefs.HasKey(EditorPrefKeys.UseHttpTransport);
_originalHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
+ _hadHttpTransportScope = EditorPrefs.HasKey(EditorPrefKeys.HttpTransportScope);
+ _originalHttpTransportScope = EditorPrefs.GetString(EditorPrefKeys.HttpTransportScope, string.Empty);
+ _hadHttpRemoteBaseUrl = EditorPrefs.HasKey(EditorPrefKeys.HttpRemoteBaseUrl);
+ _originalHttpRemoteBaseUrl = EditorPrefs.GetString(EditorPrefKeys.HttpRemoteBaseUrl, string.Empty);
+ _hadApiKey = EditorPrefs.HasKey(EditorPrefKeys.ApiKey);
+ _originalApiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);
_hadDevForceRefresh = EditorPrefs.HasKey(EditorPrefKeys.DevModeForceServerRefresh);
_originalDevForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
_originalPlatformService = MCPServiceLocator.Platform;
@@ -82,6 +94,9 @@ public void SetUp()
EditorPrefs.DeleteKey(EditorPrefKeys.GitUrlOverride);
// Default to stdio mode for existing tests unless specified otherwise
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, false);
+ EditorPrefs.DeleteKey(EditorPrefKeys.HttpTransportScope);
+ EditorPrefs.DeleteKey(EditorPrefKeys.HttpRemoteBaseUrl);
+ EditorPrefs.DeleteKey(EditorPrefKeys.ApiKey);
// Ensure deterministic uvx args ordering for these tests regardless of editor settings
// (dev-mode inserts --no-cache/--refresh, which changes the first args).
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
@@ -129,6 +144,33 @@ public void OneTimeTearDown()
EditorPrefs.DeleteKey(EditorPrefKeys.UseHttpTransport);
}
+ if (_hadHttpTransportScope)
+ {
+ EditorPrefs.SetString(EditorPrefKeys.HttpTransportScope, _originalHttpTransportScope);
+ }
+ else
+ {
+ EditorPrefs.DeleteKey(EditorPrefKeys.HttpTransportScope);
+ }
+
+ if (_hadHttpRemoteBaseUrl)
+ {
+ EditorPrefs.SetString(EditorPrefKeys.HttpRemoteBaseUrl, _originalHttpRemoteBaseUrl);
+ }
+ else
+ {
+ EditorPrefs.DeleteKey(EditorPrefKeys.HttpRemoteBaseUrl);
+ }
+
+ if (_hadApiKey)
+ {
+ EditorPrefs.SetString(EditorPrefKeys.ApiKey, _originalApiKey);
+ }
+ else
+ {
+ EditorPrefs.DeleteKey(EditorPrefKeys.ApiKey);
+ }
+
if (_hadDevForceRefresh)
{
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, _originalDevForceRefresh);
@@ -511,6 +553,43 @@ public void BuildCodexServerBlock_HttpMode_GeneratesUrlField()
Assert.IsFalse(unityMcp.TryGetNode("command", out _), "HTTP mode should not contain command field");
Assert.IsFalse(unityMcp.TryGetNode("args", out _), "HTTP mode should not contain args field");
Assert.IsFalse(unityMcp.TryGetNode("env", out _), "HTTP mode should not contain env field");
+ Assert.IsFalse(unityMcp.TryGetNode("http_headers", out _), "Local HTTP mode should not contain auth headers");
+ }
+
+ [Test]
+ public void BuildCodexServerBlock_RemoteHttpModeWithApiKey_IncludesHttpHeaders()
+ {
+ EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, true);
+ EditorPrefs.SetString(EditorPrefKeys.HttpTransportScope, "remote");
+ EditorPrefs.SetString(EditorPrefKeys.HttpRemoteBaseUrl, "https://unity-mcp.example");
+ EditorPrefs.SetString(EditorPrefKeys.ApiKey, "test-api-key");
+ EditorConfigurationCache.Instance.Refresh();
+
+ string result = CodexConfigHelper.BuildCodexServerBlock("uvx");
+
+ TomlTable parsed;
+ using (var reader = new StringReader(result))
+ {
+ parsed = TOML.Parse(reader);
+ }
+
+ var mcpServers = parsed["mcp_servers"] as TomlTable;
+ var unityMcp = mcpServers["unityMCP"] as TomlTable;
+
+ Assert.IsTrue(unityMcp.TryGetNode("url", out var urlNode), "Remote HTTP mode should contain url");
+ Assert.AreEqual("https://unity-mcp.example/mcp", (urlNode as TomlString).Value);
+
+ Assert.IsTrue(unityMcp.TryGetNode("http_headers", out var headersNode), "Remote HTTP mode should include auth headers");
+ Assert.IsInstanceOf(headersNode);
+ var headers = headersNode as TomlTable;
+ Assert.IsTrue(headers.TryGetNode(AuthConstants.ApiKeyHeader, out var apiKeyNode), "Headers should contain X-API-Key");
+ Assert.AreEqual("test-api-key", (apiKeyNode as TomlString).Value);
+ Assert.IsTrue(
+ CodexConfigHelper.HasCodexHttpHeader(result, AuthConstants.ApiKeyHeader, "test-api-key"),
+ "Header helper should detect the generated X-API-Key");
+ Assert.IsFalse(
+ CodexConfigHelper.HasCodexHttpHeader(result, AuthConstants.ApiKeyHeader, "stale-key"),
+ "Header helper should reject stale X-API-Key values");
}
[Test]
@@ -591,5 +670,36 @@ public void UpsertCodexServerBlock_HttpMode_GeneratesUrlField()
Assert.IsFalse(unityMcp.TryGetNode("command", out _), "HTTP mode should not contain command field");
Assert.IsFalse(unityMcp.TryGetNode("args", out _), "HTTP mode should not contain args field");
}
+
+ [Test]
+ public void RemoveCodexServerBlock_RemovesUnityServerAndPreservesOtherSections()
+ {
+ string existingToml = string.Join("\n", new[]
+ {
+ "[profile.default]",
+ "model = \"gpt-5.2-codex\"",
+ "",
+ "[mcp_servers.unityMCP]",
+ "url = \"http://127.0.0.1:8080/mcp\"",
+ "",
+ "[mcp_servers.otherServer]",
+ "url = \"http://127.0.0.1:9999/mcp\""
+ });
+
+ string result = CodexConfigHelper.RemoveCodexServerBlock(existingToml);
+
+ TomlTable parsed;
+ using (var reader = new StringReader(result))
+ {
+ parsed = TOML.Parse(reader);
+ }
+
+ Assert.IsTrue(parsed.TryGetNode("profile", out _), "Unrelated sections should be preserved");
+ Assert.IsTrue(parsed.TryGetNode("mcp_servers", out var serversNode), "Other MCP servers should be preserved");
+
+ var servers = serversNode as TomlTable;
+ Assert.IsFalse(servers.TryGetNode("unityMCP", out _), "Unity MCP server should be removed");
+ Assert.IsTrue(servers.TryGetNode("otherServer", out _), "Other MCP servers should remain");
+ }
}
}
diff --git a/docs/guides/CODEX_HELP.md b/docs/guides/CODEX_HELP.md
new file mode 100644
index 000000000..dc66f6fc6
--- /dev/null
+++ b/docs/guides/CODEX_HELP.md
@@ -0,0 +1,87 @@
+### Codex: MCP for Unity setup
+
+Codex can use MCP for Unity through the Codex CLI or by reading `~/.codex/config.toml`. The Unity Editor window uses the CLI first for the common local cases and falls back to TOML when needed.
+
+## Quick setup
+
+1. Install Codex and confirm the CLI is available:
+
+```bash
+codex --version
+```
+
+2. Open Unity and start MCP for Unity:
+ - `Window > MCP for Unity`
+ - Click `Start Server`
+ - Select `Codex`
+ - Click `Configure`
+
+3. Start a new Codex session and check the server:
+
+```bash
+codex mcp list
+codex mcp get unityMCP --json
+```
+
+For local HTTP, the generated entry should point at:
+
+```text
+http://127.0.0.1:8080/mcp
+```
+
+`127.0.0.1` is preferred over `localhost` because some Codex HTTP clients resolve `localhost` to IPv6 first, while the local Unity MCP server binds to IPv4.
+
+## Manual local HTTP setup
+
+If the Unity Editor setup is not available, register the server from a terminal:
+
+```bash
+codex mcp add unityMCP --url http://127.0.0.1:8080/mcp
+codex mcp list
+```
+
+To remove it:
+
+```bash
+codex mcp remove unityMCP
+```
+
+## Manual stdio setup
+
+Use stdio when you want Codex to launch the MCP server process itself:
+
+```bash
+codex mcp add unityMCP -- uvx --from mcpforunityserver mcp-for-unity --transport stdio
+```
+
+On Windows, if Codex cannot launch the stdio server, add `SystemRoot`:
+
+```bash
+codex mcp add unityMCP --env "SystemRoot=C:\Windows" -- uvx --from mcpforunityserver mcp-for-unity --transport stdio
+```
+
+If `uvx` is not on Codex's PATH, use the absolute `uvx` path shown in the MCP for Unity window. Quote paths that contain spaces.
+
+## Remote-hosted auth
+
+Codex supports HTTP MCP servers, but the Codex CLI does not currently expose a flag for arbitrary HTTP headers such as `X-API-Key`. For remote-hosted MCP for Unity servers that require this header, configure `~/.codex/config.toml` directly:
+
+```toml
+[mcp_servers.unityMCP]
+url = "https://your-server.example/mcp"
+http_headers = { "X-API-Key" = "your-api-key" }
+```
+
+This stores the API key in `~/.codex/config.toml` under `[mcp_servers.unityMCP].http_headers`. Keep that file private and use restrictive file permissions where your platform supports them.
+
+The Unity Editor's `Configure` action writes this TOML form automatically when remote-hosted mode has an API key configured.
+
+## Troubleshooting
+
+If Codex does not show Unity tools:
+
+1. Confirm the MCP for Unity server is running in Unity.
+2. Run `codex mcp list` and check that `unityMCP` is enabled.
+3. Run `codex mcp get unityMCP --json` and verify the URL or stdio command.
+4. Restart the Codex session after changing MCP configuration.
+5. If local HTTP fails with `localhost`, use `http://127.0.0.1:8080/mcp`.
diff --git a/docs/guides/MCP_CLIENT_CONFIGURATORS.md b/docs/guides/MCP_CLIENT_CONFIGURATORS.md
index 09d8a0540..aebbe5dcf 100644
--- a/docs/guides/MCP_CLIENT_CONFIGURATORS.md
+++ b/docs/guides/MCP_CLIENT_CONFIGURATORS.md
@@ -136,11 +136,14 @@ Some clients cannot be handled by the generic JSON configurator alone.
### Codex (TOML-based)
- Uses **`CodexMcpConfigurator`**.
-- Reads and writes a **TOML** config (usually `~/.codex/config.toml`).
+- Uses `codex mcp add` / `codex mcp remove` for local HTTP and stdio setup when the Codex CLI is available.
+- Reads `~/.codex/config.toml` for fast status checks and to detect manual configuration.
+- Falls back to writing TOML directly when the Codex CLI is not available.
+- Writes TOML directly for remote-hosted `X-API-Key` auth because the Codex CLI does not currently expose an arbitrary HTTP header flag.
- Uses `CodexConfigHelper` to:
- Parse the existing TOML.
- Check for a matching Unity MCP server configuration.
- - Write/patch the Codex server block.
+ - Write/patch/remove the Codex server block.
- The `CodexConfigurator` class:
- Only needs to supply a `McpClient` with TOML config paths.
- Inherits the Codex-specific status and configure behavior from `CodexMcpConfigurator`.
diff --git a/docs/i18n/README-zh.md b/docs/i18n/README-zh.md
index 87e451f4f..787bb9312 100644
--- a/docs/i18n/README-zh.md
+++ b/docs/i18n/README-zh.md
@@ -13,7 +13,7 @@
[](https://modelcontextprotocol.io/introduction)
[](https://opensource.org/licenses/MIT)
-**用大语言模型创建你的 Unity 应用!** MCP for Unity 通过 [Model Context Protocol](https://modelcontextprotocol.io/introduction) 将 AI 助手(Claude、Cursor、VS Code 等)与你的 Unity Editor 连接起来。为你的大语言模型提供管理资源、控制场景、编辑脚本和自动化任务的工具。
+**用大语言模型创建你的 Unity 应用!** MCP for Unity 通过 [Model Context Protocol](https://modelcontextprotocol.io/introduction) 将 AI 助手(Claude、Claude Code、Codex、Cursor、VS Code 等)与你的 Unity Editor 连接起来。为你的大语言模型提供管理资源、控制场景、编辑脚本和自动化任务的工具。
@@ -46,7 +46,7 @@
* **Unity 2021.3 LTS+** — [下载 Unity](https://unity.com/download)
* **Python 3.10+** 和 **uv** — [安装 uv](https://docs.astral.sh/uv/getting-started/installation/)
-* **一个 MCP 客户端** — [Claude Desktop](https://claude.ai/download) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai)
+* **一个 MCP 客户端** — [Claude Desktop](https://claude.ai/download) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | [Codex](https://developers.openai.com/codex/mcp) | [Cursor](https://www.cursor.com/en/downloads) | [VS Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [GitHub Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) | [OpenClaw](https://openclaw.ai)
### 1. 安装 Unity 包
@@ -81,7 +81,7 @@ openupm add com.coplaydev.unity-mcp
2. 点击 **Start Server**(会在 `localhost:8080` 启动 HTTP 服务器)
3. 从下拉菜单选择你的 MCP Client,然后点击 **Configure**
4. 查找 🟢 "Connected ✓"
-5. **连接你的客户端:** 一些客户端(Cursor、Antigravity、OpenClaw)需要在设置里启用 MCP 开关或插件。OpenClaw 还需要启用 `openclaw-mcp-bridge` 插件,并会跟随 MCP for Unity 当前选择的传输方式(HTTP 或 stdio);另一些(Claude Desktop、Claude Code)在配置后会自动连接。
+5. **连接你的客户端:** 一些客户端(Cursor、Antigravity、OpenClaw)需要在设置里启用 MCP 开关或插件。OpenClaw 还需要启用 `openclaw-mcp-bridge` 插件,并会跟随 MCP for Unity 当前选择的传输方式(HTTP 或 stdio);另一些(Claude Desktop、Claude Code、Codex)会在配置后或下一个会话中读取该服务器。
**就这些!** 试试这样的提示词:*"Create a red, blue and yellow cube"* 或 *"Build a simple player controller"*
@@ -133,6 +133,14 @@ openupm add com.coplaydev.unity-mcp
}
```
+**Codex CLI:**
+```bash
+codex mcp add unityMCP --url http://127.0.0.1:8080/mcp
+codex mcp list
+```
+
+Codex 会把 MCP server 保存到 `~/.codex/config.toml`。如果你使用带 `X-API-Key` 的 remote-hosted 模式,请使用 Unity Editor 的 Configure 按钮,或参考 [CODEX_HELP.md](../guides/CODEX_HELP.md) 中的 TOML 配置。
+
Stdio 配置(uvx)
@@ -204,6 +212,7 @@ MCP for Unity 支持多个 Unity Editor 实例。要将操作定向到某个特
**详细的设置指南:**
* [Fix Unity MCP and Cursor, VSCode & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf) — uv/Python 安装、PATH 问题
* [Fix Unity MCP and Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code) — Claude CLI 安装
+* [Codex setup help](../guides/CODEX_HELP.md) — Codex CLI 注册、TOML fallback、验证步骤
* [Common Setup Problems](https://github.com/CoplayDev/unity-mcp/wiki/3.-Common-Setup-Problems) — macOS dyld 错误、FAQ
还是卡住?[开一个 Issue](https://github.com/CoplayDev/unity-mcp/issues) 或 [加入 Discord](https://discord.gg/y4p8KfzrN4)