Skip to content

Conversation

@dsarno
Copy link

@dsarno dsarno commented Jan 7, 2026

Summary

This PR adds critical testing infrastructure improvements and tool enhancements to prepare for launch. All changes merge cleanly with no conflicts and pass all test suites (269 Unity EditMode tests, 118 Python integration tests).

Key Features

🚀 Server-Side Test Polling (wait_timeout parameter)

  • Problem: Clients polling get_test_job rapidly were triggering loop detection warnings
  • Solution: Added optional wait_timeout parameter (30-60s recommended) to get_test_job tool
  • Benefit: Server waits for test completion internally, reducing polling frequency and avoiding client-side throttling
  • Usage: get_test_job(job_id="...", wait_timeout=60) - returns immediately if tests finish early

🛠️ ScriptableObject Tool Enhancements

  • Enhanced dry-run validation for AnimationCurve and Quaternion types
  • Added 279 new stress tests (ManageScriptableObjectStressTests)
  • Better error messages for invalid property values
  • Improved vector parsing and parameter coercion

💾 Test Mode Improvements (Fix CoplayDev#525)

  • Fixed: Dirty scenes now saved properly for all test modes (EditMode & PlayMode)
  • Previously only handled EditMode, causing data loss in PlayMode tests
  • Updated warning messages to reflect all test modes

🐛 Bug Fixes

  • Fixed missing FAST_FAIL_TIMEOUT constant in PluginHub
  • Fixed integration tests after merges
  • Improved Pydantic model handling in test assertions
  • Enhanced editor state caching and test runner service

Testing

  • ✅ All 269 Unity EditMode tests passing (265 passed, 4 intentionally skipped)
  • ✅ All 118 Python integration tests passing (including 7 xpassed improvements)
  • ✅ No merge conflicts with target branch
  • ✅ Test run time: ~30s Unity, ~3.4s Python

Files Changed

  • 17 files changed (+776 / -98 lines)
  • Core changes: run_tests.py, ManageScriptableObject.cs, TestRunnerService.cs, ParamCoercion.cs, VectorParsing.cs
  • New: ManageScriptableObjectStressTests.cs (279 comprehensive tests)

Breaking Changes

None - all changes are backward compatible

Dependencies

No new dependencies added


Ready to merge

Summary by CodeRabbit

Release Notes

  • New Features

    • Added VFX tools for LineRenderer, TrailRenderer, and ParticleSystem management with shape creation, property controls, and playback support.
    • Expanded GameObject management with new create, modify, delete, duplicate, and relative move operations.
    • Refactored test execution to asynchronous job-based system with polling support.
    • Enhanced editor state information with richer advisory and staleness data.
  • Bug Fixes

    • Updated URI scheme endpoints and improved scene-saving behavior for test runs.

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

dsarno and others added 12 commits January 7, 2026 09:54
Scripts for running the NL/T/GO test suites locally against a GUI Unity
Editor, complementing the CI workflows in .github/workflows/.

Benefits:
- 10-100x faster than CI (no Docker startup)
- Real-time Unity console debugging
- Single test execution for rapid iteration
- Auto-detects HTTP vs stdio transport

Usage:
  ./scripts/local-test/setup.sh           # One-time setup
  ./scripts/local-test/quick-test.sh NL-0 # Run single test
  ./scripts/local-test/run-nl-suite-local.sh  # Full suite

See scripts/local-test/README.md for details.

Also updated .gitignore to:
- Allow scripts/local-test/ to be tracked
- Ignore generated artifacts (reports/*.xml, .claude/local/, .unity-mcp/)
Move SaveDirtyScenesIfNeeded() call outside the PlayMode conditional
so EditMode tests don't get blocked by Unity's "Save Scene" modal dialog.

This prevents MCP from timing out when running EditMode tests with unsaved
scene changes.
The FAST_FAIL_TIMEOUT class attribute was referenced on line 149 but never
defined, causing AttributeError on every ping attempt. This error was silently
caught by the broad 'except Exception' handler, causing all fast-fail commands
(read_console, get_editor_state, ping) to fail after 6 seconds of retries with
'ping not answered' error.

Added FAST_FAIL_TIMEOUT = 10 to define a 10-second timeout for fast-fail
commands, matching the intent of the existing fast-fail infrastructure.
… and Quaternion

Dry-run validation now validates value formats, not just property existence:

- AnimationCurve: Validates structure ({keys:[...]} or direct array), checks
  each keyframe is an object, validates numeric fields (time, value, inSlope,
  outSlope, inWeight, outWeight) and integer fields (weightedMode)
- Quaternion: Validates array length (3 for Euler, 4 for raw) or object
  structure ({x,y,z,w} or {euler:[x,y,z]}), ensures all components are numeric

Refactored shared validation helpers into appropriate locations:
- ParamCoercion: IsNumericToken, ValidateNumericField, ValidateIntegerField
- VectorParsing: ValidateAnimationCurveFormat, ValidateQuaternionFormat

Added comprehensive XML documentation clarifying keyframe field defaults
(all default to 0 except as noted).

Added 5 new dry-run validation tests covering valid and invalid formats
for both AnimationCurve and Quaternion properties.
Resolved conflict in plugin_hub.py: both branches added FAST_FAIL_TIMEOUT
(fixing the same missing constant bug). Kept msanatan's version (2.0s with
better comment) as it's more appropriate for fast-fail behavior.
- test_refresh_unity_retry_recovery: Mock now handles both refresh_unity and
  get_editor_state commands (refresh_unity internally calls get_editor_state
  when wait_for_ready=True)
- test_run_tests_async_forwards_params: Mock response now includes required
  'mode' field for RunTestsStartResponse Pydantic validation
- test_get_test_job_forwards_job_id: Updated to handle GetTestJobResponse as
  Pydantic model instead of dict (use model_dump() for assertions)
Follow-up to PR CoplayDev#527: Since SaveDirtyScenesIfNeeded() now runs for all test modes, update the warning message to say 'tests' instead of 'PlayMode tests'.
…p detection

When polling for test completion, MCP clients like Cursor can detect the
repeated get_test_job calls as 'looping' and terminate the agent.

Added wait_timeout parameter that makes the server wait internally for tests
to complete (polling Unity every 2s) before returning. This dramatically
reduces client-side tool calls from 10-20 down to 1-2, avoiding loop detection.

Usage: get_test_job(job_id='xxx', wait_timeout=30)
- Returns immediately if tests complete within timeout
- Returns current status if timeout expires (client can call again)
- Recommended: 30-60 seconds
@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This PR comprehensively restructures MCPForUnity's architecture by migrating all resource URIs from unity:// to mcpforunity://, modularizing GameObject and VFX manipulation tools into dedicated subdirectories, consolidating editor state handling, and refactoring test execution from synchronous to asynchronous job-based polling. Additionally, new validation helpers for numeric and vector types are introduced, and menu items are relabeled for clarity.

Changes

Cohort / File(s) Summary
URI Scheme Migration
.claude/prompts/*, README*.md, Server/src/main.py, Server/src/services/resources/*, Server/src/services/tools/*, Server/src/transport/*, Server/tests/integration/*, MCPForUnity/Editor/Tools/ManageScript.cs
Systematic replacement of unity:// scheme with mcpforunity:// across all prompt files, documentation, server resource/tool decorators, and client URI construction. Affects resource registration, error messages, and user-facing documentation.
GameObject Tools Modularization
MCPForUnity/Editor/Tools/GameObjects/*, MCPForUnity/Editor/Tools/ManageGameObject.cs
New modular structure: GameObjectCreate.cs, GameObjectModify.cs, GameObjectDelete.cs, GameObjectDuplicate.cs, GameObjectMoveRelative.cs, ManageGameObjectCommon.cs (search utilities), ComponentResolver.cs (component helpers), GameObjectComponentHelpers.cs (property manipulation), and consolidated ManageGameObject.cs wrapper. Old monolithic file removed.
VFX Tools Restructuring
MCPForUnity/Editor/Tools/Vfx/*, MCPForUnity/Editor/Tools/ManageVFX.cs
New specialized classes: ManageVFX.cs (dispatcher), ParticleControl.cs, ParticleRead.cs, ParticleWrite.cs, ParticleCommon.cs, LineCreate.cs, LineRead.cs, LineWrite.cs, TrailControl.cs, TrailRead.cs, TrailWrite.cs, ManageVfxCommon.cs. Old monolithic file removed.
Async Test Execution
MCPForUnity/Editor/Services/TestJobManager.cs, MCPForUnity/Editor/Services/TestRunnerService.cs, Server/src/services/tools/run_tests.py, Server/src/services/tools/test_jobs.py
Refactored from synchronous run_tests_async to modular run_tests (starts job) + get_test_job (polls status). Scene-save logic now applies to all modes. TestJobManager methods visibility narrowed to internal. Old test_jobs.py and RunTestsAsync.cs removed.
Editor State Consolidation
MCPForUnity/Editor/Resources/Editor/EditorState.cs, MCPForUnity/Editor/Resources/Editor/EditorStateV2.cs, MCPForUnity/Editor/Services/EditorStateCache.cs, Server/src/services/resources/editor_state.py, Server/src/services/resources/editor_state_v2.py
V2 snapshot models introduced in EditorStateCache; EditorStateV2 resource removed; editor_state.py expanded with comprehensive V2 data models and external-change scanning integration. Old editor_state_v2.py deleted.
Vector & Numeric Validation Helpers
MCPForUnity/Editor/Helpers/ParamCoercion.cs, MCPForUnity/Editor/Helpers/VectorParsing.cs
Added IsNumericToken, ValidateNumericField, ValidateIntegerField to ParamCoercion. Refactored ParseColorOrDefault into two overloads; added ValidateAnimationCurveFormat, ValidateQuaternionFormat to VectorParsing.
Menu Item Labeling
MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs, MCPForUnity/Editor/Dependencies/DependencyManager.cs
Menu item text changed from "Setup Window" to "Local Setup Window"; corresponding guidance message updated.
Asset Modification Enhancement
MCPForUnity/Editor/Tools/ManageAsset.cs
Added per-component property modification for GameObject assets; imports adjusted for broader tool access.
Test Validation
MCPForUnity/Editor/Tools/ManageScriptableObject.cs
Enhanced dry-run validation for AnimationCurve and Quaternion using new VectorParsing.*Format validators with detailed error messages.
Test Infrastructure Updates
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/*, Server/tests/integration/*
Updated imports to reference modularized tool namespaces; added new validation tests for AnimationCurve/Quaternion formats; refactored async test execution tests to use new job-based API.
Documentation & Scripts
README*.md, docs/README-DEV-zh.md, docs/v8_NEW_NETWORKING_SETUP.md, tools/prepare_unity_asset_store_release.py, Server/src/services/custom_tool_service.py
Comprehensive documentation updates reflecting new tool structure, URI scheme, and configuration flows; removed asset store menu item rewriting.
Whitespace & Formatting
MCPForUnity/Editor/Tools/FindGameObjects.cs, MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs, MCPForUnity/Editor/Tools/JsonUtil.cs, MCPForUnity/Editor/Tools/GetTestJob.cs, MCPForUnity/Editor/Tools/ManageComponents.cs, Server/src/*
Trailing blank lines removed and minor formatting adjustments across multiple files; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Server as MCP Server
    participant Transport
    participant UnityEditor as Unity Editor

    Note over Client,UnityEditor: Async Test Execution Flow (New)
    Client->>Server: run_tests(mode, filters...)
    Server->>Transport: send_with_unity_instance("run_tests", params)
    Transport->>UnityEditor: start test job → job_id
    UnityEditor-->>Transport: { success: true, job_id, mode }
    Transport-->>Server: job_id
    Server-->>Client: { job_id, mode, status: "started" }
    
    Note over Client: Poll for results
    Client->>Server: get_test_job(job_id, wait_timeout?)
    Server->>Transport: send_with_unity_instance("get_test_job", job_id)
    Transport->>UnityEditor: query job status
    alt Tests Still Running
        UnityEditor-->>Transport: { success: true, job_id, status: "running" }
    else Tests Complete
        UnityEditor-->>Transport: { success: true, job_id, status: "completed", result }
    end
    Transport-->>Server: status/result
    Server-->>Client: GetTestJobResponse
Loading
sequenceDiagram
    autonumber
    participant Client
    participant Server as ManageGameObject
    participant Resolvers as Helpers
    participant UnityEditor as Unity Editor

    Note over Client,UnityEditor: Modular GameObject Tool Flow (New)
    Client->>Server: HandleCommand({ action: "create", name, ... })
    
    alt action == "create"
        Server->>Resolvers: GameObjectCreate.Handle(params)
        Resolvers->>Resolvers: Validate name, resolve prefab/parent
        Resolvers->>UnityEditor: Undo + instantiate/create
        Resolvers->>Resolvers: AddComponentInternal (via ComponentHelpers)
        Resolvers->>Resolvers: SetComponentPropertiesInternal (via GameObjectComponentHelpers)
        Resolvers-->>Server: SuccessResponse with serialized object
    else action == "modify"
        Server->>Resolvers: GameObjectModify.Handle(params)
        Resolvers->>Resolvers: Find target via ManageGameObjectCommon
        Resolvers->>UnityEditor: Apply transform/properties/components
        Resolvers-->>Server: SuccessResponse or ErrorResponse
    else action == "delete"
        Server->>Resolvers: GameObjectDelete.Handle(params)
        Resolvers->>Resolvers: Find targets via ManageGameObjectCommon
        Resolvers->>UnityEditor: Undo.DestroyObjectImmediate
        Resolvers-->>Server: SuccessResponse with deleted list
    end
    
    Server-->>Client: SuccessResponse | ErrorResponse
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 A rabbit hops through code refactored clean,
From unity to mcpforunity's gleam—
Async tests now poll with patient care,
GameObjects modular, helpers everywhere,
The editor state grows rich and wise,
As validation sharpens watching eyes!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'Pre-Launch Enhancements: Testing Infrastructure & Tool Improvements' comprehensively summarizes the main changes: testing infrastructure improvements (get_test_job with wait_timeout, job-based polling), tool enhancements (ScriptableObject validation, ParamCoercion/VectorParsing improvements), and bug fixes (SaveDirtyScenes, FAST_FAIL_TIMEOUT).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@msanatan msanatan changed the base branch from main to pre-release-tidying January 7, 2026 22:42
@msanatan msanatan merged commit ce1fbc6 into msanatan:pre-release-tidying Jan 7, 2026
1 check was pending
msanatan added a commit that referenced this pull request Jan 7, 2026
* refactor: Split ParseColorOrDefault into two overloads and change default to Color.white

* Auto-format Python code

* Remove unused Python module

* Refactored VFX functionality into multiple files

Tested everything, works like a charm

* Rename ManageVfx folder to just Vfx

We know what it's managing

* Clean up whitespace on plugin tools and resources

* Make ManageGameObject less of a monolith by splitting it out into different files

* Remove obsolete FindObjectByInstruction method

We also update the namespace for ManageVFX

* refactor: Consolidate editor state resources into single canonical implementation

Merged EditorStateV2 into EditorState, making get_editor_state the canonical resource. Updated Unity C# to use EditorStateCache directly. Enhanced Python implementation with advice/staleness enrichment, external changes detection, and instance ID inference. Removed duplicate EditorStateV2 resource and legacy fallback mapping.

* Validate editor state with Pydantic models in both C# and Python

Added strongly-typed Pydantic models for EditorStateV2 schema in Python and corresponding C# classes with JsonProperty attributes. Updated C# to serialize using typed classes instead of anonymous objects. Python now validates the editor state payload before returning it, catching schema mismatches early.

* Consolidate run_tests and run_tests_async into single async implementation

Merged run_tests_async into run_tests, making async job-based execution the default behavior. Removed synchronous blocking test execution. Updated RunTests.cs to start test jobs immediately and return job_id for polling. Changed TestJobManager methods to internal visibility. Updated README to reflect single run_tests_async tool. Python implementation now uses async job pattern exclusively.

* Validate test job responses with Pydantic models in Python

* Change resources URI from unity:// to mcpforunity://

It should reduce conflicts with other Unity MCPs that users try, and to comply with Unity's requests regarding use of their company and product name

* Update README with all tools + better listing for resources

* Update other references to resources

* Updated translated doc - unfortunately I cannot verify

* Update the Chinese translation of the dev docks

* Change menu item from Setup Window to Local Setup Window

We now differentiate whether it's HTTP local or remote

* Fix URIs for menu items and tests

* Shouldn't have removed it

* Minor edits from CodeRabbit feedback

* Don't use reflection which takes longer

* Fix failing python tests

* Add serialization helpers for ParticleSystem curves and MinMaxCurve types

Added SerializeAnimationCurve and SerializeMinMaxCurve helper methods to properly serialize Unity's curve types. Updated GetInfo to use these helpers for startLifetime, startSpeed, startSize, gravityModifier, and rateOverTime instead of only reading constant values.

* Use ctx param

* Update Server/src/services/tools/run_tests.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Minor fixes

* Rename anything EditorStateV2 to just EditorState

It's the default, there's no old version

* Make infer_single_instance_id public by removing underscore prefix

* Fix Python tests, again

* Replace AI generated .meta files with actual Unity ones

* ## Pre-Launch Enhancements: Testing Infrastructure & Tool Improvements (#8)

* Add local test harness for fast developer iteration

Scripts for running the NL/T/GO test suites locally against a GUI Unity
Editor, complementing the CI workflows in .github/workflows/.

Benefits:
- 10-100x faster than CI (no Docker startup)
- Real-time Unity console debugging
- Single test execution for rapid iteration
- Auto-detects HTTP vs stdio transport

Usage:
  ./scripts/local-test/setup.sh           # One-time setup
  ./scripts/local-test/quick-test.sh NL-0 # Run single test
  ./scripts/local-test/run-nl-suite-local.sh  # Full suite

See scripts/local-test/README.md for details.

Also updated .gitignore to:
- Allow scripts/local-test/ to be tracked
- Ignore generated artifacts (reports/*.xml, .claude/local/, .unity-mcp/)

* Fix issue CoplayDev#525: Save dirty scenes for all test modes

Move SaveDirtyScenesIfNeeded() call outside the PlayMode conditional
so EditMode tests don't get blocked by Unity's "Save Scene" modal dialog.

This prevents MCP from timing out when running EditMode tests with unsaved
scene changes.

* fix: add missing FAST_FAIL_TIMEOUT constant in PluginHub

The FAST_FAIL_TIMEOUT class attribute was referenced on line 149 but never
defined, causing AttributeError on every ping attempt. This error was silently
caught by the broad 'except Exception' handler, causing all fast-fail commands
(read_console, get_editor_state, ping) to fail after 6 seconds of retries with
'ping not answered' error.

Added FAST_FAIL_TIMEOUT = 10 to define a 10-second timeout for fast-fail
commands, matching the intent of the existing fast-fail infrastructure.

* feat(ScriptableObject): enhance dry-run validation for AnimationCurve and Quaternion

Dry-run validation now validates value formats, not just property existence:

- AnimationCurve: Validates structure ({keys:[...]} or direct array), checks
  each keyframe is an object, validates numeric fields (time, value, inSlope,
  outSlope, inWeight, outWeight) and integer fields (weightedMode)
- Quaternion: Validates array length (3 for Euler, 4 for raw) or object
  structure ({x,y,z,w} or {euler:[x,y,z]}), ensures all components are numeric

Refactored shared validation helpers into appropriate locations:
- ParamCoercion: IsNumericToken, ValidateNumericField, ValidateIntegerField
- VectorParsing: ValidateAnimationCurveFormat, ValidateQuaternionFormat

Added comprehensive XML documentation clarifying keyframe field defaults
(all default to 0 except as noted).

Added 5 new dry-run validation tests covering valid and invalid formats
for both AnimationCurve and Quaternion properties.

* test: fix integration tests after merge

- test_refresh_unity_retry_recovery: Mock now handles both refresh_unity and
  get_editor_state commands (refresh_unity internally calls get_editor_state
  when wait_for_ready=True)
- test_run_tests_async_forwards_params: Mock response now includes required
  'mode' field for RunTestsStartResponse Pydantic validation
- test_get_test_job_forwards_job_id: Updated to handle GetTestJobResponse as
  Pydantic model instead of dict (use model_dump() for assertions)

* Update warning message to apply to all test modes

Follow-up to PR CoplayDev#527: Since SaveDirtyScenesIfNeeded() now runs for all test modes, update the warning message to say 'tests' instead of 'PlayMode tests'.

* feat(run_tests): add wait_timeout to get_test_job to avoid client loop detection

When polling for test completion, MCP clients like Cursor can detect the
repeated get_test_job calls as 'looping' and terminate the agent.

Added wait_timeout parameter that makes the server wait internally for tests
to complete (polling Unity every 2s) before returning. This dramatically
reduces client-side tool calls from 10-20 down to 1-2, avoiding loop detection.

Usage: get_test_job(job_id='xxx', wait_timeout=30)
- Returns immediately if tests complete within timeout
- Returns current status if timeout expires (client can call again)
- Recommended: 30-60 seconds

* fix: use Pydantic attribute access in test_run_tests_async for merge compatibility

* revert: remove local test harness - will be submitted in separate PR

---------

Co-authored-by: Scott Jennings <scott.jennings+CIGINT@cloudimperiumgames.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: dsarno <david@lighthaus.us>
Co-authored-by: Scott Jennings <scott.jennings+CIGINT@cloudimperiumgames.com>
@dsarno dsarno deleted the pre-launch-enhancements branch January 15, 2026 16:04
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.

EditMode tests blocked by "Save Scene" dialog when scene is dirty

2 participants