Skip to content

Conversation

@dsarno
Copy link

@dsarno dsarno commented Nov 21, 2025

  • Problem: Instance routing didn't work under http. When two Unity projects connect over HTTP, they share a single WebSocket session ID and the server can’t reliably route commands. unity_instances and set_active_instance only worked in stdio mode, so HTTP clients lost track of which editor they were targeting.

  • Fix: Each project now stores a session ID under a hash-scoped EditorPrefs key, so simultaneous editors keep their own sessions. unity_instances enumerates HTTP sessions in PluginHub, and set_active_instance (plus PluginHub._resolve_session_id) understands both Project@hash and hash-only inputs while running asynchronously.

  • Result: After these changes, the domain-reload pipeline and tools (create_script, list_resources, etc.) continue targeting the intended Unity instance no matter which transport is in use.

  • Test: uvx [YOUR_PATH]/Server pytest Server/tests/integration/test_instance_routing_comprehensive.py

Summary by CodeRabbit

Release Notes

  • New Features

    • Sessions can now be resolved using hash-based identifiers (Name@hash format)
    • Added HTTP transport support for discovering active Unity instances
    • Implemented per-project session tracking for improved isolation
  • Improvements

    • Enhanced resilience with fallback mechanisms for project identity detection

✏️ Tip: You can customize this high-level summary in your review settings.

@dsarno
Copy link
Author

dsarno commented Nov 21, 2025

@CodeRabbit you there? do a review of this, dude!

@dsarno dsarno force-pushed the fix/http-instance-routing branch from 8a2e04a to 2c865bf Compare November 21, 2025 04:58
@msanatan
Copy link
Owner

@coderabbit_ai how about now?

@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

@msanatan: I'll review the changes in this PR right away!

✅ Actions performed

Review triggered.

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
Copy link

coderabbitai bot commented Nov 21, 2025

Walkthrough

The pull request enhances Unity session identity management across the editor and server layers. Changes introduce project-scoped session IDs with fallback mechanisms for deterministic per-project hashing, add support for compound session resolution formats (Name@hash), implement HTTP transport-based instance discovery via PluginHub, and convert instance routing to async with transport-aware logic.

Changes

Cohort / File(s) Summary
Session Identity (Editor)
MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs
Introduces project-scoped session IDs persisted under project-hash-specific keys with fallback to legacy keys. Adds fallback hash computation via working directory hashing when Application.dataPath is unavailable. Implements one-time cleanup of legacy keys via _legacyKeyCleared flag. Adds EnsureIdentityCache and TryComputeFallbackProjectHash helpers.
Session Resolution (Server)
Server/plugin_hub.py, Server/tools/set_active_instance.py
Adds Name@hash compound format support for session resolution with extraction logic for target hash. Converts set_active_instance to async and introduces HTTP-transport-aware instance discovery via PluginHub queries. Retains existing fallback behavior for session enumeration and error handling.
Instance Discovery & Transport
Server/resources/unity_instances.py
Implements dual-branch transport handling: HTTP path fetches sessions from PluginHub with per-session instance entries (id, name, hash, unity_version, connected_at, session_id), validates required fields, and aggregates duplicates with warnings; stdio path preserves existing pool-based discovery. Adds _is_http_transport() helper and structured error responses.
Test Coverage
Server/tests/integration/test_instance_routing_comprehensive.py
Adds TestInstanceRoutingHTTP class with four asyncio-based tests covering HTTP transport instance enumeration, hash-only resolution, missing sessions, and ambiguous hash prefixes. Imports set_active_instance_tool for HTTP transport path validation.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor<br/>(ProjectIdentityUtility)
    participant Prefs as EditorPrefs
    participant Hash as Hash Cache
    participant WD as Working Dir<br/>(Fallback)
    
    Editor->>Prefs: GetProjectHash() called
    Editor->>Hash: EnsureIdentityCache()
    Hash-->>Editor: Cache ready
    alt Cache hit
        Editor-->>Editor: Return cached hash
    else Cache miss
        Editor->>Prefs: Read dataPath-based hash
        alt dataPath available
            Prefs-->>Editor: Return hash
        else dataPath unavailable (batch mode)
            Editor->>WD: TryComputeFallbackProjectHash()
            WD->>WD: Hash(CWD + "/Assets")
            WD-->>Editor: Deterministic hash
        end
    end
    
    Editor->>Prefs: GetOrCreateSessionId(project_hash)
    alt Project key exists
        Prefs-->>Editor: Return existing GUID
    else Project key missing, legacy key exists
        Prefs->>Prefs: Read legacy key
        Prefs-->>Editor: Return legacy GUID
    else Both keys missing
        Editor->>Editor: Generate new GUID
        Editor->>Prefs: Store under project key
        Editor-->>Editor: Return new GUID
    end
Loading
sequenceDiagram
    participant Client as Client/MCP
    participant Tool as set_active_instance
    participant Hub as PluginHub
    participant Registry as Session Registry
    
    Client->>Tool: set_active_instance("MyProject@abc123")
    Tool->>Tool: Check transport type
    alt HTTP transport
        Tool->>Hub: get_sessions()
        Hub->>Registry: Query all sessions
        Registry-->>Hub: Session list
        Hub-->>Tool: Sessions with (name, hash, id)
        Tool->>Tool: Extract "abc123" from input
        Tool->>Registry: get_session_id_by_hash("abc123")
        Registry-->>Tool: Session ID or error
        Tool-->>Client: Set active + response
    else Stdio transport (fallback)
        Tool->>Tool: Query Unity pool
        Tool->>Tool: Resolve by hash/name
        Tool-->>Client: Set active + response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • ProjectIdentityUtility.cs: Fallback hash computation logic and cache initialization require careful trace-through to ensure determinism in edge cases (batch mode, missing dataPath)
  • plugin_hub.py & set_active_instance.py: Session resolution logic with Name@hash parsing and async conversion adds complexity; verify error handling paths and compatibility with existing clients
  • unity_instances.py: Dual-branch transport handling (HTTP vs stdio) with schema differences introduces behavioral divergence; validate field presence checks and duplicate aggregation logic
  • Test coverage: New HTTP tests help validate transport path but do not reduce review burden for core logic changes
  • Cross-file consistency: Verify async/await patterns are correctly propagated and session key extraction is consistent across files

Poem

🐰 A rabbit hops through sessions with glee,
Per-project hashes dance wild and free,
Name@hash paths now resolve with care,
Fallback mechanisms hop everywhere,
Transport-aware routes, async and swift,
Identity unified—a protocol gift! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main changes: fixing HTTP instance routing and implementing per-project session IDs, which are the core objectives described in the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
Server/resources/unity_instances.py (1)

13-14: Code duplication: _is_http_transport helper.

This helper is duplicated in Server/tools/set_active_instance.py (lines 12-14). Both delegate to _core_is_http_transport from unity_transport. While this keeps modules loosely coupled, consider whether a shared import of _core_is_http_transport directly (or re-exporting from a common location) might reduce duplication.

This is a minor duplication and the current approach maintains module independence, so it's acceptable as-is.

MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs (2)

73-75: Fallback identity cache is solid; consider extending the same robustness to project name

EnsureIdentityCache() + TryComputeFallbackProjectHash() give you deterministic hashes even when Application.dataPath is unavailable, which directly addresses the “everything collapses to 'default'” problem. One minor gap: the fallback path only updates _cachedProjectHash, and GetProjectName() doesn’t invoke EnsureIdentityCache(), so in those edge cases the name will remain "Unknown" even though you’ve derived a meaningful hash. If you ever rely on GetProjectName() in such contexts, it might be worth either calling EnsureIdentityCache() there as well and/or deriving a fallback name from Directory.GetCurrentDirectory() (e.g., mirroring ComputeProjectName on the same synthetic .../Assets path).

Also applies to: 180-202, 204-222


19-19: Project-specific reset logic matches new key format; legacy guard is fine but could be simplified

The project-specific cleanup in ResetSessionId() ("{SessionPrefKey}_{projectHash}") correctly mirrors the new storage key, so tests and tools can reliably clear the right session. The _legacyKeyCleared flag ensures the old SessionPrefKey is only removed once, which is fine for a one-time migration scenario. The trade-off is that if an older plugin version in the same editor session recreates SessionPrefKey, subsequent ResetSessionId() calls won’t delete it again. If mixed-version usage within one editor session is at all likely, you could drop the boolean and just always check/delete the legacy key; otherwise this implementation is acceptable as-is.

Also applies to: 159-172

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5396dff and 2c865bf.

📒 Files selected for processing (5)
  • MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs (4 hunks)
  • Server/plugin_hub.py (1 hunks)
  • Server/resources/unity_instances.py (2 hunks)
  • Server/tests/integration/test_instance_routing_comprehensive.py (2 hunks)
  • Server/tools/set_active_instance.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.5)
Server/resources/unity_instances.py

54-56: Abstract raise to an inner function

(TRY301)


54-56: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (5)
Server/plugin_hub.py (1)

225-232: Hash extraction logic handles edge cases defensively.

The rpartition logic correctly extracts the hash from Name@hash or treats the full string as a hash. One edge case: if unity_instance is "Name@" (trailing @ with no suffix), line 230 sets target_hash to None, which triggers the fallback path (first available session) rather than returning an error. This is lenient but safe.

Server/tests/integration/test_instance_routing_comprehensive.py (1)

172-295: Comprehensive HTTP transport test coverage.

The four new tests validate the HTTP transport path for set_active_instance:

  • Full Name@hash format resolution
  • Hash-only resolution
  • Missing hash error handling
  • Ambiguous hash prefix error handling

The tests properly mock PluginHub.get_sessions and validate both success and error scenarios.

Server/tools/set_active_instance.py (1)

57-62: Good defensive validation for empty input.

The explicit check for empty or whitespace-only instance identifiers prevents confusing errors downstream and provides a clear, actionable message.

Server/resources/unity_instances.py (1)

58-65: HTTP instance structure looks correct.

The instance dictionaries include appropriate fields for HTTP transport, including the transport-specific connected_at timestamp and session_id for reference. This aligns with the docstring updates noting which fields are transport-specific.

MCPForUnity/Editor/Helpers/ProjectIdentityUtility.cs (1)

127-142: Per-project session ID scoping via EditorPrefs key suffix looks correct

Using GetProjectHash() to suffix SessionPrefKey and storing the GUID under "{SessionPrefKey}_{projectHash}" gives you the per-project isolation you need while preserving the legacy hash semantics. The try/catch around EditorPrefs and the fallback to an in-memory Guid in error/batch scenarios are reasonable safeguards; I don’t see correctness issues here.

Comment on lines +53 to +56
if not project or not project_hash:
raise ValueError(
"PluginHub session missing required 'project' or 'hash' fields."
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Strict validation may be too restrictive.

If any session from PluginHub is missing project or hash fields, the entire operation fails (raises ValueError caught at line 115). This differs from Server/tools/set_active_instance.py (lines 32-33), which silently skips sessions without a hash.

Consider whether this strict validation is intentional:

  • Current behavior: One malformed session breaks the entire unity://instances listing
  • Alternative: Skip invalid sessions with a warning, allowing valid sessions to be returned

The strict approach catches data quality issues early, but may impact availability if PluginHub occasionally returns incomplete session data.

Static analysis notes: Ruff suggests abstracting the raise to an inner function (TRY301) and avoiding long messages outside the exception class (TRY003), though these are minor style concerns.

🧰 Tools
🪛 Ruff (0.14.5)

54-56: Abstract raise to an inner function

(TRY301)


54-56: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In Server/resources/unity_instances.py around lines 53-56 the code raises a
ValueError when a PluginHub session lacks 'project' or 'hash', which causes the
whole unity://instances listing to fail; change this to mirror
Server/tools/set_active_instance.py by skipping malformed sessions instead of
raising: log a warning (include minimal context like session id or index) and
continue to the next session so valid sessions are returned; keep the validation
check, but replace the exception with a warning + continue, and if needed
centralize the check into a small helper to satisfy static analysis (reduce
message length in exception handling).

Comment on lines +24 to +43
# Discover running instances based on transport
if _is_http_transport():
sessions_data = await PluginHub.get_sessions()
sessions = sessions_data.get("sessions", {}) if isinstance(sessions_data, dict) else {}
instances = []
for session_id, session in sessions.items():
project = session.get("project") or session.get("project_name") or "Unknown"
hash_value = session.get("hash")
if not hash_value:
continue
inst_id = f"{project}@{hash_value}"
instances.append(SimpleNamespace(
id=inst_id,
hash=hash_value,
name=project,
session_id=session_id,
))
else:
pool = get_unity_connection_pool()
instances = pool.discover_all_instances(force_refresh=True)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling compared to unity_instances.py.

This HTTP transport discovery path differs from Server/resources/unity_instances.py in how it handles sessions with missing fields:

This file (set_active_instance.py):

  • Line 30: Falls back to "Unknown" if project name is missing
  • Lines 32-33: Silently skips sessions without hash (continues)

unity_instances.py:

  • Line 50: No "Unknown" fallback for project name
  • Lines 53-56: Raises ValueError if project or hash is missing

This inconsistency means:

  • set_active_instance is more lenient (skips bad sessions, uses "Unknown" for missing names)
  • unity_instances is stricter (fails if any session has missing required fields)

Consider aligning the validation logic:

  • Should both silently skip invalid sessions, or both fail fast?
  • Should "Unknown" be an acceptable fallback, or should project name be required?

The lenient approach in this file seems more resilient (one bad session doesn't break everything), while the strict approach in unity_instances.py might catch data quality issues earlier. Document the intended behavior or unify the validation strategy.

🤖 Prompt for AI Agents
In Server/tools/set_active_instance.py around lines 24-43, the HTTP discovery
code is inconsistent with Server/resources/unity_instances.py: remove the
fallback to "Unknown" and the silent continue for missing fields and instead
validate sessions the same way as unity_instances.py by raising a ValueError
when required fields (project or hash) are missing; update the code to require
session.get("project") (no "Unknown" fallback), require session.get("hash") and
raise a clear ValueError if either is absent, and add a brief comment/docstring
indicating the strict validation choice so the behavior is consistent with
unity_instances.py.

@msanatan
Copy link
Owner

Closed by #5 which solves the merge conflict

@msanatan msanatan closed this Nov 21, 2025
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