IvanMurzak / IvanMurzak/Unity-MCP
unity-mcp-cli derives the local port with ProjectIdentity v1 while the plugin uses v2 — status/wait-for-ready probe the wrong port on Windows
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 4.3k
- Forks
- 379
- Avg merge
- 6h 52m
- Merged PRs (30d)
- 17
Description
Summary
unity-mcp-cli derives the deterministic local port with the v1 ProjectIdentity
algorithm, while the Unity plugin and setup-mcp use v2. On Windows the two disagree,
because v1 does not normalise \ to /. Five CLI code paths therefore probe a port nothing
is listening on.
The runtime path (agent ↔ broker ↔ Editor) is unaffected — only the CLI's own diagnostics
and wait-for-ready are, which is probably why this has gone unnoticed.
Environment
unity-mcp-cli |
0.90.0 |
com.ivanmurzak.unity.mcp |
0.90.0 |
gamedev-mcp-server |
9.2.5 |
| Unity | 6000.5.1f1 |
| OS | Windows 11 (10.0.26200) |
| Node | v24.16.0 |
Reproduction
With a Unity project at C:\Users\admin\Documents\GitHub\LumberJack, the Editor open, and the
broker healthy on port 27244:
> unity-mcp-cli status .
Unity Editor Process
SUCCESS: Unity is running (PID: 61456)
Local MCP Server
URL: http://localhost:28268
Probing http://localhost:28268...
ERROR: Not available (connection refused)
Config Server
URL: http://localhost:27244
Probing http://localhost:27244...
SUCCESS: Connected
28268 is the v1 port; 27244 is the v2 port that the plugin and setup-mcp actually use.
Passing a forward-slash path does not help — resolveProjectPath runs path.resolve() and
converts the separators back.
wait-for-ready is the worse symptom: it polls the v1 port and times out while the Editor and
broker are up and healthy.
> unity-mcp-cli wait-for-ready . --timeout 540000
Waiting for Unity Editor and MCP server...
ERROR: Timed out after 540.0s waiting for MCP server
Root cause
dist/utils/port.js re-exports the v1 functions under the names the rest of the CLI
imports:
export { derivePin as deriveProjectPin, derivePort as generatePortFromDirectory, normalize as normalizeProjectRoot, } from '@baizor/gamedev-cli-core';
Five consumers take the v1 port from it:
dist/commands/status.js:18dist/commands/wait-for-ready.js:18dist/lib/run-tool.js:182(fallback URL)dist/utils/config.js:12dist/utils/connection.js:62
Meanwhile @baizor/gamedev-cli-core/dist/setup-mcp.js:127 uses derivePortV2, and the Unity
plugin's UnityMcpPlugin.GeneratePortFromDirectory uses ProjectIdentity.DerivePortV2.
status.js compares its derived localUrl against the config URL and hides the second
section when they match — so the mismatch also makes that consistency check permanently
report a false discrepancy on Windows.
Note that status.js calls generatePortFromDirectory(projectPath) directly rather than
deriveProjectIdentity(projectRoot, portOverride), so a .ai-game-dev/project.json port
override cannot work around it either.
Why v2 is the intended side
This looks like an incomplete migration rather than a deliberate split:
- #882 (2026-07-14, CLI) aligned
generatePortFromDirectorywithProjectIdentity
because it "could derive a different port than the plugin's canonicalProjectIdentity".
At that point the canonical algorithm was v1. - #904 / #905 (2026-07-18, plugin) landed defect B10, moving the plugin to the v2
normalisation "so the port stays in lock-step with the routing pin".
The CLI side was never brought forward the four days to match.
The plugin's own Tests/Editor/Auth/PortDerivationV2Tests.cs commits the golden vectors:
| path | expected port |
|---|---|
C:\Users\user\my-game |
24298 |
C:\Users\user\my-game\ |
24298 |
C:/Users/user/my-game |
24298 |
/home/user/my-game |
23940 |
An independent implementation of the documented algorithm reproduces all of them, and
reproduces 28268 for the v1 path of the project above — so the divergence is exactly the
v1/v2 seam and nothing else.
Suggested fix
Point the historical names at v2 in utils/port.js, which fixes all five consumers at once:
export { derivePinV2 as deriveProjectPin, derivePortV2 as generatePortFromDirectory, normalizeV2 as normalizeProjectRoot, } from '@baizor/gamedev-cli-core';
v1 and v2 are identical for paths without backslashes, so this is a no-op on macOS/Linux.
If v1 must stay reachable for the golden-vector parity tests, importing derivePortV2
directly at the five call sites works equally well.
A CI check that asserts the CLI and the plugin agree for a Windows-style path would stop this
seam from reopening.
Workaround
For anyone hitting this before a release: patching that single line locally restores
status, wait-for-ready, and run-tool. I put the patch script, the golden-vector tests,
and setup notes in https://github.com/duckxy166/unity-mcp-connect — happy to open a PR here
instead if you'd prefer the fix upstream.
Thanks for the project — the broker-as-separate-process design surviving domain reloads is
genuinely the right call.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with dist/utils/port.js and compare its exported functions with the v2 exports used by @baizor/gamedev-cli-core/dist/setup-mcp.js. Check the five listed CLI consumers, then run or add coverage using the Windows-style vectors from Tests/Editor/Auth/PortDerivationV2Tests.cs. Done means status, wait-for-ready, run-tool, config, and connection derive the same port as the Unity plugin on Windows.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, javascript, node.js, unity
- Domain
- cli, game-dev, testing-qa
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100