1- # Unity Editor + MCP Server for GitHub Copilot Coding Agent
2- #
3- # IMPORTANT: Both Unity and MCP Server run as GitHub Services so they persist
4- # after the setup job completes, allowing Copilot to connect to them.
5- #
6- # Architecture:
7- # ┌──────────────────────────────────────────────────────────────┐
8- # │ GitHub Actions Runner │
9- # │ │
10- # │ ┌─────────────────┐ SignalR ┌───────────────────┐ │
11- # │ │ Unity Editor │◄────────────────►│ Unity-MCP-Server │ │
12- # │ │ (SERVICE) │ localhost │ (SERVICE) │ │
13- # │ │ localhost:55123 │ │ localhost:55123 │ │
14- # │ └─────────────────┘ └────────▲──────────┘ │
15- # │ │ │
16- # │ MCP Protocol │
17- # │ │ │
18- # │ ┌────────────────┴──────────┐ │
19- # │ │ GitHub Copilot Agent │ │
20- # │ └───────────────────────────┘ │
21- # └──────────────────────────────────────────────────────────────┘
22- #
23- # How it works:
24- # 1. Services start immediately (before steps run)
25- # 2. Unity service waits (doesn't run Unity yet - no license)
26- # 3. Steps run: checkout, activate license
27- # 4. Copy license + project INTO Unity service via docker cp
28- # 5. Start Unity inside the service via docker exec
29- # 6. Both services persist after job completes for Copilot to use
30- #
31- # Prerequisites:
32- # - UNITY_LICENSE: Contents of your .ulf license file
33- # - UNITY_EMAIL: Your Unity account email
34- # - UNITY_PASSWORD: Your Unity account password
35-
36- name : Copilot Setup Steps
1+ name : " Copilot Setup Steps"
372
3+ # Automatically run the setup steps when they are changed to allow for easy validation, and
4+ # allow manual testing through the repository's "Actions" tab
385on :
396 workflow_dispatch :
407 push :
418 paths :
42- - " .github/workflows/copilot-setup-steps.yml"
9+ - .github/workflows/copilot-setup-steps.yml
4310 pull_request :
4411 paths :
45- - " .github/workflows/copilot-setup-steps.yml"
12+ - .github/workflows/copilot-setup-steps.yml
4613
4714jobs :
48- # IMPORTANT: Job name MUST be exactly ' copilot-setup-steps'
15+ # The job MUST be called ` copilot-setup-steps` or it will not be picked up by Copilot.
4916 copilot-setup-steps :
5017 runs-on : ubuntu-latest
51- timeout-minutes : 59
18+
19+ # Set the permissions to the lowest permissions possible needed for your steps.
20+ # Copilot will be given its own token for its operations.
5221 permissions :
22+ # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
5323 contents : read
5424
55- # =========================================================================
56- # SERVICE CONTAINERS - These persist after job completes!
57- # =========================================================================
58- services :
59- # Unity Editor Service
60- # Starts with 'tail' entrypoint to stay alive without running Unity
61- # We'll start Unity later via docker exec after license is ready
62- unity-editor :
63- image : unityci/editor:ubuntu-2022.3.61f1-linux-il2cpp-3
64- ports :
65- - 55123:55123
66- # Use 'bash' as entrypoint - keeps container waiting for input
67- # The default ENTRYPOINT is ["bash", "-c"] which exits, but just "bash" waits
68- options : >-
69- --entrypoint /bin/bash
70- -t
71-
72- # Unity-MCP-Server Service
73- # Bridges Copilot (MCP client) to Unity Editor
74- mcp-server :
75- image : ivanmurzakdev/unity-mcp-server:latest
76- ports :
77- - 55123:55123
78- env :
79- TRANSPORT : http
80- # Connect to Unity service via localhost (both services on host network)
81- SIGNALR_URL : http://localhost:55123/unityhub
82-
25+ # You can define any steps you want, and they will run before the agent starts.
26+ # If you do not check out your code, Copilot will do this for you.
8327 steps :
84- # =========================================================================
85- # SETUP
86- # =========================================================================
87-
88- - name : Checkout repository
89- uses : actions/checkout@v4
28+ - name : Checkout code
29+ uses : actions/checkout@v5
9030
91- - name : Find service containers
92- id : containers
31+ - name : Remove existing Docker network and containers
9332 run : |
94- echo "All running containers:"
95- docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}"
96- echo ""
33+ docker rm -f unity-editor || true
34+ docker rm -f unity-mcp-server || true
35+ docker network rm unity-mcp-network || true
9736
98- # Find Unity service container (use grep for partial match on image name)
99- UNITY_CONTAINER=$(docker ps --format "{{.ID}} {{.Image}}" | grep -i "unityci/editor" | awk '{print $1}' | head -1)
100- if [ -z "$UNITY_CONTAINER" ]; then
101- echo "WARNING: Unity container not found by image name, trying by port..."
102- UNITY_CONTAINER=$(docker ps --format "{{.ID}} {{.Ports}}" | grep "55123" | awk '{print $1}' | head -1)
103- fi
104- echo "unity_container=$UNITY_CONTAINER" >> $GITHUB_OUTPUT
105- echo "Unity container: $UNITY_CONTAINER"
37+ - name : Create Docker network
38+ run : docker network create unity-mcp-network
10639
107- # Find MCP Server container
108- MCP_CONTAINER=$(docker ps --format "{{.ID}} {{.Image}}" | grep -i "unity-mcp-server" | awk '{print $1}' | head -1)
109- if [ -z "$MCP_CONTAINER" ]; then
110- echo "WARNING: MCP container not found by image name, trying by port..."
111- MCP_CONTAINER=$(docker ps --format "{{.ID}} {{.Ports}}" | grep "55123" | awk '{print $1}' | head -1)
112- fi
113- echo "mcp_container=$MCP_CONTAINER" >> $GITHUB_OUTPUT
114- echo "MCP Server container: $MCP_CONTAINER"
40+ - name : Create Unity Editor container
41+ run : |
42+ docker create \
43+ -v ${{ github.workspace }}/Unity-MCP-Plugin:/project \
44+ --network unity-mcp-network \
45+ --name unity-editor \
46+ unityci/editor:ubuntu-2022.3.61f1-linux-il2cpp-3 \
47+ tail -f /dev/null
48+
49+ - name : Start Unity Editor container
50+ run : docker start unity-editor
11551
11652 - name : Activate Unity License
11753 uses : game-ci/unity-activate@v2
@@ -120,163 +56,76 @@ jobs:
12056 UNITY_EMAIL : ${{ secrets.UNITY_EMAIL }}
12157 UNITY_PASSWORD : ${{ secrets.UNITY_PASSWORD }}
12258
123- # =========================================================================
124- # INJECT FILES INTO UNITY SERVICE
125- # =========================================================================
126-
127- - name : Copy license to Unity service
59+ - name : Start Unity Editor and execute startup method
12860 run : |
129- UNITY_CONTAINER="${{ steps.containers.outputs.unity_container }}"
130-
131- if [ -z "$UNITY_CONTAINER" ]; then
132- echo "ERROR: Unity container not found!"
133- exit 1
134- fi
135-
136- echo "Copying license to Unity service container..."
137-
138- # Create license directory in container
139- docker exec "$UNITY_CONTAINER" mkdir -p /root/.local/share/unity3d/Unity
140-
141- # Copy GameCI activated license
142- if [ -d ~/.local/share/unity3d/Unity ]; then
143- docker cp ~/.local/share/unity3d/Unity/. "$UNITY_CONTAINER":/root/.local/share/unity3d/Unity/
144- echo "Copied activated license from GameCI"
145- fi
146-
147- # Also write the secret directly as backup
148- echo '${{ secrets.UNITY_LICENSE }}' | docker exec -i "$UNITY_CONTAINER" tee /root/.local/share/unity3d/Unity/Unity_lic.ulf > /dev/null
149-
150- echo "License files in container:"
151- docker exec "$UNITY_CONTAINER" ls -la /root/.local/share/unity3d/Unity/
152-
153- - name : Copy project to Unity service
154- run : |
155- UNITY_CONTAINER="${{ steps.containers.outputs.unity_container }}"
156-
157- echo "Copying Unity project to service container..."
158-
159- # Create project directory
160- docker exec "$UNITY_CONTAINER" mkdir -p /project
161-
162- # Copy the entire Unity project
163- docker cp "${{ github.workspace }}/UnityProject/." "$UNITY_CONTAINER":/project/
164-
165- echo "Project copied. Contents:"
166- docker exec "$UNITY_CONTAINER" ls -la /project/
167-
168- # =========================================================================
169- # START UNITY IN THE SERVICE
170- # =========================================================================
171-
172- - name : Start Unity Editor in service
173- run : |
174- UNITY_CONTAINER="${{ steps.containers.outputs.unity_container }}"
175-
176- echo "Starting Unity Editor inside service container..."
177-
178- # Start Unity in background inside the service container
179- docker exec -d "$UNITY_CONTAINER" /bin/bash -c "
180- unity-editor \
181- -batchmode \
182- -nographics \
183- -logFile /tmp/unity.log \
184- -projectPath /project \
185- -executeMethod Editor.Startup.Init \
186- 2>&1 &
187-
188- # Keep this exec alive to monitor
189- tail -f /tmp/unity.log 2>/dev/null || sleep infinity
190- "
191-
192- echo "Unity Editor starting in service container..."
193-
194- - name : Wait for Unity initialization
195- run : |
196- UNITY_CONTAINER="${{ steps.containers.outputs.unity_container }}"
197-
198- echo "Waiting for Unity to initialize..."
199- echo "This may take 2-5 minutes for first-time project import..."
200-
201- timeout=300
61+ echo "Starting Unity Editor with startup method execution..."
62+
63+ # Start Unity with executeMethod (without -quit to keep it running)
64+ docker exec unity-editor bash -c "unity-editor \
65+ -projectPath /project \
66+ -batchmode \
67+ -nographics \
68+ -username ${{ secrets.UNITY_EMAIL }} \
69+ -password ${{ secrets.UNITY_PASSWORD }} \
70+ -executeMethod com.IvanMurzak.Unity.MCP.Editor.Startup.Init \
71+ -logFile /dev/stdout 2>&1"
72+
73+ echo "Unity Editor started, streaming logs until method completes..."
74+ echo "================================================"
75+
76+ # Follow logs and look for completion indicators
77+ timeout=600 # 10 minutes timeout for method execution
20278 elapsed=0
79+ last_log_count=0
20380
20481 while [ $elapsed -lt $timeout ]; do
205- # Check for ready marker
206- if docker exec "$UNITY_CONTAINER" grep -q "Unity-MCP-Ready" /tmp/unity.log 2>/dev/null; then
207- echo ""
208- echo "Unity initialized successfully!"
209- exit 0
82+ # Get current logs
83+ current_logs=$(docker logs unity-editor 2>&1)
84+
85+ # Print new logs only
86+ current_log_count=$(echo "$current_logs" | wc -l)
87+ if [ $current_log_count -gt $last_log_count ]; then
88+ echo "$current_logs" | tail -n +$((last_log_count + 1))
89+ last_log_count=$current_log_count
21090 fi
21191
212- # Check for license errors
213- if docker exec "$UNITY_CONTAINER" grep -q "No valid Unity Editor license" /tmp/unity.log 2>/dev/null; then
214- echo "ERROR: License activation failed!"
215- docker exec "$UNITY_CONTAINER" tail -50 /tmp/unity.log
216- exit 1
217- fi
92+ # Check for method completion indicators
93+ if echo "$current_logs" | grep -q "com.IvanMurzak.Unity.MCP.Editor.Startup.Init\|Refreshing native plugins\|Initialize mono"; then
94+ # Wait a bit more to capture any final logs
95+ sleep 5
21896
219- # Check Unity process
220- if docker exec "$UNITY_CONTAINER" pgrep -f "Unity" > /dev/null 2>&1; then
221- : # Unity is running, continue waiting
222- else
223- # Check if Unity exited with error
224- if docker exec "$UNITY_CONTAINER" test -f /tmp/unity.log 2>/dev/null; then
225- if docker exec "$UNITY_CONTAINER" grep -q "Fatal Error" /tmp/unity.log 2>/dev/null; then
226- echo "ERROR: Unity crashed!"
227- docker exec "$UNITY_CONTAINER" tail -50 /tmp/unity.log
228- exit 1
229- fi
97+ # Print final logs
98+ final_logs=$(docker logs unity-editor 2>&1)
99+ final_count=$(echo "$final_logs" | wc -l)
100+ if [ $final_count -gt $last_log_count ]; then
101+ echo "$final_logs" | tail -n +$((last_log_count + 1))
230102 fi
103+
104+ echo "================================================"
105+ echo "Unity startup method execution detected, detaching from logs"
106+ break
231107 fi
232108
233- sleep 10
234- elapsed=$((elapsed + 10))
235- printf "Waiting... %d/%ds " "$elapsed" "$timeout"
236- docker exec "$UNITY_CONTAINER" tail -1 /tmp/unity.log 2>/dev/null | head -c 60 || true
237- echo ""
109+ sleep 2
110+ elapsed=$((elapsed + 2))
238111 done
239112
240- echo ""
241- echo "Timeout reached. Final logs:"
242- docker exec "$UNITY_CONTAINER" tail -30 /tmp/unity.log 2>/dev/null || true
113+ if [ $elapsed -ge $timeout ]; then
114+ echo "================================================"
115+ echo "Warning: Timeout waiting for method completion, but continuing..."
116+ echo "Last 20 lines of logs:"
117+ docker logs unity-editor 2>&1 | tail -n 20
118+ fi
243119
244- # =========================================================================
245- # VERIFICATION
246- # =========================================================================
120+ echo "Unity Editor is now running in background"
247121
248- - name : Verify setup
122+ - name : Install Unity-MCP-Server docker image
249123 run : |
250- UNITY_CONTAINER="${{ steps.containers.outputs.unity_container }}"
251- MCP_CONTAINER="${{ steps.containers.outputs.mcp_container }}"
252-
253- echo "=========================================="
254- echo " COPILOT ENVIRONMENT READY"
255- echo "=========================================="
256- echo ""
257-
258- echo "SERVICE CONTAINERS (persist after job):"
259- docker ps --format " {{.ID}} | {{.Image}} | {{.Status}}" | head -5
260-
261- echo ""
262- echo "UNITY SERVICE:"
263- echo " Container: $UNITY_CONTAINER"
264- docker exec "$UNITY_CONTAINER" pgrep -a Unity 2>/dev/null | head -1 | sed 's/^/ Process: /' || echo " Process: Starting..."
265- nc -zv localhost 55123 2>&1 | grep -q succeeded && echo " Port 55123: OPEN" || echo " Port 55123: Closed"
266-
267- echo ""
268- echo "MCP SERVER SERVICE:"
269- echo " Container: $MCP_CONTAINER"
270- nc -zv localhost 55123 2>&1 | grep -q succeeded && echo " Port 55123: OPEN" || echo " Port 55123: Closed"
271-
272- echo ""
273- echo "UNITY LOGS (last 15 lines):"
274- docker exec "$UNITY_CONTAINER" tail -15 /tmp/unity.log 2>/dev/null | sed 's/^/ /' || echo " No logs yet"
275-
276- echo ""
277- echo "=========================================="
278- echo "Both services will persist for Copilot!"
279- echo ""
280- echo "Copilot MCP endpoint: http://localhost:55123"
281- echo "Unity health check: http://localhost:55123"
282- echo "=========================================="
124+ docker run -d \
125+ -p 8080:8080 \
126+ -e UNITY_MCP_PORT=8080 \
127+ -e UNITY_MCP_CLIENT_TRANSPORT=http \
128+ -e UNITY_MCP_PLUGIN_TIMEOUT=120000 \
129+ --network unity-mcp-network \
130+ --name unity-mcp-server \
131+ ivanmurzakdev/unity-mcp-server:latest
0 commit comments