Skip to content

feat: auto-select Unity instance by launch directory#1194

Open
imurashka wants to merge 3 commits into
CoplayDev:betafrom
imurashka:feat/autoselect-instance-by-launch-dir
Open

feat: auto-select Unity instance by launch directory#1194
imurashka wants to merge 3 commits into
CoplayDev:betafrom
imurashka:feat/autoselect-instance-by-launch-dir

Conversation

@imurashka

@imurashka imurashka commented Jun 8, 2026

Copy link
Copy Markdown

Description

When several Unity editors are connected the server only auto-selects one if exactly one is open. With more than one it gives up and you have to pass unity_instance on every call. This happens often when the Unity project is not at the repo root, or when you run several projects at once, or git worktrees of one project. Worktrees share the same project name and differ only by path, so name alone cannot tell them apart.

This PR picks the editor that matches the directory the client is working in.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Test update

Changes Made

The whole change lives in the selection middleware. The old code gave up in the "more than one instance" branch of _maybe_autoselect_instance. That branch now tries to resolve a single editor before giving up.

  • unity_instance_middleware.py: in the "more than one instance" branch, select the editor whose project path shares a path lineage with the client launch directory. The single instance path and the explicit unity_instance path are untouched. No match or an ambiguous match keeps the current "ask the user" behavior.
  • The launch directory is resolved client-agnostically. UNITY_MCP_PROJECT_DIR wins if set, otherwise the file:// MCP roots the client advertises. The result is cached per session so the client is queried at most once.
  • Added file:// URI parsing (UNC hosts, percent-encoding, Windows drive letters) and project-root normalization. Over stdio Unity reports the Assets folder, over HTTP the project root.
  • models.py and plugin_hub.py: SessionDetails gets an optional project_path so the HTTP path can match too. It defaults to None, so this is backward compatible.
  • Added Server/tests/integration/test_instance_autoselect_by_launch_dir.py.

Compatibility / Package Source

  • Unity version(s) tested: not applicable, no C#/Unity code changed. Verified against running Unity 6000.4 editors over stdio.
  • Package source used (#beta, #main, tag, branch, or file:): not applicable, no Unity package change. Branch is off beta.
  • Resolved commit hash from Packages/packages-lock.json: not applicable.

Testing/Screenshots/Recordings

  • Python tests (cd Server && uv run pytest tests/ -v), full suite passes including the new file.
  • Unity EditMode tests
  • Unity PlayMode tests
  • Package import/compile check
  • Not applicable (explain why in Additional Notes)

Documentation Updates

  • I have added/removed/modified tools or resources

No tools or resources changed, so the generated tool reference is not affected.

Related Issues

Relates to the multi-instance routing item under "Areas That Need Help" in CONTRIBUTING. Independent of #1121, which hardens discovery and auto-start. This PR only changes the selection middleware.

Additional Notes

  • Manual check: ran the selection path against two live editors over stdio. Both the roots-based and the UNITY_MCP_PROJECT_DIR-based routing pick the right editor, and an ambiguous case picks nothing.
  • Pre-existing note, not introduced here: on Linux over stdio, discovery is keyed by port, so two editors that bind the same port collapse to one. This routing needs editors on distinct ports. The HTTP path is keyed by hash and is not affected.

Summary by CodeRabbit

  • New Features
    • Automatically disambiguates and selects the correct Unity instance when multiple are running, using client project/launch directory signals.
    • Adds optional project-path context to session details to improve matching and normalization.
    • Honors an optional configured project-directory override during selection.
  • Bug Fixes
    • Prevents incorrect auto-selection when lineage/matching is missing or ambiguous, preserving manual selection when needed.
  • Tests
    • Adds integration coverage for launch-dir and project-path matching, URI handling, caching behavior, and HTTP/stdio selection flows.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 271898b7-88e1-4fa7-acd0-ea9d704ce46d

📥 Commits

Reviewing files that changed from the base of the PR and between 43bd065 and 4fd9cba.

📒 Files selected for processing (2)
  • Server/src/transport/plugin_hub.py
  • Server/src/transport/unity_instance_middleware.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • Server/src/transport/plugin_hub.py
  • Server/src/transport/unity_instance_middleware.py

📝 Walkthrough

Walkthrough

This PR adds launch-directory-aware Unity instance auto-selection using session project paths, MCP roots, path normalization, per-session caching, and PluginHub and stdio discovery integration. Tests cover URI parsing, matching, ambiguity, caching, metadata, and transport flows.

Changes

Unity Instance Auto-selection via Launch Directory Matching

Layer / File(s) Summary
Session metadata contract
Server/src/transport/models.py, Server/src/transport/plugin_hub.py, Server/tests/integration/test_instance_autoselect_by_launch_dir.py
Adds optional session project paths, propagates them through PluginHub responses, formats available instance identifiers, and verifies backward-compatible defaults.
Path conversion and launch-directory caching
Server/src/transport/unity_instance_middleware.py, Server/tests/integration/test_instance_autoselect_by_launch_dir.py
Parses file URIs, normalizes Unity roots, derives session keys, and caches root resolution including failures and concurrent probes.
Unique instance selection
Server/src/transport/unity_instance_middleware.py, Server/tests/integration/test_instance_autoselect_by_launch_dir.py
Matches candidate project roots against launch-directory lineage and selects only an unambiguous candidate.
Transport discovery integration
Server/src/transport/unity_instance_middleware.py, Server/tests/integration/test_instance_autoselect_by_launch_dir.py
Applies disambiguation to PluginHub and stdio discovery, persists unique selections, and validates both flows.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UnityInstanceMiddleware
  participant PluginHub
  participant UnityInstances
  Client->>UnityInstanceMiddleware: advertise MCP roots
  UnityInstanceMiddleware->>PluginHub: get_sessions()
  PluginHub-->>UnityInstanceMiddleware: instance IDs and project paths
  UnityInstanceMiddleware->>UnityInstanceMiddleware: normalize paths and find unique match
  UnityInstanceMiddleware->>UnityInstances: set_active_instance()
  UnityInstanceMiddleware-->>Client: selected Unity instance
Loading

Possibly related PRs

  • CoplayDev/unity-mcp#1236: Updates related Unity instance routing and session identity behavior in the same middleware.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: auto-selecting a Unity instance by launch directory.
Description check ✅ Passed The description follows the template well and includes the required sections with clear change, testing, compatibility, and related issue details.
Docstring Coverage ✅ Passed Docstring coverage is 95.83% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@imurashka
imurashka force-pushed the feat/autoselect-instance-by-launch-dir branch 4 times, most recently from 87aa320 to 4e009c7 Compare June 8, 2026 14:00
@imurashka

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@imurashka

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@imurashka

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@imurashka
imurashka force-pushed the feat/autoselect-instance-by-launch-dir branch from 4e009c7 to 3617b75 Compare June 8, 2026 14:51
@imurashka

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Server/src/transport/unity_instance_middleware.py`:
- Around line 272-280: The current _client_root_dirs logic double-fetches under
concurrency; modify it to memoize the in-flight fetch per key instead of only
storing the final dirs after await: inside _client_root_dirs (use
get_session_key, _root_dirs_by_key, _lock and _fetch_client_root_dirs) acquire
the lock, check for an existing entry and return if present, and if missing
create and store a placeholder future/task for that key before releasing the
lock; then await the actual _fetch_client_root_dirs, resolve the placeholder
with the result and replace it with the real dirs; ensure exceptions clear the
placeholder so subsequent calls can retry.
- Around line 327-345: The selector currently skips candidates with missing
project_path and may still auto-select a single match; change the logic so that
if there is more than one candidate and any candidate has a missing or falsy
project_path, the function treats the set as ambiguous and returns None instead
of auto-selecting. Concretely, before the matching loop or before returning a
single match, check the candidates list for more than one entry and for any
inst_id whose project_path is falsy; if found, return None; otherwise proceed
with the existing _strip_assets/project_real/commonpath matching that populates
matches and the existing matches==1 return behavior. Ensure this uses the
existing variables candidates, project_path, matches, _strip_assets, and
launch_reals so the change is local and minimal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da8dd5e3-4aab-4020-95bf-ad14f04bf876

📥 Commits

Reviewing files that changed from the base of the PR and between 4e009c7 and 3617b75.

📒 Files selected for processing (4)
  • Server/src/transport/models.py
  • Server/src/transport/plugin_hub.py
  • Server/src/transport/unity_instance_middleware.py
  • Server/tests/integration/test_instance_autoselect_by_launch_dir.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • Server/src/transport/models.py
  • Server/src/transport/plugin_hub.py
  • Server/tests/integration/test_instance_autoselect_by_launch_dir.py

Comment thread Server/src/transport/unity_instance_middleware.py
Comment thread Server/src/transport/unity_instance_middleware.py
@imurashka

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

When several Unity editors are connected, auto-select gave up at ">1 instance"
and forced the caller to pass unity_instance on every call. Worktrees made it
worse: identical project names, only the path differs.

Resolve the directories the client is working in - client-agnostic, via the
package's own UNITY_MCP_PROJECT_DIR or the file:// MCP roots the client
advertises (all of them) - and pick the single connected editor whose project
shares a path lineage with one of them. Candidate paths are normalized to the
project root (stdio reports .../Assets, HTTP the root) before matching, and the
roots lookup is cached per session to keep the no-match path off the wire.
Surface project_path on SessionDetails so the PluginHub path can match too.

Purely additive: only the existing ">1 instance, give up" branch changes;
single-instance and explicit selection are untouched, and no/ambiguous match
keeps the "ask the user" behavior.
@imurashka
imurashka force-pushed the feat/autoselect-instance-by-launch-dir branch from 3617b75 to 3e38a06 Compare June 8, 2026 18:07
@imurashka

Copy link
Copy Markdown
Author

@coderabbitai full review

@imurashka
imurashka marked this pull request as ready for review June 8, 2026 18:19

@jordandunmire97-ai jordandunmire97-ai left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow I love this .13 days from now I won't be here physically in this realm. Hopefully when I am back I am a bird . With that . Also why I am approving it myself. And no im not sad. Its very hard to explain
. Don't worry. Never one has . Thank you so much for what you have done .

@imurashka

Copy link
Copy Markdown
Author

Hi @Scriptwonder, when you have a moment, would you be able to take a look at this PR? Happy to adjust anything if needed. Thanks!

…tance-by-launch-dir

# Conflicts:
#	Server/src/transport/unity_instance_middleware.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Server/src/transport/plugin_hub.py (1)

104-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Format the list of instances for better readability.

Stringifying self.available_instances directly embeds the Python list representation (e.g., Available instances: ['RepoA@hash', 'RepoB@hash'].). Joining the elements results in a slightly cleaner, more user-friendly error string.

🔨 Proposed tweak
     def __init__(self, message: str | None = None,
                  available_instances: list[str] | None = None):
         # Carried structurally so the transport layer can surface the ids without
         # parsing the message; also appended to the text for parity with the stdio
         # guard, which lists the ids inline.
         self.available_instances = available_instances or []
         text = message or self._SELECTION_REQUIRED
         if self.available_instances:
-            text = f"{text} Available instances: {self.available_instances}."
+            text = f"{text} Available instances: {', '.join(self.available_instances)}."
         super().__init__(text)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Server/src/transport/plugin_hub.py` around lines 104 - 113, The PluginHub
exception initializer’s available-instance message uses Python list repr
formatting. Update __init__ to join self.available_instances into a readable
comma-separated string before interpolating it into the “Available instances”
text, while preserving the existing empty-list behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Server/src/transport/unity_instance_middleware.py`:
- Around line 125-141: Update get_session_key to prioritize a non-empty
ctx.session_id before client_id, so MCP root caches are isolated per session.
Preserve the existing client_id, user_id, and global fallbacks when no session
ID is available.

---

Nitpick comments:
In `@Server/src/transport/plugin_hub.py`:
- Around line 104-113: The PluginHub exception initializer’s available-instance
message uses Python list repr formatting. Update __init__ to join
self.available_instances into a readable comma-separated string before
interpolating it into the “Available instances” text, while preserving the
existing empty-list behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd2a1b73-daf9-45ca-813b-369e02c6442d

📥 Commits

Reviewing files that changed from the base of the PR and between 3617b75 and 43bd065.

⛔ Files ignored due to path filters (1)
  • Server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Server/src/transport/models.py
  • Server/src/transport/plugin_hub.py
  • Server/src/transport/unity_instance_middleware.py
  • Server/tests/integration/test_instance_autoselect_by_launch_dir.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • Server/src/transport/models.py
  • Server/tests/integration/test_instance_autoselect_by_launch_dir.py

Comment thread Server/src/transport/unity_instance_middleware.py
@imurashka

Copy link
Copy Markdown
Author

Hi @Scriptwonder, this PR is still relevant :)
Please take a look, when you will have time. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants