Skip to content

Commit 3f03337

Browse files
authored
Merge branch 'CoplayDev:main' into main
2 parents 2127444 + aa47838 commit 3f03337

File tree

16 files changed

+382
-63
lines changed

16 files changed

+382
-63
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEngine.Rendering;
4+
5+
namespace MCPForUnity.Editor.Helpers
6+
{
7+
internal static class RenderPipelineUtility
8+
{
9+
internal enum PipelineKind
10+
{
11+
BuiltIn,
12+
Universal,
13+
HighDefinition,
14+
Custom
15+
}
16+
17+
private static readonly string[] BuiltInLitShaders = { "Standard", "Legacy Shaders/Diffuse" };
18+
private static readonly string[] BuiltInUnlitShaders = { "Unlit/Color", "Unlit/Texture" };
19+
private static readonly string[] UrpLitShaders = { "Universal Render Pipeline/Lit", "Universal Render Pipeline/Simple Lit" };
20+
private static readonly string[] UrpUnlitShaders = { "Universal Render Pipeline/Unlit" };
21+
private static readonly string[] HdrpLitShaders = { "HDRP/Lit", "High Definition Render Pipeline/Lit" };
22+
private static readonly string[] HdrpUnlitShaders = { "HDRP/Unlit", "High Definition Render Pipeline/Unlit" };
23+
24+
internal static PipelineKind GetActivePipeline()
25+
{
26+
var asset = GraphicsSettings.currentRenderPipeline;
27+
if (asset == null)
28+
{
29+
return PipelineKind.BuiltIn;
30+
}
31+
32+
var typeName = asset.GetType().FullName ?? string.Empty;
33+
if (typeName.IndexOf("HighDefinition", StringComparison.OrdinalIgnoreCase) >= 0 ||
34+
typeName.IndexOf("HDRP", StringComparison.OrdinalIgnoreCase) >= 0)
35+
{
36+
return PipelineKind.HighDefinition;
37+
}
38+
39+
if (typeName.IndexOf("Universal", StringComparison.OrdinalIgnoreCase) >= 0 ||
40+
typeName.IndexOf("URP", StringComparison.OrdinalIgnoreCase) >= 0)
41+
{
42+
return PipelineKind.Universal;
43+
}
44+
45+
return PipelineKind.Custom;
46+
}
47+
48+
internal static Shader ResolveShader(string requestedNameOrAlias)
49+
{
50+
var pipeline = GetActivePipeline();
51+
52+
if (!string.IsNullOrWhiteSpace(requestedNameOrAlias))
53+
{
54+
var alias = requestedNameOrAlias.Trim();
55+
var aliasMatch = ResolveAlias(alias, pipeline);
56+
if (aliasMatch != null)
57+
{
58+
WarnIfPipelineMismatch(aliasMatch.name, pipeline);
59+
return aliasMatch;
60+
}
61+
62+
var direct = Shader.Find(alias);
63+
if (direct != null)
64+
{
65+
WarnIfPipelineMismatch(direct.name, pipeline);
66+
return direct;
67+
}
68+
69+
McpLog.Warn($"Shader '{alias}' not found. Falling back to {pipeline} defaults.");
70+
}
71+
72+
var fallback = ResolveDefaultLitShader(pipeline)
73+
?? ResolveDefaultLitShader(PipelineKind.BuiltIn)
74+
?? Shader.Find("Unlit/Color");
75+
76+
if (fallback != null)
77+
{
78+
WarnIfPipelineMismatch(fallback.name, pipeline);
79+
}
80+
81+
return fallback;
82+
}
83+
84+
internal static Shader ResolveDefaultLitShader(PipelineKind pipeline)
85+
{
86+
return pipeline switch
87+
{
88+
PipelineKind.HighDefinition => TryFindShader(HdrpLitShaders) ?? TryFindShader(UrpLitShaders),
89+
PipelineKind.Universal => TryFindShader(UrpLitShaders) ?? TryFindShader(HdrpLitShaders),
90+
PipelineKind.Custom => TryFindShader(BuiltInLitShaders) ?? TryFindShader(UrpLitShaders) ?? TryFindShader(HdrpLitShaders),
91+
_ => TryFindShader(BuiltInLitShaders) ?? Shader.Find("Unlit/Color")
92+
};
93+
}
94+
95+
internal static Shader ResolveDefaultUnlitShader(PipelineKind pipeline)
96+
{
97+
return pipeline switch
98+
{
99+
PipelineKind.HighDefinition => TryFindShader(HdrpUnlitShaders) ?? TryFindShader(UrpUnlitShaders) ?? TryFindShader(BuiltInUnlitShaders),
100+
PipelineKind.Universal => TryFindShader(UrpUnlitShaders) ?? TryFindShader(HdrpUnlitShaders) ?? TryFindShader(BuiltInUnlitShaders),
101+
PipelineKind.Custom => TryFindShader(BuiltInUnlitShaders) ?? TryFindShader(UrpUnlitShaders) ?? TryFindShader(HdrpUnlitShaders),
102+
_ => TryFindShader(BuiltInUnlitShaders)
103+
};
104+
}
105+
106+
private static Shader ResolveAlias(string alias, PipelineKind pipeline)
107+
{
108+
if (string.Equals(alias, "lit", StringComparison.OrdinalIgnoreCase) ||
109+
string.Equals(alias, "default", StringComparison.OrdinalIgnoreCase) ||
110+
string.Equals(alias, "default_lit", StringComparison.OrdinalIgnoreCase))
111+
{
112+
return ResolveDefaultLitShader(pipeline);
113+
}
114+
115+
if (string.Equals(alias, "unlit", StringComparison.OrdinalIgnoreCase))
116+
{
117+
return ResolveDefaultUnlitShader(pipeline);
118+
}
119+
120+
if (string.Equals(alias, "urp_lit", StringComparison.OrdinalIgnoreCase))
121+
{
122+
return TryFindShader(UrpLitShaders);
123+
}
124+
125+
if (string.Equals(alias, "hdrp_lit", StringComparison.OrdinalIgnoreCase))
126+
{
127+
return TryFindShader(HdrpLitShaders);
128+
}
129+
130+
if (string.Equals(alias, "built_in_lit", StringComparison.OrdinalIgnoreCase))
131+
{
132+
return TryFindShader(BuiltInLitShaders);
133+
}
134+
135+
return null;
136+
}
137+
138+
private static Shader TryFindShader(params string[] shaderNames)
139+
{
140+
foreach (var shaderName in shaderNames)
141+
{
142+
var shader = Shader.Find(shaderName);
143+
if (shader != null)
144+
{
145+
return shader;
146+
}
147+
}
148+
return null;
149+
}
150+
151+
private static void WarnIfPipelineMismatch(string shaderName, PipelineKind activePipeline)
152+
{
153+
if (string.IsNullOrEmpty(shaderName))
154+
{
155+
return;
156+
}
157+
158+
var lowerName = shaderName.ToLowerInvariant();
159+
bool shaderLooksUrp = lowerName.Contains("universal render pipeline") || lowerName.Contains("urp/");
160+
bool shaderLooksHdrp = lowerName.Contains("high definition render pipeline") || lowerName.Contains("hdrp/");
161+
bool shaderLooksBuiltin = lowerName.Contains("standard") || lowerName.Contains("legacy shaders/");
162+
bool shaderLooksSrp = shaderLooksUrp || shaderLooksHdrp;
163+
164+
switch (activePipeline)
165+
{
166+
case PipelineKind.HighDefinition:
167+
if (shaderLooksUrp)
168+
{
169+
McpLog.Warn($"[RenderPipelineUtility] Active pipeline is HDRP but shader '{shaderName}' looks URP-based. Asset may appear incorrect.");
170+
}
171+
else if (shaderLooksBuiltin && !shaderLooksHdrp)
172+
{
173+
McpLog.Warn($"[RenderPipelineUtility] Active pipeline is HDRP but shader '{shaderName}' looks Built-in. Consider using an HDRP shader for correct results.");
174+
}
175+
break;
176+
case PipelineKind.Universal:
177+
if (shaderLooksHdrp)
178+
{
179+
McpLog.Warn($"[RenderPipelineUtility] Active pipeline is URP but shader '{shaderName}' looks HDRP-based. Asset may appear incorrect.");
180+
}
181+
else if (shaderLooksBuiltin && !shaderLooksUrp)
182+
{
183+
McpLog.Warn($"[RenderPipelineUtility] Active pipeline is URP but shader '{shaderName}' looks Built-in. Consider using a URP shader for correct results.");
184+
}
185+
break;
186+
case PipelineKind.BuiltIn:
187+
if (shaderLooksSrp)
188+
{
189+
McpLog.Warn($"[RenderPipelineUtility] Active pipeline is Built-in but shader '{shaderName}' targets URP/HDRP. Asset may not render as expected.");
190+
}
191+
break;
192+
}
193+
}
194+
}
195+
}

MCPForUnity/Editor/Helpers/RenderPipelineUtility.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Tools/ManageAsset.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,20 +196,26 @@ private static object CreateAsset(JObject @params)
196196
}
197197
else if (lowerAssetType == "material")
198198
{
199-
// Prefer provided shader; fall back to common pipelines
200199
var requested = properties?["shader"]?.ToString();
201-
Shader shader =
202-
(!string.IsNullOrEmpty(requested) ? Shader.Find(requested) : null)
203-
?? Shader.Find("Universal Render Pipeline/Lit")
204-
?? Shader.Find("HDRP/Lit")
205-
?? Shader.Find("Standard")
206-
?? Shader.Find("Unlit/Color");
200+
Shader shader = RenderPipelineUtility.ResolveShader(requested);
207201
if (shader == null)
208-
return new ErrorResponse($"Could not find a suitable shader (requested: '{requested ?? "none"}').");
202+
return new ErrorResponse($"Could not find a project-compatible shader (requested: '{requested ?? "none"}'). Consider installing URP/HDRP or provide an explicit shader path.");
209203

210204
var mat = new Material(shader);
211205
if (properties != null)
212-
ApplyMaterialProperties(mat, properties);
206+
{
207+
JObject propertiesForApply = properties;
208+
if (propertiesForApply["shader"] != null)
209+
{
210+
propertiesForApply = (JObject)properties.DeepClone();
211+
propertiesForApply.Remove("shader");
212+
}
213+
214+
if (propertiesForApply.HasValues)
215+
{
216+
ApplyMaterialProperties(mat, propertiesForApply);
217+
}
218+
}
213219
AssetDatabase.CreateAsset(mat, fullPath);
214220
newAsset = mat;
215221
}
@@ -901,7 +907,8 @@ private static bool ApplyMaterialProperties(Material mat, JObject properties)
901907
// Example: Set shader
902908
if (properties["shader"]?.Type == JTokenType.String)
903909
{
904-
Shader newShader = Shader.Find(properties["shader"].ToString());
910+
string shaderRequest = properties["shader"].ToString();
911+
Shader newShader = RenderPipelineUtility.ResolveShader(shaderRequest);
905912
if (newShader != null && mat.shader != newShader)
906913
{
907914
mat.shader = newShader;

MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,21 @@ private void ScheduleHealthCheck()
347347
{
348348
EditorApplication.delayCall += async () =>
349349
{
350+
// Ensure window and components are still valid before execution
350351
if (this == null || connectionSection == null)
351352
{
352353
return;
353354
}
354-
await connectionSection.VerifyBridgeConnectionAsync();
355+
356+
try
357+
{
358+
await connectionSection.VerifyBridgeConnectionAsync();
359+
}
360+
catch (Exception ex)
361+
{
362+
// Log but don't crash if verification fails during cleanup
363+
McpLog.Warn($"Health check verification failed: {ex.Message}");
364+
}
355365
};
356366
}
357367
}

MCPForUnity/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.coplaydev.unity-mcp",
3-
"version": "8.1.4",
3+
"version": "8.1.5",
44
"displayName": "MCP for Unity",
55
"description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4",
66
"unity": "2021.3",

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ claude mcp add --scope user UnityMCP -- "C:/Users/USERNAME/AppData/Local/Microso
246246
```json
247247
{
248248
"mcpServers": {
249-
"UnityMCP": {
249+
"unityMCP": {
250250
"url": "http://localhost:8080/mcp"
251251
}
252252
}
@@ -285,7 +285,7 @@ Switch the Unity transport dropdown to `Stdio`, then use one of the following `c
285285
```json
286286
{
287287
"mcpServers": {
288-
"UnityMCP": {
288+
"unityMCP": {
289289
"command": "uv",
290290
"args": [
291291
"run",
@@ -305,7 +305,7 @@ Switch the Unity transport dropdown to `Stdio`, then use one of the following `c
305305
```json
306306
{
307307
"mcpServers": {
308-
"UnityMCP": {
308+
"unityMCP": {
309309
"command": "C:/Users/YOUR_USERNAME/AppData/Local/Microsoft/WinGet/Links/uv.exe",
310310
"args": [
311311
"run",

Server/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ Run directly from GitHub without installation:
2323

2424
```bash
2525
# HTTP (default)
26-
uvx --from git+https://github.com/CoplayDev/[email protected].4#subdirectory=Server \
26+
uvx --from git+https://github.com/CoplayDev/[email protected].5#subdirectory=Server \
2727
mcp-for-unity --transport http --http-url http://localhost:8080
2828

2929
# Stdio
30-
uvx --from git+https://github.com/CoplayDev/[email protected].4#subdirectory=Server \
30+
uvx --from git+https://github.com/CoplayDev/[email protected].5#subdirectory=Server \
3131
mcp-for-unity --transport stdio
3232
```
3333

@@ -52,7 +52,7 @@ uvx --from git+https://github.com/CoplayDev/[email protected]#subdirectory=Server
5252
"command": "uvx",
5353
"args": [
5454
"--from",
55-
"git+https://github.com/CoplayDev/[email protected].4#subdirectory=Server",
55+
"git+https://github.com/CoplayDev/[email protected].5#subdirectory=Server",
5656
"mcp-for-unity",
5757
"--transport",
5858
"stdio"

Server/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "MCPForUnityServer"
3-
version = "8.1.4"
3+
version = "8.1.5"
44
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
55
readme = "README.md"
66
requires-python = ">=3.10"

Server/src/main.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
from core.telemetry import record_milestone, record_telemetry, MilestoneType, RecordType, get_package_version
2323
from services.tools import register_all_tools
2424
from transport.legacy.unity_connection import get_unity_connection_pool, UnityConnectionPool
25-
from transport.unity_instance_middleware import UnityInstanceMiddleware, set_unity_instance_middleware
25+
from transport.unity_instance_middleware import (
26+
UnityInstanceMiddleware,
27+
get_unity_instance_middleware
28+
)
2629

2730
# Configure logging using settings from config
2831
logging.basicConfig(
@@ -44,22 +47,25 @@
4447
_fh.setFormatter(logging.Formatter(config.log_format))
4548
_fh.setLevel(getattr(logging, config.log_level))
4649
logger.addHandler(_fh)
50+
logger.propagate = False # Prevent double logging to root logger
4751
# Also route telemetry logger to the same rotating file and normal level
4852
try:
4953
tlog = logging.getLogger("unity-mcp-telemetry")
5054
tlog.setLevel(getattr(logging, config.log_level))
5155
tlog.addHandler(_fh)
52-
except Exception:
56+
tlog.propagate = False # Prevent double logging for telemetry too
57+
except Exception as exc:
5358
# Never let logging setup break startup
54-
pass
55-
except Exception:
59+
logger.debug("Failed to configure telemetry logger", exc_info=exc)
60+
except Exception as exc:
5661
# Never let logging setup break startup
57-
pass
62+
logger.debug("Failed to configure main logger file handler", exc_info=exc)
5863
# Quieten noisy third-party loggers to avoid clutter during stdio handshake
59-
for noisy in ("httpx", "urllib3"):
64+
for noisy in ("httpx", "urllib3", "mcp.server.lowlevel.server"):
6065
try:
6166
logging.getLogger(noisy).setLevel(
6267
max(logging.WARNING, getattr(logging, config.log_level)))
68+
logging.getLogger(noisy).propagate = False
6369
except Exception:
6470
pass
6571

@@ -258,8 +264,8 @@ async def plugin_sessions_route(_: Request) -> JSONResponse:
258264

259265

260266
# Initialize and register middleware for session-based Unity instance routing
261-
unity_middleware = UnityInstanceMiddleware()
262-
set_unity_instance_middleware(unity_middleware)
267+
# Using the singleton getter ensures we use the same instance everywhere
268+
unity_middleware = get_unity_instance_middleware()
263269
mcp.add_middleware(unity_middleware)
264270
logger.info("Registered Unity instance middleware for session-based routing")
265271

0 commit comments

Comments
 (0)