diff --git a/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/McpServerManager.cs b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/McpServerManager.cs index da71b511e..b5930de78 100644 --- a/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/McpServerManager.cs +++ b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Editor/Scripts/McpServerManager.cs @@ -52,9 +52,23 @@ public enum McpServerStatus [InitializeOnLoad] public static class McpServerManager { - const string ProcessIdKey = "McpServerManager_ProcessId"; + // Qualified by the server port so two Editors (different projects, different ports) on + // the same machine never share this slot. EditorPrefs are machine-global - shared across + // every project of the same Editor version - so an UNqualified key let one project's + // CheckExistingProcess adopt the OTHER project's server PID and then tear it down on stop, + // making the two Editors mutually exclusive even though their ports differ. See ProcessIdKeyForPort. + static string ProcessIdKey => ProcessIdKeyForPort(UnityMcpPluginEditor.Port); const string McpServerProcessName = "gamedev-mcp-server"; + /// + /// The EditorPrefs key under which this project's running server PID is stored, qualified by + /// the server . EditorPrefs are shared across every project of the same + /// Editor version on the machine, so this key MUST be port-qualified: otherwise two Editors + /// clobber a single global slot and adopt/terminate each other's server processes. Pure (no + /// Unity API access) so it can be unit-tested in EditMode. + /// + internal static string ProcessIdKeyForPort(int port) => $"McpServerManager_ProcessId_{port}"; + static readonly ILogger _logger = UnityLoggerFactory.LoggerFactory.CreateLogger(typeof(McpServerManager)); static readonly ReactiveProperty _serverStatus = new(McpServerStatus.Stopped); static readonly object _processMutex = new(); diff --git a/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs new file mode 100644 index 000000000..5efa374f0 --- /dev/null +++ b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs @@ -0,0 +1,53 @@ +/* +┌──────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the Apache License, Version 2.0. │ +│ See the LICENSE file in the project root for more information. │ +└──────────────────────────────────────────────────────────────────┘ +*/ + +#nullable enable +using NUnit.Framework; + +namespace com.IvanMurzak.Unity.MCP.Editor.Tests +{ + /// + /// Regression tests: the EditorPrefs key that stores the running server PID must be qualified by + /// the server port. EditorPrefs are shared across every project of the same Editor version on a + /// machine, so an unqualified key let two Editors (different projects, different ports) clobber a + /// single global slot — one project's CheckExistingProcess would adopt the other's server + /// PID and terminate it on stop, making the two Editors mutually exclusive despite distinct ports. + /// + /// is the pure helper behind the key. + /// + public class McpServerManagerProcessIdKeyTests + { + [Test] + public void ProcessIdKeyForPort_DifferentPorts_ProduceDifferentKeys() + { + Assert.AreNotEqual( + McpServerManager.ProcessIdKeyForPort(26645), + McpServerManager.ProcessIdKeyForPort(26646), + "Two projects on different ports must not share the same PID key, " + + "or they tear down each other's servers."); + } + + [Test] + public void ProcessIdKeyForPort_SamePort_IsStable() + { + Assert.AreEqual( + McpServerManager.ProcessIdKeyForPort(26646), + McpServerManager.ProcessIdKeyForPort(26646), + "The key for a given port must be deterministic across calls so the stored PID " + + "can be read back and deleted."); + } + + [Test] + public void ProcessIdKeyForPort_IncludesPort() + { + StringAssert.Contains("26646", McpServerManager.ProcessIdKeyForPort(26646)); + } + } +} diff --git a/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs.meta b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs.meta new file mode 100644 index 000000000..7fe6e68e5 --- /dev/null +++ b/Unity-MCP-Plugin/Packages/com.ivanmurzak.unity.mcp/Tests/Editor/McpServerManagerProcessIdKeyTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be9a124f208946b2b5197e3f1de6fdfa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: