From 17507d129a8f71c26298efd9dc5467ffc6bd37d4 Mon Sep 17 00:00:00 2001 From: Daliys Date: Sat, 11 Jul 2026 00:05:26 +0200 Subject: [PATCH] Fix current MCP client setup --- CHANGELOG.md | 4 ++ DOCUMENTATION.MD | 4 +- Editor/MCPCliInstaller.ClaudeCode.cs | 21 ++++++++--- Editor/MCPCliInstaller.Gemini.cs | 8 ++-- Editor/MCPCliInstaller.cs | 11 +++++- Editor/NexusMcpConfigGenerator.cs | 12 ++---- README.md | 4 +- Tests~/Editor/MCPCliInstallerTests.cs | 7 ++++ Tests~/Editor/NexusMcpConfigGeneratorTests.cs | 37 +++++++++++++++++++ 9 files changed, 85 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 521b301..e35787d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ All notable public changes to Nexus Unity are documented here. - Relicensed Nexus Unity from `GPL-3.0-only` to the `MIT` license to remove copyleft friction for commercial Unity studios. All prior contributors consented to the relicense. ### Fixed +- Updated Claude Code and Gemini CLI setup commands for their current option ordering and explicit project-scoped stdio registration; Gemini setup no longer bypasses tool approvals with `--trust`. +- Antigravity setup now writes its workspace configuration to `.agents/mcp_config.json` instead of the legacy global Gemini config path. +- Cline setup now writes its shared MCP settings to `~/.cline/data/settings/cline_mcp_settings.json` instead of the retired VS Code extension storage path. +- Claude Code setup now skips the CLI add command when removal of a stale project registration fails, logs the actionable failure, and falls back directly to `.mcp.json`. - The local HTTP/WebSocket control plane now requires a per-session auth token before JSON-RPC dispatch; generated MCP configs pass the token through the Python bridge. - `add_component` now returns a clear `GameObject not found` error for stale instance IDs instead of throwing a raw Unity null reference. - `batch_execute` now caps batches at 50 requests and rejects nested batch execution. diff --git a/DOCUMENTATION.MD b/DOCUMENTATION.MD index 9972bba..b2de5f6 100644 --- a/DOCUMENTATION.MD +++ b/DOCUMENTATION.MD @@ -43,7 +43,9 @@ Console logging defaults to `Important`, which keeps the Unity Console focused o The `Integrations` tab builds all snippets from the resolved Python interpreter and the project-root bridge path `nexus_unity_bridge.py`. Auto setup deploys the bridge first. JSON-style clients use `mcpServers.nexus-unity`, VS Code-compatible clients use `.vscode/mcp.json` with `servers.nexus-unity`, Claude Code uses a project-root `.mcp.json` with `mcpServers.nexus-unity`, and Codex uses `[mcp_servers.nexus-unity]` in TOML. -Claude Code and Claude Desktop are distinct products with distinct config locations. The Claude Code card registers `nexus-unity` in `/.mcp.json` (via the `claude` CLI when it is on `PATH`, otherwise by writing the file directly); the Claude Desktop card writes the desktop app config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows). Claude Code does not read the desktop config. +Claude Code and Claude Desktop are distinct products with distinct config locations. The Claude Code card registers `nexus-unity` in `/.mcp.json` (via the `claude` CLI when it is on `PATH`, otherwise by writing the file directly). If the CLI cannot remove a stale registration, setup skips its add command and writes `.mcp.json` directly instead; the CLI error is logged for diagnosis. The Claude Desktop card writes the desktop app config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows). Claude Code does not read the desktop config. + +Gemini CLI uses a project-scoped stdio registration in `.gemini/settings.json`; Auto Setup keeps Gemini's normal tool-approval prompts enabled. Antigravity uses the workspace-local `.agents/mcp_config.json` with the standard `mcpServers` object, not the older shared Gemini configuration path. Cline uses `~/.cline/data/settings/cline_mcp_settings.json` for shared MCP settings. ### Platform notes diff --git a/Editor/MCPCliInstaller.ClaudeCode.cs b/Editor/MCPCliInstaller.ClaudeCode.cs index 76bf702..e7734f0 100644 --- a/Editor/MCPCliInstaller.ClaudeCode.cs +++ b/Editor/MCPCliInstaller.ClaudeCode.cs @@ -35,18 +35,22 @@ private static void ExecuteClaudeCodeLinkSequence(string scriptPath, string pyth // Preferred path: the official claude CLI writes the project-scoped .mcp.json for us. if (!string.IsNullOrEmpty(claudePath) && claudePath != "claude") { - // 1. Remove any stale registration so re-running is idempotent (silent). - RunInstallerProcess(CreateProcessStartInfo(claudePath, "mcp", "remove", "nexus-unity", "-s", "project"), claudePath, false, "Claude Code"); + // 1. Remove any stale registration so re-running is idempotent. + // A missing registration is expected on first setup and is safe to add over. + bool removedStaleRegistration = RunInstallerProcess(CreateProcessStartInfo(claudePath, "mcp", "remove", "--scope", "project", "nexus-unity"), claudePath, false, "Claude Code", out string removeError, false); + bool registrationWasAbsent = !removedStaleRegistration && IsClaudeCodeRegistrationAbsent(removeError); - // 2. Add the server at project scope (silent, because we have a file fallback). - if (RunInstallerProcess(CreateProcessStartInfo(claudePath, "mcp", "add", "nexus-unity", "-s", "project", "-e", MCPServer.AuthTokenEnvironmentVariable + "=" + MCPServer.AuthToken, "--", pythonPath, scriptPath), claudePath, false, "Claude Code")) + // 2. Add the server at project scope only when the prior state is known to be clear. + if ((removedStaleRegistration || registrationWasAbsent) && RunInstallerProcess(CreateProcessStartInfo(claudePath, "mcp", "add", "--transport", "stdio", "--scope", "project", "--env", MCPServer.AuthTokenEnvironmentVariable + "=" + MCPServer.AuthToken, "nexus-unity", "--", pythonPath, scriptPath), claudePath, false, "Claude Code")) { NexusEditorLog.Log(NexusLogCategory.Integrations, "[MCP] Successfully linked Nexus Unity to Claude Code via '" + claudePath + "'.", true); EditorUtility.DisplayDialog("MCP Success", "Successfully linked Nexus Unity to Claude Code.\n\nRun /mcp inside Claude Code (or restart it) to load the server.", "OK"); return; } - NexusEditorLog.Warning(NexusLogCategory.Integrations, "[MCP] Claude Code CLI command failed at '" + claudePath + "'. Falling back to direct .mcp.json edit."); + NexusEditorLog.Warning(NexusLogCategory.Integrations, (removedStaleRegistration || registrationWasAbsent) + ? "[MCP] Claude Code CLI add command failed at '" + claudePath + "'. Falling back to direct .mcp.json edit." + : "[MCP] Claude Code CLI could not remove the existing registration at '" + claudePath + "': " + removeError + ". Skipping CLI add and falling back to direct .mcp.json edit."); } // Fallback: write the project-root .mcp.json directly. @@ -88,5 +92,12 @@ private static void ExecuteClaudeCodeLinkSequence(string scriptPath, string pyth EditorUtility.DisplayDialog("MCP Error", "Failed to write .mcp.json for Claude Code.\n\n" + e.Message, "OK"); } } + + internal static bool IsClaudeCodeRegistrationAbsent(string error) + { + return !string.IsNullOrEmpty(error) + && error.IndexOf("nexus-unity", StringComparison.OrdinalIgnoreCase) >= 0 + && error.IndexOf("not found", StringComparison.OrdinalIgnoreCase) >= 0; + } } } diff --git a/Editor/MCPCliInstaller.Gemini.cs b/Editor/MCPCliInstaller.Gemini.cs index 97f5048..41e7b5f 100644 --- a/Editor/MCPCliInstaller.Gemini.cs +++ b/Editor/MCPCliInstaller.Gemini.cs @@ -29,11 +29,11 @@ private static void ExecuteGeminiLinkSequence(string scriptPath) string geminiPath = ResolveExecutablePath("gemini"); string pythonPath = ResolvePythonPath(); - // 1. Ensure clean slate by removing existing registration - RunInstallerProcess(CreateProcessStartInfo(geminiPath, "mcp", "remove", "nexus-unity"), geminiPath, false, "Gemini"); + // 1. Ensure clean slate by removing the existing project registration. + RunInstallerProcess(CreateProcessStartInfo(geminiPath, "mcp", "remove", "--scope", "project", "nexus-unity"), geminiPath, false, "Gemini"); - // 2. Add new registration with stable path - RunInstallerProcess(CreateProcessStartInfo(geminiPath, "mcp", "add", "nexus-unity", "--trust", "-e", MCPServer.AuthTokenEnvironmentVariable + "=" + MCPServer.AuthToken, pythonPath, scriptPath), geminiPath, true, "Gemini"); + // 2. Add a stdio registration without bypassing Gemini's tool-approval prompts. + RunInstallerProcess(CreateProcessStartInfo(geminiPath, "mcp", "add", "--scope", "project", "--transport", "stdio", "--env", MCPServer.AuthTokenEnvironmentVariable + "=" + MCPServer.AuthToken, "nexus-unity", pythonPath, scriptPath), geminiPath, true, "Gemini"); } } } diff --git a/Editor/MCPCliInstaller.cs b/Editor/MCPCliInstaller.cs index 2b1133f..47c3e07 100644 --- a/Editor/MCPCliInstaller.cs +++ b/Editor/MCPCliInstaller.cs @@ -397,11 +397,17 @@ private static string QuoteWindowsArgument(string argument) private static bool RunInstallerProcess(ProcessStartInfo psi, string cliPath, bool showSuccessDialog, string cliName) { + return RunInstallerProcess(psi, cliPath, showSuccessDialog, cliName, out _, true); + } + + private static bool RunInstallerProcess(ProcessStartInfo psi, string cliPath, bool showSuccessDialog, string cliName, out string error, bool logFailure) + { + error = string.Empty; try { using (Process p = Process.Start(psi)) { - string error = p.StandardError.ReadToEnd(); + error = p.StandardError.ReadToEnd(); p.WaitForExit(); if (p.ExitCode == 0) @@ -411,7 +417,7 @@ private static bool RunInstallerProcess(ProcessStartInfo psi, string cliPath, bo else { string msg = "CLI command failed at " + cliPath + ".\n\nExit Code: " + p.ExitCode + "\nError: " + error; - NexusEditorLog.Warning(NexusLogCategory.Integrations, "[MCP] " + msg); + if (logFailure) NexusEditorLog.Warning(NexusLogCategory.Integrations, "[MCP] " + msg); if (showSuccessDialog) // If this was supposed to be the final step { @@ -422,6 +428,7 @@ private static bool RunInstallerProcess(ProcessStartInfo psi, string cliPath, bo } catch (Exception e) { + error = e.Message; NexusEditorLog.Error(NexusLogCategory.Integrations, "[MCP] Process start failed: " + e.Message); } return false; diff --git a/Editor/NexusMcpConfigGenerator.cs b/Editor/NexusMcpConfigGenerator.cs index 9a57664..349c39a 100644 --- a/Editor/NexusMcpConfigGenerator.cs +++ b/Editor/NexusMcpConfigGenerator.cs @@ -44,8 +44,8 @@ internal static List BuildAll(string bridgePath, string pyth // Plain JSON-config clients - one row per tool, no client-specific behavior beyond path/format. var jsonClients = new[] { - (NexusMcpClientKind.Antigravity, "Antigravity", Path.Combine(homeDir, ".gemini", "config", "mcp_config.json"), - "Paste into ~/.gemini/config/mcp_config.json (shared with Gemini), then restart Antigravity sessions.", "mcpServers"), + (NexusMcpClientKind.Antigravity, "Antigravity", Path.Combine(projectRoot, ".agents", "mcp_config.json"), + "Paste into .agents/mcp_config.json, then restart or reload Antigravity sessions.", "mcpServers"), (NexusMcpClientKind.Cursor, "Cursor", Path.Combine(projectRoot, ".cursor", "mcp.json"), "Paste into .cursor/mcp.json or use Auto Setup for this Unity project.", "mcpServers"), (NexusMcpClientKind.VsCode, "VS Code", Path.Combine(projectRoot, ".vscode", "mcp.json"), @@ -433,13 +433,7 @@ private static string GetClaudeDesktopConfigPath() // not in the workspace - same layout on macOS/Windows/Linux, rooted at the OS config dir for VS Code. private static string GetClineConfigPath(string homeDir) { - string vsCodeUserDir = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) - ? Path.Combine(homeDir, "Library", "Application Support", "Code", "User") - : RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Path.Combine(homeDir, "AppData", "Roaming", "Code", "User") - : Path.Combine(homeDir, ".config", "Code", "User"); - - return Path.Combine(vsCodeUserDir, "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"); + return Path.Combine(homeDir, ".cline", "data", "settings", "cline_mcp_settings.json"); } } } diff --git a/README.md b/README.md index a748f21..b314852 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ The Unity window can also deploy the bridge to the project root for CLIs that pr For Codex, Claude Desktop, Claude Code, Gemini, Antigravity, Cursor, VS Code/Cline/Roo, Windsurf, or compatible MCP clients, open `Window > Nexus Unity` and use the `Integrations` tab. -> **Claude Code vs. Claude Desktop:** these are different products with different config locations. The `Claude Code` card registers the `nexus-unity` server in a project-scoped `.mcp.json` at the Unity project root (using the `claude` CLI when it is on `PATH`, otherwise writing the file directly). The `Claude Desktop` card writes the desktop app's `claude_desktop_config.json`. After running `Auto Setup` for Claude Code, run `/mcp` inside Claude Code (or restart it) to load the server. +> **Claude Code vs. Claude Desktop:** these are different products with different config locations. The `Claude Code` card registers the `nexus-unity` server in a project-scoped `.mcp.json` at the Unity project root (using the `claude` CLI when it is on `PATH`, otherwise writing the file directly). If the CLI cannot remove an existing registration, setup skips its add command and writes `.mcp.json` directly instead. The `Claude Desktop` card writes the desktop app's `claude_desktop_config.json`. After running `Auto Setup` for Claude Code, run `/mcp` inside Claude Code (or restart it) to load the server. Each integration card shows `Detected`, `Not found`, `Configured`, `Outdated`, or `Error` status and provides: @@ -122,7 +122,7 @@ Each integration card shows `Detected`, `Not found`, `Configured`, `Outdated`, o - `Copy Config` for manual setup. - `Open Config` when the client uses a known config file path. -Nexus Unity generates configs from the resolved Python interpreter and the deployed `nexus_unity_bridge.py` path. User/global config writes create a timestamped backup before modifying an existing file. +Nexus Unity generates configs from the resolved Python interpreter and the deployed `nexus_unity_bridge.py` path. Antigravity uses the workspace-local `.agents/mcp_config.json`; its older global Gemini config location is not used. User/global config writes create a timestamped backup before modifying an existing file. > **Windows / Linux:** The Python interpreter is resolved as `python3`, then `python`, then the Windows `py` launcher — make sure a Python 3 install is on `PATH`. CLI-based cards (Codex, Gemini, and the Claude Code CLI path) are detected with `where` on Windows and `which` on macOS/Linux, so the CLI must be on the `PATH` of the shell that launched Unity for `Auto Setup` to find it; otherwise use `Copy Config`. diff --git a/Tests~/Editor/MCPCliInstallerTests.cs b/Tests~/Editor/MCPCliInstallerTests.cs index 56cd509..b145992 100644 --- a/Tests~/Editor/MCPCliInstallerTests.cs +++ b/Tests~/Editor/MCPCliInstallerTests.cs @@ -8,6 +8,13 @@ namespace UnityMCP.Editor.Tests { public class MCPCliInstallerTests { + [Test] + public void ClaudeCodeMissingRegistrationIsSafeToAdd() + { + Assert.IsTrue(MCPCliInstaller.IsClaudeCodeRegistrationAbsent("MCP server 'nexus-unity' not found.")); + Assert.IsFalse(MCPCliInstaller.IsClaudeCodeRegistrationAbsent("Could not read project configuration.")); + } + [Test] public void CreateProcessStartInfoPreservesShellMetacharactersAsArguments() { diff --git a/Tests~/Editor/NexusMcpConfigGeneratorTests.cs b/Tests~/Editor/NexusMcpConfigGeneratorTests.cs index 1ffe0f0..8d0b241 100644 --- a/Tests~/Editor/NexusMcpConfigGeneratorTests.cs +++ b/Tests~/Editor/NexusMcpConfigGeneratorTests.cs @@ -76,6 +76,43 @@ public void WriteVsCodeConfigUsesServersRoot() } } + [Test] + public void AntigravityUsesWorkspaceMcpConfig() + { + string root = CreateTempRoot(); + try + { + string projectRoot = Path.Combine(root, "Project"); + var antigravity = NexusMcpConfigGenerator.BuildAll("/bridge.py", "/python3", projectRoot, Path.Combine(root, "Home")) + .First(item => item.Kind == NexusMcpClientKind.Antigravity); + + Assert.AreEqual(Path.Combine(projectRoot, ".agents", "mcp_config.json"), antigravity.ConfigPath); + Assert.AreEqual(NexusMcpConfigFormat.JsonMcpServers, antigravity.Format); + } + finally + { + DeleteTempRoot(root); + } + } + + [Test] + public void ClineUsesCurrentSharedSettingsPath() + { + string root = CreateTempRoot(); + try + { + string homeRoot = Path.Combine(root, "Home"); + var cline = NexusMcpConfigGenerator.BuildAll("/bridge.py", "/python3", Path.Combine(root, "Project"), homeRoot) + .First(item => item.Kind == NexusMcpClientKind.Cline); + + Assert.AreEqual(Path.Combine(homeRoot, ".cline", "data", "settings", "cline_mcp_settings.json"), cline.ConfigPath); + } + finally + { + DeleteTempRoot(root); + } + } + [Test] public void DetectsConfiguredJsonServer() {