Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
/// The EditorPrefs key under which this project's running server PID is stored, qualified by
/// the server <paramref name="port"/>. 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.
/// </summary>
internal static string ProcessIdKeyForPort(int port) => $"McpServerManager_ProcessId_{port}";

static readonly ILogger _logger = UnityLoggerFactory.LoggerFactory.CreateLogger(typeof(McpServerManager));
static readonly ReactiveProperty<McpServerStatus> _serverStatus = new(McpServerStatus.Stopped);
static readonly object _processMutex = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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 <c>CheckExistingProcess</c> would adopt the other's server
/// PID and terminate it on stop, making the two Editors mutually exclusive despite distinct ports.
///
/// <see cref="McpServerManager.ProcessIdKeyForPort"/> is the pure helper behind the key.
/// </summary>
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));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading