Skip to content

Commit 9d5b9c6

Browse files
authored
Merge pull request #1 from dsarno/fix/multi-instance
Fix/multi-instance & resolve merge conflicts
2 parents aa01fe6 + 198a4ff commit 9d5b9c6

File tree

185 files changed

+1237
-28298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

185 files changed

+1237
-28298
lines changed

MCPForUnity/Editor/Dependencies/DependencyManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private static void GenerateRecommendations(DependencyCheckResult result, IPlatf
126126
{
127127
if (dep.Name == "Python")
128128
{
129-
result.RecommendedActions.Add($"Install Python 3.11+ from: {detector.GetPythonInstallUrl()}");
129+
result.RecommendedActions.Add($"Install Python 3.10+ from: {detector.GetPythonInstallUrl()}");
130130
}
131131
else if (dep.Name == "UV Package Manager")
132132
{

MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override DependencyStatus DetectPython()
6262
}
6363
}
6464

65-
status.ErrorMessage = "Python not found. Please install Python 3.11 or later.";
65+
status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
6666
status.Details = "Checked common installation paths including system, snap, and user-local locations.";
6767
}
6868
catch (Exception ex)
@@ -144,10 +144,10 @@ private bool TryValidatePython(string pythonPath, out string version, out string
144144
version = output.Substring(7); // Remove "Python " prefix
145145
fullPath = pythonPath;
146146

147-
// Validate minimum version (Python 4+ or Python 3.11+)
147+
// Validate minimum version (Python 4+ or Python 3.10+)
148148
if (TryParseVersion(version, out var major, out var minor))
149149
{
150-
return major > 3 || (major >= 3 && minor >= 11);
150+
return major > 3 || (major >= 3 && minor >= 10);
151151
}
152152
}
153153
}

MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ public override DependencyStatus DetectPython()
3333
"/usr/bin/python3",
3434
"/usr/local/bin/python3",
3535
"/opt/homebrew/bin/python3",
36+
"/Library/Frameworks/Python.framework/Versions/3.14/bin/python3",
3637
"/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
3738
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3",
38-
"/Library/Frameworks/Python.framework/Versions/3.11/bin/python3"
39+
"/Library/Frameworks/Python.framework/Versions/3.11/bin/python3",
40+
"/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
3941
};
4042

4143
foreach (var candidate in candidates)
@@ -64,7 +66,7 @@ public override DependencyStatus DetectPython()
6466
}
6567
}
6668

67-
status.ErrorMessage = "Python not found. Please install Python 3.11 or later.";
69+
status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
6870
status.Details = "Checked common installation paths including Homebrew, Framework, and system locations.";
6971
}
7072
catch (Exception ex)
@@ -143,10 +145,10 @@ private bool TryValidatePython(string pythonPath, out string version, out string
143145
version = output.Substring(7); // Remove "Python " prefix
144146
fullPath = pythonPath;
145147

146-
// Validate minimum version (Python 4+ or Python 3.11+)
148+
// Validate minimum version (Python 4+ or Python 3.10+)
147149
if (TryParseVersion(version, out var major, out var minor))
148150
{
149-
return major > 3 || (major >= 3 && minor >= 11);
151+
return major > 3 || (major >= 3 && minor >= 10);
150152
}
151153
}
152154
}

MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,26 @@ public override DependencyStatus DetectPython()
3030
{
3131
"python.exe",
3232
"python3.exe",
33+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
34+
"Programs", "Python", "Python314", "python.exe"),
3335
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
3436
"Programs", "Python", "Python313", "python.exe"),
3537
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
3638
"Programs", "Python", "Python312", "python.exe"),
3739
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
3840
"Programs", "Python", "Python311", "python.exe"),
41+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
42+
"Programs", "Python", "Python310", "python.exe"),
43+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
44+
"Python314", "python.exe"),
3945
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
4046
"Python313", "python.exe"),
4147
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
42-
"Python312", "python.exe")
48+
"Python312", "python.exe"),
49+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
50+
"Python311", "python.exe"),
51+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
52+
"Python310", "python.exe")
4353
};
4454

4555
foreach (var candidate in candidates)
@@ -68,7 +78,7 @@ public override DependencyStatus DetectPython()
6878
}
6979
}
7080

71-
status.ErrorMessage = "Python not found. Please install Python 3.11 or later.";
81+
status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
7282
status.Details = "Checked common installation paths and PATH environment variable.";
7383
}
7484
catch (Exception ex)
@@ -94,7 +104,7 @@ public override string GetInstallationRecommendations()
94104
return @"Windows Installation Recommendations:
95105
96106
1. Python: Install from Microsoft Store or python.org
97-
- Microsoft Store: Search for 'Python 3.12' or 'Python 3.13'
107+
- Microsoft Store: Search for 'Python 3.10' or higher
98108
- Direct download: https://python.org/downloads/windows/
99109
100110
2. UV Package Manager: Install via PowerShell
@@ -132,10 +142,10 @@ private bool TryValidatePython(string pythonPath, out string version, out string
132142
version = output.Substring(7); // Remove "Python " prefix
133143
fullPath = pythonPath;
134144

135-
// Validate minimum version (Python 4+ or Python 3.11+)
145+
// Validate minimum version (Python 4+ or Python 3.10+)
136146
if (TryParseVersion(version, out var major, out var minor))
137147
{
138-
return major > 3 || (major >= 3 && minor >= 11);
148+
return major > 3 || (major >= 3 && minor >= 10);
139149
}
140150
}
141151
}

MCPForUnity/Editor/Helpers/ServerInstaller.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,10 +733,12 @@ internal static string FindUvPath()
733733
Path.Combine(programFiles, "WinGet", "Links", "uv.exe"),
734734

735735
// Common per-user installs
736+
Path.Combine(localAppData, @"Programs\Python\Python314\Scripts\uv.exe"),
736737
Path.Combine(localAppData, @"Programs\Python\Python313\Scripts\uv.exe"),
737738
Path.Combine(localAppData, @"Programs\Python\Python312\Scripts\uv.exe"),
738739
Path.Combine(localAppData, @"Programs\Python\Python311\Scripts\uv.exe"),
739740
Path.Combine(localAppData, @"Programs\Python\Python310\Scripts\uv.exe"),
741+
Path.Combine(appData, @"Python\Python314\Scripts\uv.exe"),
740742
Path.Combine(appData, @"Python\Python313\Scripts\uv.exe"),
741743
Path.Combine(appData, @"Python\Python312\Scripts\uv.exe"),
742744
Path.Combine(appData, @"Python\Python311\Scripts\uv.exe"),
@@ -761,8 +763,11 @@ internal static string FindUvPath()
761763
Path.Combine(home, ".local", "bin", "uv"),
762764
"/opt/homebrew/opt/uv/bin/uv",
763765
// Framework Python installs
766+
"/Library/Frameworks/Python.framework/Versions/3.14/bin/uv",
764767
"/Library/Frameworks/Python.framework/Versions/3.13/bin/uv",
765768
"/Library/Frameworks/Python.framework/Versions/3.12/bin/uv",
769+
"/Library/Frameworks/Python.framework/Versions/3.11/bin/uv",
770+
"/Library/Frameworks/Python.framework/Versions/3.10/bin/uv",
766771
// Fallback to PATH resolution by name
767772
"uv"
768773
};

MCPForUnity/Editor/Services/PathResolverService.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,21 @@ public bool IsPythonDetected()
7676
// Common Windows Python installation paths
7777
string[] windowsCandidates =
7878
{
79+
@"C:\Python314\python.exe",
7980
@"C:\Python313\python.exe",
8081
@"C:\Python312\python.exe",
8182
@"C:\Python311\python.exe",
8283
@"C:\Python310\python.exe",
83-
@"C:\Python39\python.exe",
84+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python314\python.exe"),
8485
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python313\python.exe"),
8586
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python312\python.exe"),
8687
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python311\python.exe"),
8788
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python310\python.exe"),
88-
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39\python.exe"),
89+
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python314\python.exe"),
8990
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python313\python.exe"),
9091
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python312\python.exe"),
9192
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python311\python.exe"),
9293
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python310\python.exe"),
93-
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python39\python.exe"),
9494
};
9595

9696
foreach (string c in windowsCandidates)
@@ -134,8 +134,11 @@ public bool IsPythonDetected()
134134
"/usr/bin/python3",
135135
"/opt/local/bin/python3",
136136
Path.Combine(home, ".local", "bin", "python3"),
137+
"/Library/Frameworks/Python.framework/Versions/3.14/bin/python3",
137138
"/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
138139
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3",
140+
"/Library/Frameworks/Python.framework/Versions/3.11/bin/python3",
141+
"/Library/Frameworks/Python.framework/Versions/3.10/bin/python3",
139142
};
140143
foreach (string c in candidates)
141144
{

MCPForUnity/Editor/Setup/SetupWizardWindow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private void DrawSetupStep()
120120
{
121121
// Only show critical warnings when dependencies are actually missing
122122
EditorGUILayout.HelpBox(
123-
"\u26A0 Missing Dependencies: MCP for Unity requires Python 3.11+ and UV package manager to function properly.",
123+
"\u26A0 Missing Dependencies: MCP for Unity requires Python 3.10+ and UV package manager to function properly.",
124124
MessageType.Warning
125125
);
126126

MCPForUnity/UnityMcpServer~/src/port_discovery.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from datetime import datetime
2020
from pathlib import Path
2121
import socket
22-
from typing import Optional, List
22+
from typing import Optional, List, Dict
2323

2424
from models import UnityInstanceInfo
2525

@@ -228,7 +228,7 @@ def discover_all_unity_instances() -> List[UnityInstanceInfo]:
228228
Returns:
229229
List of UnityInstanceInfo objects for all discovered instances
230230
"""
231-
instances = []
231+
instances_by_port: Dict[int, tuple[UnityInstanceInfo, datetime]] = {}
232232
base = PortDiscovery.get_registry_dir()
233233

234234
# Scan all status files
@@ -237,7 +237,10 @@ def discover_all_unity_instances() -> List[UnityInstanceInfo]:
237237

238238
for status_file_path in status_files:
239239
try:
240-
with open(status_file_path, 'r') as f:
240+
status_path = Path(status_file_path)
241+
file_mtime = datetime.fromtimestamp(status_path.stat().st_mtime)
242+
243+
with status_path.open('r') as f:
241244
data = json.load(f)
242245

243246
# Extract hash from filename: unity-mcp-status-{hash}.json
@@ -266,6 +269,19 @@ def discover_all_unity_instances() -> List[UnityInstanceInfo]:
266269
logger.debug(f"Instance {project_name}@{hash_value} has heartbeat but port {port} not responding")
267270
continue
268271

272+
freshness = last_heartbeat or file_mtime
273+
274+
existing = instances_by_port.get(port)
275+
if existing:
276+
_, existing_time = existing
277+
if existing_time >= freshness:
278+
logger.debug(
279+
"Skipping stale status entry %s in favor of more recent data for port %s",
280+
status_path.name,
281+
port,
282+
)
283+
continue
284+
269285
# Create instance info
270286
instance = UnityInstanceInfo(
271287
id=f"{project_name}@{hash_value}",
@@ -278,12 +294,14 @@ def discover_all_unity_instances() -> List[UnityInstanceInfo]:
278294
unity_version=data.get('unity_version') # May not be available in current version
279295
)
280296

281-
instances.append(instance)
297+
instances_by_port[port] = (instance, freshness)
282298
logger.debug(f"Discovered Unity instance: {instance.id} on port {instance.port}")
283299

284300
except Exception as e:
285301
logger.debug(f"Failed to parse status file {status_file_path}: {e}")
286302
continue
287303

288-
logger.info(f"Discovered {len(instances)} Unity instances")
289-
return instances
304+
deduped_instances = [entry[0] for entry in sorted(instances_by_port.values(), key=lambda item: item[1], reverse=True)]
305+
306+
logger.info(f"Discovered {len(deduped_instances)} Unity instances (after de-duplication by port)")
307+
return deduped_instances

MCPForUnity/UnityMcpServer~/src/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "MCPForUnityServer"
33
version = "6.3.0"
44
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
55
readme = "README.md"
6-
requires-python = ">=3.11"
6+
requires-python = ">=3.10"
77
dependencies = [
88
"httpx>=0.27.2",
99
"fastmcp>=2.13.0",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Tool to list all available Unity Editor instances.
3+
"""
4+
from typing import Annotated, Any
5+
6+
from fastmcp import Context
7+
from registry import mcp_for_unity_tool
8+
from unity_connection import get_unity_connection_pool
9+
10+
11+
@mcp_for_unity_tool(description="List all running Unity Editor instances with their details.")
12+
def list_unity_instances(
13+
ctx: Context,
14+
force_refresh: Annotated[bool, "Force refresh the instance list, bypassing cache"] = False
15+
) -> dict[str, Any]:
16+
"""
17+
List all available Unity Editor instances.
18+
19+
Returns information about each instance including:
20+
- id: Unique identifier (ProjectName@hash)
21+
- name: Project name
22+
- path: Full project path
23+
- hash: 8-character hash of project path
24+
- port: TCP port number
25+
- status: Current status (running, reloading, etc.)
26+
- last_heartbeat: Last heartbeat timestamp
27+
- unity_version: Unity version (if available)
28+
29+
Args:
30+
force_refresh: If True, bypass cache and scan immediately
31+
32+
Returns:
33+
Dictionary containing list of instances and metadata
34+
"""
35+
ctx.info(f"Listing Unity instances (force_refresh={force_refresh})")
36+
37+
try:
38+
pool = get_unity_connection_pool()
39+
instances = pool.discover_all_instances(force_refresh=force_refresh)
40+
41+
# Check for duplicate project names
42+
name_counts = {}
43+
for inst in instances:
44+
name_counts[inst.name] = name_counts.get(inst.name, 0) + 1
45+
46+
duplicates = [name for name, count in name_counts.items() if count > 1]
47+
48+
result = {
49+
"success": True,
50+
"instance_count": len(instances),
51+
"instances": [inst.to_dict() for inst in instances],
52+
}
53+
54+
if duplicates:
55+
result["warning"] = (
56+
f"Multiple instances found with duplicate project names: {duplicates}. "
57+
f"Use full format (e.g., 'ProjectName@hash') to specify which instance."
58+
)
59+
60+
return result
61+
62+
except Exception as e:
63+
ctx.error(f"Error listing Unity instances: {e}")
64+
return {
65+
"success": False,
66+
"error": f"Failed to list Unity instances: {str(e)}",
67+
"instance_count": 0,
68+
"instances": []
69+
}
70+

0 commit comments

Comments
 (0)