Skip to content

Commit 00fad91

Browse files
committed
Merge branch 'main' of https://github.com/CoplayDev/unity-mcp into main
2 parents 9dff8f1 + 8f227ff commit 00fad91

Some content is hidden

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

47 files changed

+7200
-51
lines changed

.github/workflows/unity-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Unity Tests
22

33
on:
4+
workflow_dispatch: {}
45
push:
56
branches: [main]
67
paths:

MCPForUnity/Editor/Tools/ManageAsset.cs

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ public static object HandleCommand(JObject @params)
8888
case "create":
8989
return CreateAsset(@params);
9090
case "modify":
91-
return ModifyAsset(path, @params["properties"] as JObject);
91+
var properties = @params["properties"] as JObject;
92+
return ModifyAsset(path, properties);
9293
case "delete":
9394
return DeleteAsset(path);
9495
case "duplicate":
@@ -988,28 +989,43 @@ private static bool ApplyMaterialProperties(Material mat, JObject properties)
988989
}
989990
}
990991
}
991-
// Example: Set texture property
992-
if (properties["texture"] is JObject texProps)
992+
// Example: Set texture property (case-insensitive key and subkeys)
993993
{
994-
string propName = texProps["name"]?.ToString() ?? "_MainTex"; // Default main texture
995-
string texPath = texProps["path"]?.ToString();
996-
if (!string.IsNullOrEmpty(texPath))
994+
JObject texProps = null;
995+
var direct = properties.Property("texture");
996+
if (direct != null && direct.Value is JObject t0) texProps = t0;
997+
if (texProps == null)
997998
{
998-
Texture newTex = AssetDatabase.LoadAssetAtPath<Texture>(
999-
AssetPathUtility.SanitizeAssetPath(texPath)
1000-
);
1001-
if (
1002-
newTex != null
1003-
&& mat.HasProperty(propName)
1004-
&& mat.GetTexture(propName) != newTex
1005-
)
1006-
{
1007-
mat.SetTexture(propName, newTex);
1008-
modified = true;
1009-
}
1010-
else if (newTex == null)
999+
var ci = properties.Properties().FirstOrDefault(
1000+
p => string.Equals(p.Name, "texture", StringComparison.OrdinalIgnoreCase));
1001+
if (ci != null && ci.Value is JObject t1) texProps = t1;
1002+
}
1003+
if (texProps != null)
1004+
{
1005+
string rawName = (texProps["name"] ?? texProps["Name"])?.ToString();
1006+
string texPath = (texProps["path"] ?? texProps["Path"])?.ToString();
1007+
if (!string.IsNullOrEmpty(texPath))
10111008
{
1012-
Debug.LogWarning($"Texture not found at path: {texPath}");
1009+
var newTex = AssetDatabase.LoadAssetAtPath<Texture>(
1010+
AssetPathUtility.SanitizeAssetPath(texPath));
1011+
if (newTex == null)
1012+
{
1013+
Debug.LogWarning($"Texture not found at path: {texPath}");
1014+
}
1015+
else
1016+
{
1017+
// Reuse alias resolver so friendly names like 'albedo' work here too
1018+
string candidateName = string.IsNullOrEmpty(rawName) ? "_BaseMap" : rawName;
1019+
string targetProp = ResolvePropertyName(candidateName);
1020+
if (!string.IsNullOrEmpty(targetProp) && mat.HasProperty(targetProp))
1021+
{
1022+
if (mat.GetTexture(targetProp) != newTex)
1023+
{
1024+
mat.SetTexture(targetProp, newTex);
1025+
modified = true;
1026+
}
1027+
}
1028+
}
10131029
}
10141030
}
10151031
}
@@ -1026,15 +1042,20 @@ string ResolvePropertyName(string name)
10261042
{
10271043
if (string.IsNullOrEmpty(name)) return name;
10281044
string[] candidates;
1029-
switch (name)
1045+
var lower = name.ToLowerInvariant();
1046+
switch (lower)
10301047
{
1031-
case "_Color": candidates = new[] { "_Color", "_BaseColor" }; break;
1032-
case "_BaseColor": candidates = new[] { "_BaseColor", "_Color" }; break;
1033-
case "_MainTex": candidates = new[] { "_MainTex", "_BaseMap" }; break;
1034-
case "_BaseMap": candidates = new[] { "_BaseMap", "_MainTex" }; break;
1035-
case "_Glossiness": candidates = new[] { "_Glossiness", "_Smoothness" }; break;
1036-
case "_Smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
1037-
default: candidates = new[] { name }; break;
1048+
case "_color": candidates = new[] { "_Color", "_BaseColor" }; break;
1049+
case "_basecolor": candidates = new[] { "_BaseColor", "_Color" }; break;
1050+
case "_maintex": candidates = new[] { "_MainTex", "_BaseMap" }; break;
1051+
case "_basemap": candidates = new[] { "_BaseMap", "_MainTex" }; break;
1052+
case "_glossiness": candidates = new[] { "_Glossiness", "_Smoothness" }; break;
1053+
case "_smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
1054+
// Friendly names → shader property names
1055+
case "metallic": candidates = new[] { "_Metallic" }; break;
1056+
case "smoothness": candidates = new[] { "_Smoothness", "_Glossiness" }; break;
1057+
case "albedo": candidates = new[] { "_BaseMap", "_MainTex" }; break;
1058+
default: candidates = new[] { name }; break; // keep original as-is
10381059
}
10391060
foreach (var candidate in candidates)
10401061
{

MCPForUnity/Editor/Tools/ManageScript.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,15 +1933,8 @@ string namespaceName
19331933
/// </summary>
19341934
private static ValidationLevel GetValidationLevelFromGUI()
19351935
{
1936-
string savedLevel = EditorPrefs.GetString("MCPForUnity_ScriptValidationLevel", "standard");
1937-
return savedLevel.ToLower() switch
1938-
{
1939-
"basic" => ValidationLevel.Basic,
1940-
"standard" => ValidationLevel.Standard,
1941-
"comprehensive" => ValidationLevel.Comprehensive,
1942-
"strict" => ValidationLevel.Strict,
1943-
_ => ValidationLevel.Standard // Default fallback
1944-
};
1936+
int savedLevel = EditorPrefs.GetInt("MCPForUnity.ValidationLevel", (int)ValidationLevel.Standard);
1937+
return (ValidationLevel)Mathf.Clamp(savedLevel, 0, 3);
19451938
}
19461939

19471940
/// <summary>

Server/Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM python:3.13-slim
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
git \
5+
&& rm -rf /var/lib/apt/lists/*
6+
7+
WORKDIR /app
8+
9+
RUN pip install uv
10+
11+
COPY . /app
12+
13+
RUN uv sync
14+
15+
CMD ["uv", "run", "server.py"]

Server/README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# MCP for Unity Server
2+
3+
[![MCP](https://badge.mcpx.dev?status=on 'MCP Enabled')](https://modelcontextprotocol.io/introduction)
4+
[![python](https://img.shields.io/badge/Python-3.11+-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org)
5+
[![License](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT)
6+
[![Discord](https://img.shields.io/badge/discord-join-red.svg?logo=discord&logoColor=white)](https://discord.gg/y4p8KfzrN4)
7+
8+
Model Context Protocol server for Unity Editor integration. Control Unity through natural language using AI assistants like Claude, Cursor, and more.
9+
10+
**Maintained by [Coplay](https://www.coplay.dev/?ref=unity-mcp)** - This project is not affiliated with Unity Technologies.
11+
12+
💬 **Join our community:** [Discord Server](https://discord.gg/y4p8KfzrN4)
13+
14+
**Required:** Install the [Unity MCP Plugin](https://github.com/CoplayDev/unity-mcp?tab=readme-ov-file#-step-1-install-the-unity-package) to connect Unity Editor with this MCP server.
15+
16+
---
17+
18+
## Installation
19+
20+
### Option 1: Using uvx (Recommended)
21+
22+
Run directly from GitHub without installation:
23+
24+
```bash
25+
uvx --from git+https://github.com/CoplayDev/[email protected]#subdirectory=Server mcp-for-unity
26+
```
27+
28+
**MCP Client Configuration:**
29+
30+
```json
31+
{
32+
"mcpServers": {
33+
"UnityMCP": {
34+
"command": "uvx",
35+
"args": [
36+
"--from",
37+
"git+https://github.com/CoplayDev/[email protected]#subdirectory=Server",
38+
"mcp-for-unity"
39+
]
40+
}
41+
}
42+
}
43+
```
44+
45+
### Option 2: Using uv (Local Installation)
46+
47+
For local development or custom installations:
48+
49+
```bash
50+
# Clone the repository
51+
git clone https://github.com/CoplayDev/unity-mcp.git
52+
cd unity-mcp/Server
53+
54+
# Run with uv
55+
uv run server.py
56+
```
57+
58+
**MCP Client Configuration:**
59+
60+
**Windows:**
61+
```json
62+
{
63+
"mcpServers": {
64+
"UnityMCP": {
65+
"command": "uv",
66+
"args": [
67+
"run",
68+
"--directory",
69+
"C:\\path\\to\\unity-mcp\\Server",
70+
"server.py"
71+
]
72+
}
73+
}
74+
}
75+
```
76+
77+
**macOS/Linux:**
78+
```json
79+
{
80+
"mcpServers": {
81+
"UnityMCP": {
82+
"command": "uv",
83+
"args": [
84+
"run",
85+
"--directory",
86+
"/path/to/unity-mcp/Server",
87+
"server.py"
88+
]
89+
}
90+
}
91+
}
92+
```
93+
94+
### Option 3: Using Docker
95+
96+
```bash
97+
docker build -t unity-mcp-server .
98+
docker run unity-mcp-server
99+
```
100+
101+
**MCP Client Configuration:**
102+
103+
```json
104+
{
105+
"mcpServers": {
106+
"UnityMCP": {
107+
"command": "docker",
108+
"args": ["run", "-i", "unity-mcp-server"]
109+
}
110+
}
111+
}
112+
```
113+
114+
---
115+
116+
## Configuration
117+
118+
The server connects to Unity Editor automatically when both are running. No additional configuration needed.
119+
120+
**Environment Variables:**
121+
122+
- `DISABLE_TELEMETRY=true` - Opt out of anonymous usage analytics
123+
- `LOG_LEVEL=DEBUG` - Enable detailed logging (default: INFO)
124+
125+
---
126+
127+
## Example Prompts
128+
129+
Once connected, try these commands in your AI assistant:
130+
131+
- "Create a 3D player controller with WASD movement"
132+
- "Add a rotating cube to the scene with a red material"
133+
- "Create a simple platformer level with obstacles"
134+
- "Generate a shader that creates a holographic effect"
135+
- "List all GameObjects in the current scene"
136+
137+
---
138+
139+
## Documentation
140+
141+
For complete documentation, troubleshooting, and advanced usage:
142+
143+
📖 **[Full Documentation](https://github.com/CoplayDev/unity-mcp#readme)**
144+
145+
---
146+
147+
## Requirements
148+
149+
- **Python:** 3.11 or newer
150+
- **Unity Editor:** 2021.3 LTS or newer
151+
- **uv:** Python package manager ([Installation Guide](https://docs.astral.sh/uv/getting-started/installation/))
152+
153+
---
154+
155+
## License
156+
157+
MIT License - See [LICENSE](https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE)

Server/__init__.py

Whitespace-only changes.

Server/config.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Configuration settings for the MCP for Unity Server.
3+
This file contains all configurable parameters for the server.
4+
"""
5+
6+
from dataclasses import dataclass
7+
8+
9+
@dataclass
10+
class ServerConfig:
11+
"""Main configuration class for the MCP server."""
12+
13+
# Network settings
14+
unity_host: str = "localhost"
15+
unity_port: int = 6400
16+
mcp_port: int = 6500
17+
18+
# Connection settings
19+
connection_timeout: float = 30.0
20+
buffer_size: int = 16 * 1024 * 1024 # 16MB buffer
21+
# Framed receive behavior
22+
# max seconds to wait while consuming heartbeats only
23+
framed_receive_timeout: float = 2.0
24+
# cap heartbeat frames consumed before giving up
25+
max_heartbeat_frames: int = 16
26+
27+
# Logging settings
28+
log_level: str = "INFO"
29+
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
30+
31+
# Server settings
32+
max_retries: int = 5
33+
retry_delay: float = 0.25
34+
# Backoff hint returned to clients when Unity is reloading (milliseconds)
35+
reload_retry_ms: int = 250
36+
# Number of polite retries when Unity reports reloading
37+
# 40 × 250ms ≈ 10s default window
38+
reload_max_retries: int = 40
39+
40+
# Telemetry settings
41+
telemetry_enabled: bool = True
42+
# Align with telemetry.py default Cloud Run endpoint
43+
telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events"
44+
45+
46+
# Create a global config instance
47+
config = ServerConfig()

Server/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import Any
2+
from pydantic import BaseModel
3+
4+
5+
class MCPResponse(BaseModel):
6+
success: bool
7+
message: str | None = None
8+
error: str | None = None
9+
data: Any | None = None

0 commit comments

Comments
 (0)