Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 85c101f

Browse filesBrowse files
Merge pull request #1204 from CoplayDev/release/v9.7.3
chore: bump version to 9.7.3
2 parents 78ee541 + 39e3e97 commit 85c101f
Copy full SHA for 85c101f

69 files changed

+4,739-348Lines changed: 4739 additions & 348 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎.github/workflows/e2e-bridge.yml‎

Copy file name to clipboard
+194Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
name: E2E Bridge Smoke (deterministic, no LLM)
2+
3+
# Boots a headless Unity Editor, starts the Python MCP server's wire path, and
4+
# drives a fixed sequence of real tool calls with exact assertions
5+
# (Server/tests/e2e/bridge_smoke.py). Unlike claude-nl-suite.yml this needs
6+
# NO Anthropic API key -- it is deterministic and cheap, so it can gate PRs and
7+
# releases. It still needs Unity license secrets to boot the Editor.
8+
9+
on:
10+
workflow_dispatch:
11+
pull_request:
12+
paths:
13+
- "MCPForUnity/Editor/**"
14+
- "MCPForUnity/Runtime/**"
15+
- "Server/src/**"
16+
- "Server/tests/e2e/**"
17+
- "tools/local_harness.py"
18+
- ".github/workflows/e2e-bridge.yml"
19+
20+
permissions:
21+
contents: read
22+
23+
concurrency:
24+
group: ${{ github.workflow }}-${{ github.ref }}
25+
cancel-in-progress: true
26+
27+
env:
28+
UNITY_IMAGE: unityci/editor:ubuntu-2021.3.45f2-linux-il2cpp-3
29+
30+
jobs:
31+
e2e-bridge:
32+
runs-on: ubuntu-24.04
33+
timeout-minutes: 40
34+
steps:
35+
- name: Detect Unity license secrets
36+
id: detect
37+
env:
38+
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
39+
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
40+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
41+
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
42+
run: |
43+
set -e
44+
if [ -n "$UNITY_LICENSE" ] || { [ -n "$UNITY_EMAIL" ] && [ -n "$UNITY_PASSWORD" ] && [ -n "$UNITY_SERIAL" ]; }; then
45+
echo "unity_ok=true" >> "$GITHUB_OUTPUT"
46+
else
47+
echo "unity_ok=false" >> "$GITHUB_OUTPUT"
48+
echo "::warning::Unity license secrets absent; E2E bridge smoke will be skipped (not failed)."
49+
fi
50+
51+
- uses: actions/checkout@v4
52+
if: steps.detect.outputs.unity_ok == 'true'
53+
with:
54+
fetch-depth: 0
55+
56+
- uses: astral-sh/setup-uv@v4
57+
if: steps.detect.outputs.unity_ok == 'true'
58+
with:
59+
python-version: "3.11"
60+
61+
- name: Install MCP server
62+
if: steps.detect.outputs.unity_ok == 'true'
63+
run: |
64+
set -eux
65+
uv venv
66+
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
67+
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
68+
uv pip install -e Server
69+
70+
# --- License staging (mirrors claude-nl-suite.yml) ---
71+
- name: Decide license sources
72+
if: steps.detect.outputs.unity_ok == 'true'
73+
id: lic
74+
shell: bash
75+
env:
76+
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
77+
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
78+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
79+
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
80+
run: |
81+
set -eu
82+
use_ulf=false; use_ebl=false
83+
[[ -n "${UNITY_LICENSE:-}" ]] && use_ulf=true
84+
[[ -n "${UNITY_EMAIL:-}" && -n "${UNITY_PASSWORD:-}" && -n "${UNITY_SERIAL:-}" ]] && use_ebl=true
85+
echo "use_ulf=$use_ulf" >> "$GITHUB_OUTPUT"
86+
echo "use_ebl=$use_ebl" >> "$GITHUB_OUTPUT"
87+
88+
- name: Stage Unity .ulf license (from secret)
89+
if: steps.detect.outputs.unity_ok == 'true' && steps.lic.outputs.use_ulf == 'true'
90+
id: ulf
91+
env:
92+
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
93+
shell: bash
94+
run: |
95+
set -eu
96+
mkdir -p "$RUNNER_TEMP/unity-license-ulf" "$RUNNER_TEMP/unity-local/Unity"
97+
f="$RUNNER_TEMP/unity-license-ulf/Unity_lic.ulf"
98+
if printf "%s" "$UNITY_LICENSE" | base64 -d - >/dev/null 2>&1; then
99+
printf "%s" "$UNITY_LICENSE" | base64 -d - > "$f"
100+
else
101+
printf "%s" "$UNITY_LICENSE" > "$f"
102+
fi
103+
chmod 600 "$f" || true
104+
if grep -qi '<Signature>' "$f"; then
105+
cp -f "$f" "$RUNNER_TEMP/unity-local/Unity/Unity_lic.ulf"
106+
echo "ok=true" >> "$GITHUB_OUTPUT"
107+
else
108+
echo "ok=false" >> "$GITHUB_OUTPUT"
109+
fi
110+
111+
- name: Activate Unity (EBL via container)
112+
if: steps.detect.outputs.unity_ok == 'true' && steps.lic.outputs.use_ebl == 'true'
113+
shell: bash
114+
env:
115+
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
116+
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
117+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
118+
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
119+
run: |
120+
set -euo pipefail
121+
mkdir -p "$RUNNER_TEMP/unity-config" "$RUNNER_TEMP/unity-local"
122+
docker run --rm --network host \
123+
-e HOME=/root -e UNITY_EMAIL -e UNITY_PASSWORD -e UNITY_SERIAL \
124+
-v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \
125+
-v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \
126+
"$UNITY_IMAGE" bash -lc '
127+
set -euxo pipefail
128+
/opt/unity/Editor/Unity -batchmode -nographics -logFile - \
129+
-username "$UNITY_EMAIL" -password "$UNITY_PASSWORD" -serial "$UNITY_SERIAL" -quit || true
130+
'
131+
132+
- name: Warm up project (import Library once)
133+
if: steps.detect.outputs.unity_ok == 'true'
134+
shell: bash
135+
env:
136+
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
137+
ULF_OK: ${{ steps.ulf.outputs.ok }}
138+
run: |
139+
set -euxo pipefail
140+
manual_args=()
141+
if [[ "${ULF_OK:-false}" == "true" ]]; then
142+
manual_args=(-manualLicenseFile "/root/.local/share/unity3d/Unity/Unity_lic.ulf")
143+
fi
144+
docker run --rm --network host \
145+
-e HOME=/root \
146+
-v "${{ github.workspace }}:${{ github.workspace }}" -w "${{ github.workspace }}" \
147+
-v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \
148+
-v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \
149+
-v "$RUNNER_TEMP/unity-cache:/root/.cache/unity3d" \
150+
"$UNITY_IMAGE" /opt/unity/Editor/Unity -batchmode -nographics -logFile - \
151+
-projectPath "${{ github.workspace }}/TestProjects/UnityMCPTests" \
152+
"${manual_args[@]}" -quit
153+
154+
- name: Clean old MCP status
155+
if: steps.detect.outputs.unity_ok == 'true'
156+
run: |
157+
set -eux
158+
mkdir -p "$GITHUB_WORKSPACE/.unity-mcp"
159+
rm -f "$GITHUB_WORKSPACE/.unity-mcp"/unity-mcp-status-*.json || true
160+
161+
- name: Run headless bridge harness (boot + wait + smoke/editmode/playmode)
162+
if: steps.detect.outputs.unity_ok == 'true'
163+
shell: bash
164+
env:
165+
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
166+
ULF_OK: ${{ steps.ulf.outputs.ok }}
167+
run: |
168+
set -euxo pipefail
169+
# In --ci mode the harness drives the DockerLauncher: it runs the same
170+
# docker container (repo .unity-mcp status dir, docker liveness/teardown,
171+
# log redaction), waits on the status file, derives the instance, then
172+
# runs the smoke + EditMode + PlayMode legs over the bridge.
173+
license_args=()
174+
if [[ "${ULF_OK:-false}" == "true" ]]; then
175+
license_args=(--editor-arg -manualLicenseFile \
176+
--editor-arg "/root/.local/share/unity3d/Unity/Unity_lic.ulf")
177+
fi
178+
python3 tools/local_harness.py --ci \
179+
--legs smoke,editmode,playmode \
180+
--project-path TestProjects/UnityMCPTests \
181+
--reports reports \
182+
"${license_args[@]}"
183+
184+
- name: Unity logs on failure
185+
if: failure() && steps.detect.outputs.unity_ok == 'true'
186+
run: docker logs unity-mcp --tail 200 | sed -E 's/((email|serial|license|password|token)[^[:space:]]*)/[REDACTED]/Ig' || true
187+
188+
- name: Upload E2E report
189+
if: always() && steps.detect.outputs.unity_ok == 'true'
190+
uses: actions/upload-artifact@v4
191+
with:
192+
name: e2e-bridge-report
193+
path: reports/junit-*.xml
194+
if-no-files-found: ignore
Collapse file

‎.github/workflows/python-tests.yml‎

Copy file name to clipboardExpand all lines: .github/workflows/python-tests.yml
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ on:
99
branches-ignore: [beta, main]
1010
paths:
1111
- Server/**
12+
- tools/**
1213
- .github/workflows/python-tests.yml
1314
pull_request:
1415
branches: [main, beta]
1516
paths:
1617
- Server/**
18+
- tools/**
1719
- .github/workflows/python-tests.yml
1820
workflow_dispatch: {}
1921
workflow_call:
@@ -53,6 +55,11 @@ jobs:
5355
cd Server
5456
uv run pytest tests/ -v --tb=short --cov --cov-report=xml --cov-report=html --cov-report=term
5557
58+
- name: Run local harness unit tests (hermetic, no Unity)
59+
run: |
60+
cd Server
61+
uv run python -m pytest "$GITHUB_WORKSPACE/tools/tests/" -v --tb=short
62+
5663
- name: Upload coverage reports
5764
uses: codecov/codecov-action@v4
5865
if: always()
Collapse file

‎.gitignore‎

Copy file name to clipboardExpand all lines: .gitignore
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ tools/.unity-check-logs/
6868

6969
# Ignore the .claude directory, since it might contain local/project-level setting such as deny and allowlist.
7070
/.claude
71+
.mcp.json
Collapse file

‎CLAUDE.md‎

Copy file name to clipboardExpand all lines: CLAUDE.md
+11Lines changed: 11 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ tools/check-unity-versions.sh # compile-only across installed Unity Hu
167167
tools/check-unity-versions.sh --full # full EditMode test run
168168
```
169169

170+
#### Local headless test harness
171+
One command boots a headless Hub-licensed Editor against `TestProjects/UnityMCPTests` and runs the smoke + EditMode + PlayMode legs over the bridge — the same entrypoint CI uses (`.github/workflows/e2e-bridge.yml`):
172+
173+
```bash
174+
python tools/local_harness.py
175+
```
176+
177+
Key flags: `--legs smoke,editmode,playmode` (subset to run), `--project-path` (target project, default `TestProjects/UnityMCPTests`), `--reuse` (attach to an already-resident bridge instead of booting one), `--keep-alive` (leave the Editor running after the legs), `--no-warmup` (skip the warm-up import phase).
178+
179+
Exit codes: `0` pass, `1` blocking-leg regression, `2` bridge unreachable / setup failure, `3` project does not compile, `4` no Unity license / Hub seat, `5` Editor binary/version not found. Requires a Hub-activated Editor locally (no ULF/serial).
180+
170181
### Local Development
171182
1. Set **Server Source Override** in MCP for Unity Advanced Settings to your local `Server/` path
172183
2. Enable **Dev Mode** checkbox to force fresh installs
Collapse file

‎MCPForUnity/Editor/Clients/Configurators/ClineConfigurator.cs‎

Copy file name to clipboardExpand all lines: MCPForUnity/Editor/Clients/Configurators/ClineConfigurator.cs
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public ClineConfigurator() : base(new McpClient
1313
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1414
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1515
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
16+
HttpTypeValue = "streamableHttp",
1617
DefaultUnityFields = { { "disabled", false }, { "autoApprove", new object[] { } } }
1718
})
1819
{ }
Collapse file

‎MCPForUnity/Editor/Clients/Configurators/KiloCodeConfigurator.cs‎

Copy file name to clipboardExpand all lines: MCPForUnity/Editor/Clients/Configurators/KiloCodeConfigurator.cs
+18-9Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,29 @@ public class KiloCodeConfigurator : JsonFileMcpConfigurator
1010
public KiloCodeConfigurator() : base(new McpClient
1111
{
1212
name = "Kilo Code",
13-
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Code", "User", "globalStorage", "kilocode.kilo-code", "settings", "mcp_settings.json"),
14-
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Code", "User", "globalStorage", "kilocode.kilo-code", "settings", "mcp_settings.json"),
15-
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "Code", "User", "globalStorage", "kilocode.kilo-code", "settings", "mcp_settings.json"),
16-
IsVsCodeLayout = false
13+
// Kilo Code v7.0.33+ moved MCP config out of the VS Code extension's
14+
// globalStorage/mcp_settings.json to a CLI-style kilo.jsonc under ~/.config/kilo.
15+
// The new schema (https://app.kilo.ai/config.json) uses an "mcp" container,
16+
// type:"remote" for HTTP servers, type:"local" for stdio, and an "enabled" flag.
17+
// ~/.config/kilo/kilo.jsonc on every OS (UserProfile resolves to C:\Users\<user> on Windows).
18+
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "kilo", "kilo.jsonc"),
19+
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "kilo", "kilo.jsonc"),
20+
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "kilo", "kilo.jsonc"),
21+
IsVsCodeLayout = false,
22+
ServerContainerKey = "mcp",
23+
HttpTypeValue = "remote",
24+
StdioTypeValue = "local",
25+
SchemaUrl = "https://app.kilo.ai/config.json",
26+
DefaultUnityFields = { { "enabled", true } }
1727
})
1828
{ }
1929

2030
public override IList<string> GetInstallationSteps() => new List<string>
2131
{
22-
"Install Kilo Code extension in VS Code",
23-
"Open Kilo Code settings (gear icon in sidebar)",
24-
"Navigate to MCP Servers section and click 'Edit Global MCP Settings'\nOR open the config file at the path above",
25-
"Paste the configuration JSON into the mcpServers object",
26-
"Save and restart VS Code"
32+
"Install or update Kilo Code (v7.0.33 or newer)",
33+
"Open the Kilo Code MCP Servers view\nOR edit the config file at the path above (~/.config/kilo/kilo.jsonc)",
34+
"Paste the configuration JSON into the \"mcp\" object",
35+
"Save and restart Kilo Code"
2736
};
2837
}
2938
}
Collapse file
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using MCPForUnity.Editor.Models;
5+
6+
namespace MCPForUnity.Editor.Clients.Configurators
7+
{
8+
/// <summary>
9+
/// Kimi Code CLI MCP client configurator.
10+
/// Kimi Code uses a JSON-based configuration file with mcpServers section.
11+
/// Config path: ~/.kimi/mcp.json
12+
///
13+
/// Kimi Code supports both stdio (uvx) and HTTP transport modes.
14+
/// Default: stdio mode (works without Unity Editor for basic operations)
15+
/// HTTP mode: requires Unity Editor running with MCP HTTP server started
16+
/// </summary>
17+
public class KimiCodeConfigurator : JsonFileMcpConfigurator
18+
{
19+
public KimiCodeConfigurator() : base(new McpClient
20+
{
21+
name = "Kimi Code",
22+
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kimi", "mcp.json"),
23+
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kimi", "mcp.json"),
24+
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".kimi", "mcp.json"),
25+
SupportsHttpTransport = true,
26+
})
27+
{ }
28+
29+
public override IList<string> GetInstallationSteps() => new List<string>
30+
{
31+
"Ensure Kimi Code CLI is installed (pip install kimi-cli or see https://github.com/MoonshotAI/kimi-cli)",
32+
"Click 'Auto Configure' to automatically add UnityMCP to ~/.kimi/mcp.json",
33+
"OR click 'Manual Setup' to copy the configuration JSON",
34+
"Open ~/.kimi/mcp.json and paste the configuration",
35+
"Save and restart Kimi Code CLI",
36+
"Use 'kimi mcp list' to verify Unity MCP is connected",
37+
"Note: For full functionality, open Unity Editor and start HTTP server"
38+
};
39+
}
40+
}
Collapse file

‎MCPForUnity/Editor/Clients/Configurators/KimiCodeConfigurator.cs.meta‎

Copy file name to clipboardExpand all lines: MCPForUnity/Editor/Clients/Configurators/KimiCodeConfigurator.cs.meta
+11Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Collapse file

‎MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs‎

Copy file name to clipboardExpand all lines: MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
+10-5Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,12 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
179179
JToken unityToken = null;
180180
if (rootConfig != null)
181181
{
182+
string containerKey = string.IsNullOrEmpty(client.ServerContainerKey)
183+
? "mcpServers" : client.ServerContainerKey;
182184
unityToken = client.IsVsCodeLayout
183185
? rootConfig["servers"]?["unityMCP"]
184186
?? rootConfig["mcp"]?["servers"]?["unityMCP"]
185-
: rootConfig["mcpServers"]?["unityMCP"];
187+
: rootConfig[containerKey]?["unityMCP"];
186188
}
187189

188190
if (unityToken is JObject unityObj)
@@ -360,9 +362,10 @@ public override string GetConfigureActionLabel()
360362
=> client.status == McpStatus.Configured ? "Unregister" : "Configure";
361363

362364
/// <summary>
363-
/// Removes the unityMCP entry from the client's JSON config (both VS Code-style
364-
/// `servers` / `mcp.servers` layouts and the standard `mcpServers` layout). Leaves
365-
/// the file in place so we don't clobber other servers the user has configured.
365+
/// Removes the unityMCP entry from the client's JSON config (VS Code-style
366+
/// `servers` / `mcp.servers` layouts, the standard `mcpServers` layout, or a
367+
/// client-specific container such as Kilo's `mcp`). Leaves the file in place so we
368+
/// don't clobber other servers the user has configured.
366369
/// </summary>
367370
public override void Unregister()
368371
{
@@ -392,7 +395,9 @@ public override void Unregister()
392395
}
393396
else
394397
{
395-
if ((root["mcpServers"] as JObject)?.Remove("unityMCP") == true) removed = true;
398+
string containerKey = string.IsNullOrEmpty(client.ServerContainerKey)
399+
? "mcpServers" : client.ServerContainerKey;
400+
if ((root[containerKey] as JObject)?.Remove("unityMCP") == true) removed = true;
396401
}
397402

398403
if (removed)
Collapse file

‎MCPForUnity/Editor/Constants/EditorPrefKeys.cs‎

Copy file name to clipboardExpand all lines: MCPForUnity/Editor/Constants/EditorPrefKeys.cs
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ internal static class EditorPrefKeys
6868
internal const string ApiKey = "MCPForUnity.ApiKey";
6969

7070
internal const string AutoStartOnLoad = "MCPForUnity.AutoStartOnLoad";
71+
internal const string HttpServerLaunchConfirmed = "MCPForUnity.HttpServerLaunchConfirmed";
7172
internal const string BatchExecuteMaxCommands = "MCPForUnity.BatchExecute.MaxCommands";
7273
internal const string LogRecordEnabled = "MCPForUnity.LogRecordEnabled";
7374

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.