diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs index d8aa8e3f5..498034767 100644 --- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs +++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs @@ -71,5 +71,7 @@ internal static class EditorPrefKeys internal const string LogRecordEnabled = "MCPForUnity.LogRecordEnabled"; internal const string ExecuteCodeCompiler = "MCPForUnity.ExecuteCode.Compiler"; + + internal const string MacOSTerminalApp = "MCPForUnity.MacOSTerminalApp"; } } diff --git a/MCPForUnity/Editor/Services/Server/TerminalLauncher.cs b/MCPForUnity/Editor/Services/Server/TerminalLauncher.cs index 49ec84817..69f0dd21c 100644 --- a/MCPForUnity/Editor/Services/Server/TerminalLauncher.cs +++ b/MCPForUnity/Editor/Services/Server/TerminalLauncher.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; +using UnityEditor; using UnityEngine; namespace MCPForUnity.Editor.Services.Server @@ -25,6 +27,21 @@ public string GetProjectRootPath() } } + /// + /// Resolves the macOS terminal application name, falling back to "Terminal" + /// when the configured value is unset, empty, or whitespace-only. + /// + internal static string ResolveMacTerminalApp(string configuredApp) + => string.IsNullOrWhiteSpace(configuredApp) ? "Terminal" : configuredApp.Trim(); + + /// + /// Builds the argument string for /usr/bin/open to launch the given + /// script in the configured macOS terminal application. The app name is quoted + /// so values containing spaces (e.g. "iTerm 2") resolve correctly. + /// + internal static string BuildMacOpenArguments(string configuredApp, string scriptPath) + => $"-a \"{ResolveMacTerminalApp(configuredApp)}\" \"{scriptPath}\""; + /// public System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(string command) { @@ -45,10 +62,11 @@ public System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(string "clear\n" + $"{command}\n"); ExecPath.TryRun("/bin/chmod", $"+x \"{scriptPath}\"", Application.dataPath, out _, out _, 3000); + string configuredApp = EditorPrefs.GetString(EditorPrefKeys.MacOSTerminalApp, "Terminal"); return new System.Diagnostics.ProcessStartInfo { FileName = "/usr/bin/open", - Arguments = $"-a Terminal \"{scriptPath}\"", + Arguments = BuildMacOpenArguments(configuredApp, scriptPath), UseShellExecute = false, CreateNoWindow = true }; diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs index b683a21a5..f11d80409 100644 --- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs +++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs @@ -77,6 +77,7 @@ public class EditorPrefsWindow : EditorWindow { EditorPrefKeys.LastLocalHttpServerPidArgsHash, EditorPrefType.String }, { EditorPrefKeys.LastLocalHttpServerPidFilePath, EditorPrefType.String }, { EditorPrefKeys.LastLocalHttpServerInstanceToken, EditorPrefType.String }, + { EditorPrefKeys.MacOSTerminalApp, EditorPrefType.String }, }; // Templates diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/Server/TerminalLauncherTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/Server/TerminalLauncherTests.cs index 4d1b629bc..c2affe15e 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/Server/TerminalLauncherTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/Server/TerminalLauncherTests.cs @@ -181,6 +181,65 @@ public void TerminalLauncher_CanBeUsedViaInterface() #endregion + #region macOS Terminal App Resolution Tests + + [Test] + public void ResolveMacTerminalApp_NullOrEmpty_FallsBackToTerminal() + { + Assert.AreEqual("Terminal", TerminalLauncher.ResolveMacTerminalApp(null)); + Assert.AreEqual("Terminal", TerminalLauncher.ResolveMacTerminalApp(string.Empty)); + } + + [Test] + public void ResolveMacTerminalApp_WhitespaceOnly_FallsBackToTerminal() + { + Assert.AreEqual("Terminal", TerminalLauncher.ResolveMacTerminalApp(" ")); + Assert.AreEqual("Terminal", TerminalLauncher.ResolveMacTerminalApp("\t\n")); + } + + [Test] + public void ResolveMacTerminalApp_CustomValue_IsUsed() + { + Assert.AreEqual("iTerm", TerminalLauncher.ResolveMacTerminalApp("iTerm")); + Assert.AreEqual("Warp", TerminalLauncher.ResolveMacTerminalApp("Warp")); + } + + [Test] + public void ResolveMacTerminalApp_TrimsSurroundingWhitespace() + { + Assert.AreEqual("iTerm", TerminalLauncher.ResolveMacTerminalApp(" iTerm ")); + } + + [Test] + public void BuildMacOpenArguments_DefaultsToTerminal_WhenUnset() + { + string args = TerminalLauncher.BuildMacOpenArguments("", "/tmp/mcp-terminal.command"); + Assert.AreEqual("-a \"Terminal\" \"/tmp/mcp-terminal.command\"", args); + } + + [Test] + public void BuildMacOpenArguments_QuotesCustomApp() + { + string args = TerminalLauncher.BuildMacOpenArguments("iTerm", "/tmp/mcp-terminal.command"); + Assert.AreEqual("-a \"iTerm\" \"/tmp/mcp-terminal.command\"", args); + } + + [Test] + public void BuildMacOpenArguments_QuotingHandlesAppNameWithSpaces() + { + string args = TerminalLauncher.BuildMacOpenArguments("iTerm 2", "/tmp/mcp-terminal.command"); + Assert.AreEqual("-a \"iTerm 2\" \"/tmp/mcp-terminal.command\"", args); + } + + [Test] + public void BuildMacOpenArguments_QuotingHandlesScriptPathWithSpaces() + { + string args = TerminalLauncher.BuildMacOpenArguments("Terminal", "/Users/me/My Project/mcp-terminal.command"); + Assert.AreEqual("-a \"Terminal\" \"/Users/me/My Project/mcp-terminal.command\"", args); + } + + #endregion + #region Platform-Specific Behavior Tests [Test]