Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Oct 23, 2025

Summary

This branch widens python tool schemas and adds server-side coercion so clients that stringify numbers/booleans/vectors no longer fail pre-validation. Behavior is backwards compatible; native types still work.

Dependencies

Added FastMCP as an explicit dependency and standardized imports to fastmcp; aligned mcp sdk package version to FastMCP’s constraints.

Key changes

  • Schema widening + coercion
    • Numbers (accept string/float; coerce to int): resources/read (start_line, line_count, head_bytes, tail_lines), manage_asset.search (page_size, page_number), run_tests (timeout_seconds), manage_scene (build_index).
    • Booleans (accept bool|string; coerce): read_console (include_stacktrace), resources/find_in_file (ignore_case), manage_editor (wait_for_completion), manage_gameobject (find_all, search_inactive, search_in_children, save_as_prefab, set_active, includeNonPublicSerialized).
    • Vectors (accept list[float]|string; parse): manage_gameobject (position, rotation, scale).
  • Tool descriptions updated with brief usage notes for stringified params.
  • Keep imports on fastmcp (project standard); remove reliance on mcp.server.fastmcp.
  • Version bump to force client schema refresh.
  • Fix minor typo in manage_shader signature (stray character after ctx: Context).

Compatibility

  • Existing correctly-typed calls continue to work.
  • Stringified inputs now pass client-side schema checks and are safely coerced server-side.

Fixes #300

Summary by CodeRabbit

  • Chores

    • Project version updated to 6.2.3 and dependency entries adjusted.
  • New Features

    • Widespread input flexibility: many numeric and boolean parameters now accept string representations (e.g., "true", "5").
    • Added bulk component property support when managing game objects.
  • Tests

    • Test suite simplified and hardened: removed in-test import stubs and added defensive import checks; new tests cover parameter coercion.

dsarno and others added 8 commits October 22, 2025 11:09
- Update pyproject.toml to use fastmcp>=2.12.5 instead of mcp[cli]
- Replace all imports from mcp.server.fastmcp to fastmcp
- Maintain MCP protocol compliance with mcp>=1.16.0
- All 15 files updated with new import statements
- Server and tools registration working with FastMCP 2.0
…et search/read_resource/run_tests) for better LLM compatibility
…itor, and manage_gameobject

- read_console: accept int|str for count parameter with coercion
- manage_editor: accept bool|str for wait_for_completion with coercion
- manage_gameobject: accept bool|str for all boolean parameters with coercion
- All tools now handle string parameters gracefully and convert to proper types internally
Adds fastmcp as explicit dependency for FastMCP 2.0 migration.
Relaxes mcp version constraint to support broader compatibility.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Removes stub mcp modules from test files that were conflicting with
the real mcp and fastmcp packages now installed as dependencies.
Adds tests/__init__.py to make tests a proper Python package.

This fixes test collection errors after migrating to FastMCP 2.0.

Test results: 40 passed, 7 xpassed, 5 skipped, 1 failed (pre-existing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updates all remaining files to use `from fastmcp import` instead of
the old `from mcp.server.fastmcp import` path.

Changes:
- server.py: Update FastMCP import
- tools/__init__.py: Update FastMCP import
- resources/__init__.py: Update FastMCP import
- tools/manage_script.py, read_console.py, resource_tools.py: Update imports
- test stubs: Update to stub `fastmcp` instead of `mcp.server.fastmcp`

Addresses PR review feedback about incomplete migration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

Updates dependencies and package versions, replaces imports from mcp.server.fastmcp with fastmcp, broadens many tool parameter types to accept string/alternate forms and adds runtime coercion, adjusts prefab/param handling in manage_gameobject, adds/coerces inputs in several tools, and simplifies test stubbing to use fastmcp or real imports.

Changes

Cohort / File(s) Summary
Dependency & versions
MCPForUnity/UnityMcpServer~/src/pyproject.toml, MCPForUnity/UnityMcpServer~/src/server_version.txt, MCPForUnity/package.json
Bumps package versions (pyproject -> 6.2.2, server_version.txt & package.json -> 6.2.3) and replaces mcp[cli]>=1.17.0 with fastmcp>=2.12.5 and mcp>=1.16.0.
Import path migrations
MCPForUnity/UnityMcpServer~/src/resources/__init__.py, .../src/server.py, .../src/tools/__init__.py, .../src/tools/execute_menu_item.py, .../src/tools/manage_prefabs.py, .../src/tools/manage_script.py, .../src/tools/script_apply_edits.py, .../src/tools/manage_shader.py
Replaces imports from mcp.server.fastmcp to fastmcp for FastMCP/Context across modules.
Parameter coercion — simple
.../src/tools/manage_asset.py
Broadens page_size/page_number type hints to accept `int
Parameter coercion — moderate
.../src/tools/manage_editor.py, .../src/tools/manage_scene.py, .../src/tools/read_console.py, .../src/tools/resource_tools.py
Broadens boolean/int params to accept string forms; updates decorator descriptions; adds helper coercion functions (_coerce_bool, _coerce_int) and normalizes inputs before sending commands.
Parameter coercion — complex
.../src/tools/manage_gameobject.py, .../src/tools/run_tests.py
Significant changes: many params accept string forms (vectors, booleans), new component_properties param, prefabPath/prefabFolder handling, vector parsing/coercion, timeout coercion in run_tests, and updated response wrapping/validation.
Tests — stubbing & new tests
tests/__init__.py, tests/*.py (many), tests/test_manage_asset_param_coercion.py, tests/test_manage_gameobject_param_coercion.py
Removes previous in-test stubbing of mcp.server.fastmcp in many tests, simplifies mocks to fastmcp or rely on actual imports, adds new unit tests verifying numeric/boolean coercion for manage_asset and manage_gameobject.
Small API/signature adjustments
.../src/tools/read_console.py, .../src/tools/manage_scene.py, .../src/tools/manage_editor.py, .../src/tools/resource_tools.py
Public function parameter type annotations updated (e.g., int → `int

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller as Client/CLI
    participant Tool as MCP Tool
    participant Coerce as Coercion Helpers
    participant RPC as send_command_with_retry
    note over Coerce: New helpers: _coerce_bool, _coerce_int, vector parser
    Caller->>Tool: invoke tool with raw params (strings/nums)
    Tool->>Coerce: send raw params for normalization
    Coerce->>Coerce: parse/convert strings → bool/int/list
    Coerce-->>Tool: return normalized params
    Tool->>RPC: send_command_with_retry(params with coerced types)
    RPC-->>Tool: response
    Tool-->>Caller: normalized response (success / structured error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • justinpbarnett

Poem

🐇 I hopped through imports, swift and spry,
fastmcp now waving hello — no more mcp.server sigh.
Strings turned to numbers, trues found their way,
Prefabs and vectors now parse without delay.
A tiny rabbit cheers the types now bright and gay.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Harden MCP tool parameter handling to eliminate 'invalid param' errors" directly addresses the main objective of the changeset. The raw summary demonstrates extensive modifications across multiple tool files (manage_asset, manage_editor, manage_gameobject, manage_scene, read_console, and others) where parameter types have been widened to accept string representations and coercion logic has been added to handle these conversions. The title is concise, specific about what changed (parameter handling), and clearly communicates the purpose (eliminating invalid param errors). It accurately reflects the primary theme of the changes without being vague or overly broad.
Linked Issues Check ✅ Passed The pull request comprehensively addresses the coding requirements from linked issue #300. For manage_gameobject, boolean parameters (find_all, search_inactive, search_in_children, save_as_prefab, set_active, includeNonPublicSerialized) have been widened to accept bool | str with coercion logic added, and tag searches now map tag to searchTerm for backward compatibility. For manage_asset, pagination parameters (page_size, page_number) have been broadened to accept int | float | str. For read_console, the count parameter now accepts int | str with coercion. Additional fixes include parameter type widening in manage_scene (build_index), resource_tools (start_line, line_count, head_bytes, tail_lines, ignore_case), and manage_editor (wait_for_completion). All changes include defensive coercion helpers to convert string inputs to their native types before processing, directly satisfying the requirement to "properly handle string-to-type conversion."
Out of Scope Changes Check ✅ Passed All code changes in the pull request align with stated objectives in the PR description. The dependency updates (adding fastmcp, aligning mcp version) are explicitly mentioned as necessary for the schema hardening. The import path changes from mcp.server.fastmcp to fastmcp are part of the stated modernization effort to "remove reliance on mcp.server.fastmcp" and establish fastmcp as the project standard. Version bumps across pyproject.toml, server_version.txt, and package.json are intentional per the objective to "force client schema refresh." Test file modifications remove obsolete module stubs and add new parameter coercion tests, which are in scope for aligning with the typing refactors and test-suite updates mentioned in the PR objectives. No extraneous changes outside the stated scope were identified.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)

92-100: Add check for "stackTrace" (camelCase) to avoid silently failing to strip traces.

The Python code checks only for "stacktrace" (lowercase) at line 97–98, but the C# backend constructs responses with property stackTrace (camelCase). When serialized via System.Text.Json (the standard .NET serializer), this becomes JSON key "stackTrace". The tests pass because mock fixtures use lowercase, masking the real issue. When include_stacktrace=False, traces from actual backend responses won't be removed.

Update line 97 to check both variants:

if isinstance(line, dict) and ("stacktrace" in line or "stackTrace" in line):
    line.pop("stacktrace", None)
    line.pop("stackTrace", None)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (2)

135-135: Add action-specific required-parameter checks.

Avoids opaque failures from the Unity side when required fields are absent.

         # Prepare parameters, removing None values
+        # Additional action-specific validations
+        if action == "get_component" and not component_name:
+            return {"success": False, "message": "'get_component' requires 'component_name'."}
+        if action == "set_component_property" and not component_properties:
+            return {"success": False, "message": "'set_component_property' requires 'component_properties'."}
+        if action == "add_component" and not (components_to_add or component_name):
+            return {"success": False, "message": "'add_component' requires 'components_to_add' or 'component_name'."}
+        if action == "remove_component" and not (components_to_remove or component_name):
+            return {"success": False, "message": "'remove_component' requires 'components_to_remove' or 'component_name'."}

165-176: Critical: prefab_folder defaults to None, creating malformed paths like 'None/.prefab'.

The function parameter prefab_folder has a default value of None (line 43). When the code constructs the path at line 171 using f"{prefab_folder}/{params['name']}.prefab", a None value gets converted to the string "None", resulting in invalid Unity paths. Additionally, the params dictionary filters out None values, but the code uses the raw function parameter instead, bypassing any validation.

Add explicit validation before path construction:

-        # Check if 'saveAsPrefab' is explicitly True in params
-        if action == "create" and params.get("saveAsPrefab"):
+        # Check if 'saveAsPrefab' is explicitly True in params
+        if action == "create" and params.get("saveAsPrefab") is True:
             if "prefabPath" not in params:
-                if "name" not in params or not params["name"]:
-                    return {"success": False, "message": "Cannot create default prefab path: 'name' parameter is missing."}
-                # Use the provided prefab_folder (which has a default) and the name to construct the path
-                constructed_path = f"{prefab_folder}/{params['name']}.prefab"
+                if not params.get("name"):
+                    return {"success": False, "message": "Cannot create prefab: 'name' is required when 'save_as_prefab' is true and 'prefab_path' is not provided."}
+                if not prefab_folder:
+                    return {"success": False, "message": "Cannot create prefab: provide either 'prefab_path' or 'prefab_folder'."}
+                # Use the provided prefab_folder and the name to construct the path
+                constructed_path = f"{prefab_folder}/{params['name']}.prefab"
             # Ensure clean path separators (Unity prefers '/')
             params["prefabPath"] = constructed_path.replace("\\", "/")
🧹 Nitpick comments (15)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)

369-381: Consider consolidating duplicate _coerce_bool helpers.

The same _coerce_bool logic appears in multiple files (read_console.py, manage_editor.py, manage_gameobject.py per relevant snippets). Extracting this to a shared utility module would reduce duplication and ensure consistent coercion behavior across tools.

Apply this pattern:

Create a new shared utility module (e.g., src/utils/type_coercion.py):

def coerce_bool(value, default=None):
    """Coerce various inputs to bool, handling stringified booleans."""
    if value is None:
        return default
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        v = value.strip().lower()
        if v in ("true", "1", "yes", "on"):
            return True
        if v in ("false", "0", "no", "off"):
            return False
    return bool(value)

Then import and use across all tool modules.

MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (1)

41-57: Consider consolidating duplicate _coerce_int logic.

The _coerce_int helper is duplicated across files—there's a module-level version in resource_tools.py (lines 20-42) and this inline version. Extracting coercion utilities to a shared module (e.g., src/utils/type_coercion.py) would reduce duplication and centralize type-handling logic.

tests/test_manage_gameobject_param_coercion.py (2)

7-9: Avoid global sys.path mutation in tests.

Use monkeypatch.syspath_prepend within the test to keep isolation and avoid cross‑test interference.

Apply:

@@
-ROOT = pathlib.Path(__file__).resolve().parents[1]
-SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
-sys.path.insert(0, str(SRC))
+ROOT = pathlib.Path(__file__).resolve().parents[1]
+SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
@@
-def test_manage_gameobject_boolean_and_tag_mapping(monkeypatch):
+def test_manage_gameobject_boolean_and_tag_mapping(monkeypatch):
     # Import with SRC as CWD to satisfy telemetry import side effects
     _prev = os.getcwd()
     os.chdir(str(SRC))
     try:
+        # Prepend SRC to sys.path for this test only
+        monkeypatch.syspath_prepend(str(SRC))
         manage_go_mod = _load_module(SRC / "tools" / "manage_gameobject.py", "manage_go_mod")
     finally:
         os.chdir(_prev)

Also applies to: 35-41


46-49: Rename unused cmd parameter to silence Ruff ARG001.

The cmd parameter in fake_send() is defined but never used in the function body. Rename it to _cmd to follow Python conventions and indicate intentional non-use:

-    def fake_send(cmd, params):
+    def fake_send(_cmd, params):
         captured["params"] = params
         return {"success": True, "data": {}}
MCPForUnity/UnityMcpServer~/src/tools/run_tests.py (2)

51-66: DRY: reuse the shared int coercer.

Local _coerce_int duplicates logic used elsewhere. Import the central helper from resource_tools for consistency and maintenance.

-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field
@@
-from models import MCPResponse
+from models import MCPResponse
@@
-from registry import mcp_for_unity_tool
+from registry import mcp_for_unity_tool
 from unity_connection import async_send_command_with_retry
+from tools.resource_tools import _coerce_int
@@
-    # Coerce timeout defensively (string/float -> int)
-    def _coerce_int(value, default=None):
-        if value is None:
-            return default
-        try:
-            if isinstance(value, bool):
-                return default
-            if isinstance(value, int):
-                return int(value)
-            s = str(value).strip()
-            if s.lower() in ("", "none", "null"):
-                return default
-            return int(float(s))
-        except Exception:
-            return default
-
     params: dict[str, Any] = {"mode": mode}
     ts = _coerce_int(timeout_seconds)
     if ts is not None:
         params["timeoutSeconds"] = ts

Also applies to: 68-70


72-74: Normalize return type to RunTestsResponse.

Ensure the function always returns a RunTestsResponse instance.

-    return RunTestsResponse(**response) if isinstance(response, dict) else response
+    return RunTestsResponse(**response) if isinstance(response, dict) else RunTestsResponse(success=False, message=str(response))
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)

33-46: Deduplicate coercers: import shared helpers.

Use the centralized _coerce_bool and _coerce_int from resource_tools for consistency.

-from unity_connection import send_command_with_retry
+from unity_connection import send_command_with_retry
+from tools.resource_tools import _coerce_bool, _coerce_int
@@
-    # Coerce booleans defensively (strings like 'true'/'false')
-    def _coerce_bool(value, default=None):
-        if value is None:
-            return default
-        if isinstance(value, bool):
-            return value
-        if isinstance(value, str):
-            v = value.strip().lower()
-            if v in ("true", "1", "yes", "on"):
-                return True
-            if v in ("false", "0", "no", "off"):
-                return False
-        return bool(value)
-
-    include_stacktrace = _coerce_bool(include_stacktrace, True)
+    include_stacktrace = _coerce_bool(include_stacktrace, True)
@@
-    # Coerce count defensively (string/float -> int)
-    def _coerce_int(value, default=None):
-        if value is None:
-            return default
-        try:
-            if isinstance(value, bool):
-                return default
-            if isinstance(value, int):
-                return int(value)
-            s = str(value).strip()
-            if s.lower() in ("", "none", "null"):
-                return default
-            return int(float(s))
-        except Exception:
-            return default
-
     count = _coerce_int(count)

Also applies to: 54-67

MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1)

22-36: Reuse shared int coercer to avoid drift.

Align with other tools by importing _coerce_int from resource_tools.

 from registry import mcp_for_unity_tool
 from unity_connection import send_command_with_retry
+from tools.resource_tools import _coerce_int
@@
-        # Coerce numeric inputs defensively
-        def _coerce_int(value, default=None):
-            if value is None:
-                return default
-            try:
-                if isinstance(value, bool):
-                    return default
-                if isinstance(value, int):
-                    return int(value)
-                s = str(value).strip()
-                if s.lower() in ("", "none", "null"):
-                    return default
-                return int(float(s))
-            except Exception:
-                return default
+        # Coerce numeric inputs defensively
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)

27-41: Deduplicate boolean coercion.

Import and use the shared _coerce_bool from resource_tools for consistency.

 from telemetry import is_telemetry_enabled, record_tool_usage
 from unity_connection import send_command_with_retry
+from tools.resource_tools import _coerce_bool
@@
-    # Coerce boolean parameters defensively to tolerate 'true'/'false' strings
-    def _coerce_bool(value, default=None):
-        if value is None:
-            return default
-        if isinstance(value, bool):
-            return value
-        if isinstance(value, str):
-            v = value.strip().lower()
-            if v in ("true", "1", "yes", "on"):  # common truthy strings
-                return True
-            if v in ("false", "0", "no", "off"):
-                return False
-        return bool(value)
-
     wait_for_completion = _coerce_bool(wait_for_completion)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (6)

9-10: Tighten the user-facing description to match actual accepted formats.

Explicitly list accepted boolean synonyms and note that bare "x,y,z" vectors are accepted.

-    description="Manage GameObjects. For booleans, send true/false; if your client only sends strings, 'true'/'false' are accepted. Vectors may be [x,y,z] or a string like '[x,y,z]'. For 'get_components', the `data` field contains a dictionary of component names and their serialized properties. For 'get_component', specify 'component_name' to retrieve only that component's serialized data."
+    description="Manage GameObjects. For booleans, send true/false; if your client only sends strings, 'true'/'false' (and '1'/'0'/'yes'/'no'/'on'/'off') are accepted. Vectors may be [x,y,z], 'x,y,z', or a string like '[x,y,z]'. For 'get_components', the `data` field contains a dictionary of component names and their serialized properties. For 'get_component', specify 'component_name' to retrieve only that component's serialized data."

61-63: Clarify that component_name is also used by get_component.

Prevents misuse and reduces round-trips.

-    component_name: Annotated[str,
-                              "Component name for 'add_component' and 'remove_component' actions"] | None = None,
+    component_name: Annotated[str,
+                              "Component name for 'add_component', 'remove_component', and 'get_component' actions"] | None = None,

69-82: Avoid surprising truthiness on unknown strings; align bool coercion across tools.

return bool(value) turns any non-empty unrecognized string (e.g., "maybe") into True. Prefer strict parsing and fallback to default to avoid accidental True. Also, this helper duplicates similar ones in read_console/resource_tools/manage_editor — consolidate to a shared utility to keep behavior consistent.

-    def _coerce_bool(value, default=None):
+    def _coerce_bool(value, default=None):
         if value is None:
             return default
         if isinstance(value, bool):
             return value
+        if isinstance(value, (int, float)):
+            return bool(value)
         if isinstance(value, str):
             v = value.strip().lower()
             if v in ("true", "1", "yes", "on"):
                 return True
             if v in ("false", "0", "no", "off"):
                 return False
-        return bool(value)
+        # Unrecognized value; avoid truthy surprises
+        return default

Also consider moving this helper to a shared module and importing it here, read_console.py, resource_tools.py, and manage_editor.py for a single source of truth. Based on relevant code snippets.


104-106: Use None fallback for unparsable vectors instead of echoing invalid input.

Echoing the original string/list back to C# will likely fail harder. Omitting the param lets the server use defaults.

-    position = _coerce_vec(position, default=position)
-    rotation = _coerce_vec(rotation, default=rotation)
-    scale = _coerce_vec(scale, default=scale)
+    position = _coerce_vec(position)
+    rotation = _coerce_vec(rotation)
+    scale = _coerce_vec(scale)

190-191: Intentional broad catch: add a Ruff suppression or narrow exceptions.

If broad catching is desired to always return a structured MCP error, add a localized suppression.

-    except Exception as e:
-        return {"success": False, "message": f"Python error managing GameObject: {str(e)}"}
+    except Exception as e:  # noqa: BLE001 - return structured error to MCP clients
+        return {"success": False, "message": f"Python error managing GameObject: {str(e)}"}

69-103: Centralize coercers across tools modules to eliminate duplication.

Verification confirms _coerce_bool is duplicated identically across 4 files (read_console.py, resource_tools.py, manage_editor.py, manage_gameobject.py). The tools directory already has __init__.py, making a coercion.py module feasible and straightforward to import. Consider consolidating both _coerce_bool and _coerce_vec to a shared utility module to reduce maintenance burden and prevent future drift.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ccf70b and c0cd911.

📒 Files selected for processing (28)
  • MCPForUnity/UnityMcpServer~/src/pyproject.toml (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/resources/__init__.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/server.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/server_version.txt (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/__init__.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/execute_menu_item.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (2 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (2 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (3 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_prefabs.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_script.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_shader.py (2 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (4 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/run_tests.py (2 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/script_apply_edits.py (1 hunks)
  • MCPForUnity/package.json (1 hunks)
  • tests/__init__.py (1 hunks)
  • tests/test_edit_normalization_and_noop.py (0 hunks)
  • tests/test_get_sha.py (0 hunks)
  • tests/test_improved_anchor_matching.py (0 hunks)
  • tests/test_manage_asset_param_coercion.py (1 hunks)
  • tests/test_manage_gameobject_param_coercion.py (1 hunks)
  • tests/test_manage_script_uri.py (2 hunks)
  • tests/test_read_console_truncate.py (0 hunks)
  • tests/test_script_tools.py (0 hunks)
  • tests/test_validate_script_summary.py (0 hunks)
💤 Files with no reviewable changes (6)
  • tests/test_validate_script_summary.py
  • tests/test_edit_normalization_and_noop.py
  • tests/test_read_console_truncate.py
  • tests/test_improved_anchor_matching.py
  • tests/test_script_tools.py
  • tests/test_get_sha.py
🧰 Additional context used
🧬 Code graph analysis (7)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (3)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)
  • _coerce_bool (34-45)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)
  • _coerce_bool (28-39)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • _coerce_bool (70-81)
tests/test_manage_asset_param_coercion.py (2)
tests/test_helpers.py (1)
  • DummyContext (1-10)
MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (1)
  • manage_asset (15-83)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (3)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)
  • _coerce_bool (34-45)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_bool (369-380)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • _coerce_bool (70-81)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (3)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_bool (369-380)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)
  • _coerce_bool (28-39)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • _coerce_bool (70-81)
tests/test_manage_gameobject_param_coercion.py (2)
tests/test_helpers.py (1)
  • DummyContext (1-10)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • manage_gameobject (11-191)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (3)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)
  • _coerce_bool (34-45)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_bool (369-380)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)
  • _coerce_bool (28-39)
MCPForUnity/UnityMcpServer~/src/tools/run_tests.py (4)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)
  • _coerce_int (54-67)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_int (20-42)
MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1)
  • _coerce_int (22-35)
MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (1)
  • _coerce_int (41-54)
🪛 Ruff (0.14.1)
tests/test_manage_asset_param_coercion.py

47-47: Unused function argument: cmd

(ARG001)


47-47: Unused function argument: loop

(ARG001)

tests/test_manage_gameobject_param_coercion.py

46-46: Unused function argument: cmd

(ARG001)

MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py

89-89: Do not catch blind exception: Exception

(BLE001)


100-100: Do not catch blind exception: Exception

(BLE001)

MCPForUnity/UnityMcpServer~/src/tools/run_tests.py

64-64: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (23)
MCPForUnity/UnityMcpServer~/src/resources/__init__.py (1)

7-7: LGTM! Import path updated consistently.

The migration from mcp.server.fastmcp to fastmcp aligns with the dependency changes in pyproject.toml.

tests/__init__.py (1)

1-1: LGTM! Test package initialization.

This file properly designates the tests directory as a package, enabling cleaner test module imports and supporting the removal of complex in-test stubs.

MCPForUnity/package.json (1)

3-3: LGTM! Version bump to force client schema refresh.

The version update to 6.2.2 is consistent across the project and will trigger client-side schema updates for the parameter type widening changes.

MCPForUnity/UnityMcpServer~/src/server_version.txt (1)

1-1: LGTM! Version synchronized.

Server version correctly updated to 6.2.2, matching the package.json version.

MCPForUnity/UnityMcpServer~/src/pyproject.toml (2)

3-3: LGTM! Version bump coordinated.

Project version correctly updated to 6.2.2, consistent with other configuration files.


9-10: Dependency versions verified and compatible.

All original review concerns are validated:

  • fastmcp 2.12.5 is published and available on PyPI
  • mcp 1.16.0 is published and available on PyPI
  • No documented breaking changes exist between mcp 1.16.0 and 1.17.0; the 1.17.0 release contains only minor features and fixes
  • fastmcp 2.12.5 is fully MCP-compliant and interoperable with MCP clients and SDKs
  • No security vulnerabilities present for these versions

The constraint mcp>=1.16.0 is compatible with fastmcp>=2.12.5.

tests/test_manage_script_uri.py (1)

24-34: LGTM! Test infrastructure simplified.

The removal of nested module stubs (mcp.server.fastmcp) in favor of a single fastmcp stub is a clean improvement that aligns with the import path migration. This simplification makes the test setup more maintainable.

MCPForUnity/UnityMcpServer~/src/tools/script_apply_edits.py (1)

6-6: LGTM! Import path migrated.

The Context import correctly updated to use fastmcp, consistent with the dependency changes and import migration across the codebase.

MCPForUnity/UnityMcpServer~/src/tools/manage_script.py (1)

6-6: LGTM! Import path migrated successfully.

Both FastMCP and Context are now correctly imported from fastmcp, completing the import path migration for this module.

MCPForUnity/UnityMcpServer~/src/tools/execute_menu_item.py (1)

6-6: LGTM: Import path modernized.

The migration from mcp.server.fastmcp to fastmcp aligns with the broader dependency update across the codebase.

MCPForUnity/UnityMcpServer~/src/tools/manage_prefabs.py (1)

3-3: LGTM: Import path updated.

Consistent with the project-wide migration to the fastmcp package.

MCPForUnity/UnityMcpServer~/src/tools/__init__.py (1)

7-7: LGTM: Import aligned with new package structure.

The switch to fastmcp is consistent across all tool modules.

MCPForUnity/UnityMcpServer~/src/server.py (1)

2-2: LGTM: Core server updated to use fastmcp.

The import change is straightforward and matches the dependency migration.

tests/test_manage_asset_param_coercion.py (1)

36-66: LGTM: Test correctly validates string-to-int coercion.

The test successfully verifies that page_size="50" and page_number="2" are coerced to integers 50 and 2 before being passed to the Unity bridge.

Note: The static analysis warnings about unused parameters cmd and loop in the mock function are false positives—these parameters are required for signature compatibility with the real async_send_command_with_retry.

MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (3)

14-14: LGTM: Import updated.

Consistent with the project-wide migration to fastmcp.


193-200: LGTM: Type signatures broadened for client flexibility.

Accepting int | float | str for numeric parameters allows clients that stringify values to pass validation, with server-side coercion (lines 312-315) handling the conversion.


354-354: LGTM: Boolean parameter widened to accept strings.

This allows clients to pass "true"/"false" strings, which are coerced to booleans at line 381.

MCPForUnity/UnityMcpServer~/src/tools/manage_shader.py (1)

4-4: LGTM: Import path updated.

Consistent with the dependency migration to fastmcp.

MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (2)

7-7: LGTM: Import modernized.

Aligns with the project-wide switch to fastmcp.


32-33: LGTM: Pagination parameters widened for client compatibility.

The type signatures now accept int | float | str, enabling clients that stringify numeric values to pass schema validation. Server-side coercion (lines 56-57) ensures the values are normalized to integers before transmission to Unity.

MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)

16-17: LGTM on schema widening for booleans.

Accepting bool or string for wait_for_completion matches the PR goal and keeps native types working.

MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (2)

3-3: Import path migration looks correct.

Switch to fastmcp.Context matches the PR’s dependency shift.


116-134: Good guardrails for 'find' vs 'name' usage.

Clear, actionable messages prevent common misuse.

Comment on lines +46 to 48
timeout_seconds: Annotated[str, Field(
description="Optional timeout in seconds for the Unity test run (string, e.g. '30')")] | None = None,
) -> RunTestsResponse:
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Broaden timeout_seconds schema to accept both int and str.

Current schema (str only) can reject native ints at pre‑validation, contradicting “native types remain supported.”

-    timeout_seconds: Annotated[str, Field(
-        description="Optional timeout in seconds for the Unity test run (string, e.g. '30')")] | None = None,
+    timeout_seconds: Annotated[int | str, Field(
+        description="Optional timeout in seconds for the Unity test run (accepts int or string, e.g., 30 or '30')")] | None = None,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timeout_seconds: Annotated[str, Field(
description="Optional timeout in seconds for the Unity test run (string, e.g. '30')")] | None = None,
) -> RunTestsResponse:
timeout_seconds: Annotated[int | str, Field(
description="Optional timeout in seconds for the Unity test run (accepts int or string, e.g., 30 or '30')")] | None = None,
) -> RunTestsResponse:
🤖 Prompt for AI Agents
In MCPForUnity/UnityMcpServer~/src/tools/run_tests.py around lines 46 to 48, the
timeout_seconds parameter is annotated as Annotated[str, Field(...)] | None
which rejects native ints before validation; change the annotation to accept
both int and str (e.g., Annotated[int | str, Field(...)] | None or
Annotated[Union[int, str], Field(...)] | None), update the Field description to
reflect both types, and ensure necessary typing imports are present so native
integers are accepted and validated.

Parameter Type Improvements:
- Broaden count in read_console.py to accept int | str
- Broaden build_index in manage_scene.py to accept int | str
- Harden vector parsing in manage_gameobject.py with NaN/Inf checks
- Add whitespace-delimited vector support (e.g., "1 2 3")
- Narrow exception handling from Exception to (ValueError, TypeError)

Test Improvements:
- Harden _load_module in test files with spec/loader validation
- Fix test_manage_gameobject_boolean_and_tag_mapping by mapping tag→search_term

Bug Fixes:
- Fix syntax error in manage_shader.py (remove stray 'x')

Version: Bump to 6.2.3

All tests pass: 41 passed, 5 skipped, 7 xpassed
Copy link
Contributor

@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: 0

♻️ Duplicate comments (3)
MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1)

16-17: Past review concern addressed: build_index now accepts both int and str.

The parameter type has been widened to int | str as suggested in the previous review, eliminating "invalid param" errors for clients that pass native integers.

MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)

19-19: Past review concern addressed: count now accepts both int and str.

The parameter type has been widened to int | str as suggested in the previous review, allowing clients to pass either native integers or quoted strings.

MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)

83-104: Past review concern fully addressed: vector parsing is now hardened with finite checks and flexible delimiter support.

The _coerce_vec helper now:

  • Uses narrow exception handling (ValueError, TypeError instead of blind Exception)
  • Validates components with math.isfinite to reject NaN/Inf
  • Supports both comma-delimited ("x,y,z") and whitespace-delimited ("x y z") string formats

This implementation matches the recommendations from the previous review.

🧹 Nitpick comments (5)
tests/test_telemetry_queue_worker.py (1)

39-45: Consider extracting the _load_module helper to test_helpers to eliminate duplication.

This identical _load_module helper with the ImportError guard appears in at least five test files (test_telemetry_queue_worker.py, test_get_sha.py, test_validate_script_summary.py, test_manage_asset_param_coercion.py, test_read_console_truncate.py). Moving it to tests/test_helpers.py would reduce maintenance burden and ensure consistent error handling across the test suite.

MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1)

21-35: Consider extracting _coerce_int to a shared utility module to eliminate duplication.

This _coerce_int helper appears duplicated across multiple tool modules (at minimum: manage_scene.py, read_console.py, and likely manage_asset.py based on the relevant snippets). Extracting it to a shared utility module (e.g., src/utils/param_coercion.py) would reduce duplication and ensure consistent coercion behavior across all tools.

MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)

34-45: Consider extracting _coerce_bool to a shared utility module to eliminate duplication.

This _coerce_bool helper is duplicated across multiple tool modules (at minimum: read_console.py, manage_gameobject.py, manage_editor.py, and resource_tools.py based on relevant snippets). Extracting it to a shared utility module (e.g., src/utils/param_coercion.py) alongside _coerce_int would centralize coercion logic and ensure consistency.

tests/test_manage_gameobject_param_coercion.py (2)

48-50: Remove unused parameter for clarity.

The cmd parameter is captured but never used in the function body.

Apply this diff:

-    def fake_send(cmd, params):
+    def fake_send(_cmd, params):
         captured["params"] = params
         return {"success": True, "data": {}}

66-69: Tighten assertions to verify actual coercion.

The coercion functions in manage_gameobject.py convert find_all="true" to True and search_inactive="0" to False before calling send_command_with_retry. The current flexible assertions (or conditions) allow both stringified and native forms, but the captured params should contain only the coerced boolean values. Stricter assertions would better validate that coercion is working correctly and catch potential regressions.

Apply this diff:

-    # ensure tag mapped to searchTerm and booleans passed through; C# side coerces true/false already
+    # ensure tag mapped to searchTerm and booleans are coerced to native Python bool
     assert captured["params"]["searchTerm"] == "Player"
-    assert captured["params"]["findAll"] == "true" or captured["params"]["findAll"] is True
-    assert captured["params"]["searchInactive"] in ("0", False, 0)
+    assert captured["params"]["findAll"] is True
+    assert captured["params"]["searchInactive"] is False
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0cd911 and 3cd68d9.

📒 Files selected for processing (12)
  • MCPForUnity/UnityMcpServer~/src/server_version.txt (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (3 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/manage_shader.py (1 hunks)
  • MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1 hunks)
  • MCPForUnity/package.json (1 hunks)
  • tests/test_get_sha.py (1 hunks)
  • tests/test_manage_asset_param_coercion.py (1 hunks)
  • tests/test_manage_gameobject_param_coercion.py (1 hunks)
  • tests/test_read_console_truncate.py (1 hunks)
  • tests/test_telemetry_queue_worker.py (1 hunks)
  • tests/test_validate_script_summary.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • MCPForUnity/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • MCPForUnity/UnityMcpServer~/src/server_version.txt
  • MCPForUnity/UnityMcpServer~/src/tools/manage_shader.py
🧰 Additional context used
🧬 Code graph analysis (4)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (3)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • _coerce_bool (70-81)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)
  • _coerce_bool (28-39)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_bool (369-380)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (4)
tests/test_helpers.py (1)
  • info (3-4)
MCPForUnity/UnityMcpServer~/src/tools/read_console.py (1)
  • _coerce_bool (34-45)
MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py (1)
  • _coerce_bool (28-39)
MCPForUnity/UnityMcpServer~/src/tools/resource_tools.py (1)
  • _coerce_bool (369-380)
tests/test_manage_asset_param_coercion.py (2)
tests/test_helpers.py (1)
  • DummyContext (1-10)
MCPForUnity/UnityMcpServer~/src/tools/manage_asset.py (1)
  • manage_asset (15-83)
tests/test_manage_gameobject_param_coercion.py (2)
tests/test_helpers.py (1)
  • DummyContext (1-10)
MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)
  • manage_gameobject (11-197)
🪛 Ruff (0.14.1)
tests/test_validate_script_summary.py

13-13: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_telemetry_queue_worker.py

42-42: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_get_sha.py

14-14: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_manage_asset_param_coercion.py

16-16: Avoid specifying long messages outside the exception class

(TRY003)


49-49: Unused function argument: cmd

(ARG001)


49-49: Unused function argument: loop

(ARG001)

tests/test_manage_gameobject_param_coercion.py

15-15: Avoid specifying long messages outside the exception class

(TRY003)


48-48: Unused function argument: cmd

(ARG001)

tests/test_read_console_truncate.py

13-13: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (7)
tests/test_manage_asset_param_coercion.py (1)

38-68: LGTM! Test correctly validates string-to-int coercion for pagination parameters.

The test properly exercises the coercion logic by passing string values for page_size and page_number, then verifying they're converted to integers in the captured parameters.

tests/test_read_console_truncate.py (1)

52-100: LGTM! Tests correctly validate the include_stacktrace parameter and stacktrace field filtering.

Both tests properly exercise the include_stacktrace parameter: the first verifies default behavior (stacktraces included), and the second confirms stacktraces are stripped when include_stacktrace=False.

MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.py (1)

117-119: LGTM! Backward compatibility mapping ensures tag parameter works with by_tag search method.

The logic correctly maps tag to search_term when search_method is "by_tag" and search_term is not explicitly provided, maintaining backward compatibility without requiring breaking changes to existing client code.

tests/test_manage_gameobject_param_coercion.py (4)

1-10: LGTM: Path setup is correct.

The imports and path configuration properly prepare the test environment to load the source modules.


12-18: LGTM: ImportError guard in place.

The function now safely handles missing spec or loader scenarios. Previous concern resolved.


21-31: LGTM: FastMCP stubbing is appropriate.

The stub setup correctly isolates the test from real MCP dependencies.


54-62: Tag→searchTerm mapping now works correctly.

The test correctly validates that tag="Player" maps to searchTerm when search_method="by_tag". The mapping logic implemented in manage_gameobject.py (lines 121-122 of the relevant snippets) resolves the previous concern.

@dsarno dsarno merged commit c866e06 into CoplayDev:main Oct 23, 2025
1 check passed
@dsarno dsarno deleted the enhancement/fix-invalid-params-fastmcp-2.0 branch October 24, 2025 17:40
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.

Parameter type mismatches still resulting in failed calls as of 4.1.1

1 participant