Skip to content

Standardize how we define MCP tools#292

Merged
msanatan merged 33 commits intoCoplayDev:mainfrom
msanatan:standardize-mcp-tools
Sep 27, 2025
Merged

Standardize how we define MCP tools#292
msanatan merged 33 commits intoCoplayDev:mainfrom
msanatan:standardize-mcp-tools

Conversation

@msanatan
Copy link
Copy Markdown
Member

@msanatan msanatan commented Sep 27, 2025

We're just following FastMCP's docs, minus using Pydantic to validate data entry. Closes #289

Summary by CodeRabbit

  • New Features

    • Prefab management tool, explicit reload-sentinel callable, and automatic Unity port probing.
  • Improvements

    • Tools now expose clearer, named endpoints with richer, self‑documented parameters and runtime logging (assets, editor, gameobjects, scenes, scripts, shaders, console, resources).
    • Centralized command routing, stronger script-edit validation, improved diagnostics and no‑op detection.
    • Expanded telemetry record schema and milestone categories.
  • Bug Fixes

    • Better anchor/edit error messages, duplicate‑insert checks, and more robust failure reporting.
  • Chores

    • Dependency bumped (mcp[cli] >= 1.15.0) and minor formatting/doc tweaks.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 27, 2025

Walkthrough

Replaces the editor's manual ExecuteCommand switch with registry-based dispatch; adds manage_prefabs handler; large typing/metadata refactors across many MCP tools (Annotated/Literal, explicit @mcp.tool, ctx logging); major ManageScript/script-edit rewrite; port probing/auto-discovery, telemetry schema changes, dependency bump, and a reload-sentinel helper.

Changes

Cohort / File(s) Summary
Editor dispatch refactor
UnityMcpBridge/Editor/MCPForUnityBridge.cs
Replaced manual switch-based ExecuteCommand routing with centralized CommandRegistry.GetHandler(command.type) lookup and invocation; preserved JSON response/error handling.
Command registry update
UnityMcpBridge/Editor/Tools/CommandRegistry.cs
Added manage_prefabs mapping and import for MCPForUnity.Editor.Tools.Prefabs; extended registry initialization.
In-editor script tooling
UnityMcpBridge/Editor/Tools/ManageScript.cs
Large refactor: consolidated params, richer validation levels, many edit modes (replace/delete/insert/anchor), balance/no-op detection, enhanced diagnostics and structured error responses.
Server tools — bulk metadata & typing
UnityMcpBridge/UnityMcpServer~/src/tools/*
Many tools updated to use Annotated/Literal, explicit @mcp.tool(name=..., description=...), add ctx logging, normalize/filter params, centralize send/retry, and standardize dict[str, Any] returns.
Script edits & APIs
UnityMcpBridge/UnityMcpServer~/src/tools/{manage_script.py,manage_script_edits.py}
Introduced named script_apply_edits, PEP 585 typing, richer normalization/alias helpers, and updated public signatures for apply/create/delete/validate/get_sha flows.
Resource, console & read tools
UnityMcpBridge/UnityMcpServer~/src/tools/{resource_tools.py,read_console.py}
Added ctx, Annotated typing, stricter validation, windowing/read helpers, standardized responses, and central retry usage.
GameObject / Prefabs / Scene / Shader / Menu / Asset / Editor tools
UnityMcpBridge/UnityMcpServer~/src/tools/{manage_gameobject,manage_prefabs,manage_scene,manage_shader,manage_menu_item,manage_asset,manage_editor}.py
Expanded signatures with Annotated/Literal, added ctx logging, refined param normalization/payload construction, prefab/path validation, and explicit tool registration metadata.
Tools initializer
UnityMcpBridge/UnityMcpServer~/src/tools/__init__.py
register_all_tools now typed to accept FastMCP, explicitly registers modules and logs completion.
Unity connection & port discovery
UnityMcpBridge/UnityMcpServer~/src/{unity_connection.py,port_discovery.py}
UnityConnection auto-discovers port via PortDiscovery; added _try_probe_unity_mcp probe helper, improved candidate-file ordering, and strengthened connect/receive error paths.
Server core & deps
UnityMcpBridge/UnityMcpServer~/src/{server.py,pyproject.toml}
Adjusted FastMCP construction to name=..., pruned imports, and bumped mcp[cli] dependency to >=1.15.0.
Telemetry
UnityMcpBridge/UnityMcpServer~/src/{telemetry.py,telemetry_decorator.py,test_telemetry.py}
Expanded TelemetryRecord fields and MilestoneType, added optional sub_action to record_tool_usage, plus related typing/formatting edits.
Reload sentinel helper
UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py
Added flip_reload_sentinel(*args, **kwargs) -> str returning a deprecation message directing users to the MCP menu item.
Minor formatting / EOF fixes
UnityMcpBridge/UnityMcpServer~/src/{Dockerfile,__init__.py,config.py}
Trailing-newline/docstring and small formatting adjustments; no behavior changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Bridge as Unity Editor Bridge
  participant Registry as CommandRegistry
  participant Handler as Command Handler

  Client->>Bridge: ExecuteCommand(command)
  Bridge->>Registry: GetHandler(command.type)
  alt handler found
    Registry-->>Bridge: handlerFunc
    Bridge->>Handler: Invoke(paramsObject)
    Handler-->>Bridge: JSON response
    Bridge-->>Client: Response JSON
  else missing / error
    Registry-->>Bridge: None / throws
    Bridge-->>Client: Error JSON
  end
Loading
sequenceDiagram
  autonumber
  actor MCPClient
  participant Tool as MCP Tool
  participant Conn as UnityConnection
  participant Unity as Unity Editor

  MCPClient->>Tool: call(tool, annotated params)
  Tool->>Tool: normalize & filter params
  Tool->>Conn: send_command_with_retry(type, params)
  Conn->>Unity: framed request
  Unity-->>Conn: framed response
  Conn-->>Tool: parsed result
  Tool-->>MCPClient: dict result (success/error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • dsarno
  • Scriptwonder

Poem

I hop through handlers, quick and neat,
Registry routes each tiny beat.
Tools wear new names and tidy claims,
Ports I probe while nibbling frames.
A rabbit cheers — dispatch complete. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request includes a broad set of unrelated modifications—such as editor bridge dispatch refactors, port discovery enhancements, telemetry schema changes, server configuration tweaks, and script management overhauls—that fall outside the stated scope of standardizing MCP tool definitions. Please restrict this PR to the tool definition standardization changes and relocate unrelated refactorings and feature additions into separate, focused pull requests.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary change of the pull request—standardizing how MCP tools are defined—without listing individual files or using vague terms, and it aligns directly with the PR’s focus on updating tool metadata.
Linked Issues Check ✅ Passed The pull request uniformly applies FastMCP’s parameter metadata guide by adding descriptive names and descriptions to every @mcp.tool decorator and using Annotated parameter types for each tool function, explicitly omitting Pydantic while improving client compatibility as outlined in issue #289.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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.

$"Unknown or unsupported command type: {command.type}"
),
};
object result = CommandRegistry.GetHandler(command.type)(paramsObject);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was removed in an earlier PR, it probably re-appeared by a bad merge conflict resolution

Copy link
Copy Markdown
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: 6

Caution

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

⚠️ Outside diff range comments (2)
UnityMcpBridge/UnityMcpServer~/src/port_discovery.py (1)

65-69: Fix probe payload to match Unity bridge protocol

The Unity bridge speaks newline-delimited JSON (UnityConnection always writes json.dumps(...)+b"\n"). Sending the bare bytes b"ping" never produces a response—the C# side keeps waiting for a newline—so _try_probe_unity_mcp always returns False. That forces discovery to fall back to the “first seen” port, which can be stale or belong to another project, defeating the resilience we’re trying to add. Please emit the same framed JSON ping we use elsewhere.

-                    s.sendall(b"ping")
+                    s.sendall(b'{"command":"ping"}\n')
UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py (1)

47-55: Restore Unity connection handling or drop it

get_unity_connection() is still invoked here, but the import was removed. As soon as the tool runs, this will raise a NameError and the call never reaches send_command_with_retry. Either re-import the symbol or, since the retry helper already manages the connection, just delete this block. The latter keeps the code lean and avoids the unused variable too.

-        # Get the connection instance
-        bridge = get_unity_connection()
🧹 Nitpick comments (11)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (2)

17-39: Avoid per-call inspect.signature; precompute action param once.

This decorates hot paths; binding the signature on every call is unnecessary. Cache the action parameter position at decoration time and read from args/kwargs.

Apply:

 def telemetry_tool(tool_name: str):
     """Decorator to add telemetry tracking to MCP tools"""
     def decorator(func: Callable) -> Callable:
+        # Precompute 'action' param position to avoid per-call introspection
+        try:
+            _sig = inspect.signature(func)
+            _param_names = tuple(_sig.parameters.keys())
+            _action_idx = _param_names.index("action") if "action" in _param_names else None
+        except Exception:
+            _action_idx = None
+
+        def _extract_sub_action(args, kwargs):
+            if "action" in kwargs:
+                return kwargs.get("action")
+            if _action_idx is not None and len(args) > _action_idx:
+                return args[_action_idx]
+            return None
@@
-        def _sync_wrapper(*args, **kwargs) -> Any:
+        def _sync_wrapper(*args, **kwargs) -> Any:
             start_time = time.time()
             success = False
             error = None
             # Extract sub-action (e.g., 'get_hierarchy') from bound args when available
-            sub_action = None
-            try:
-                sig = inspect.signature(func)
-                bound = sig.bind_partial(*args, **kwargs)
-                bound.apply_defaults()
-                sub_action = bound.arguments.get("action")
-            except Exception:
-                sub_action = None
+            sub_action = _extract_sub_action(args, kwargs)
@@
-        async def _async_wrapper(*args, **kwargs) -> Any:
+        async def _async_wrapper(*args, **kwargs) -> Any:
             start_time = time.time()
             success = False
             error = None
             # Extract sub-action (e.g., 'get_hierarchy') from bound args when available
-            sub_action = None
-            try:
-                sig = inspect.signature(func)
-                bound = sig.bind_partial(*args, **kwargs)
-                bound.apply_defaults()
-                sub_action = bound.arguments.get("action")
-            except Exception:
-                sub_action = None
+            sub_action = _extract_sub_action(args, kwargs)

Also applies to: 63-83


46-51: Minor: tighten milestone scene check or document prefix contract.

Using startswith("manage_scene") is fine; if tool names change (e.g., "scene"), milestones could drift. Optionally constrain to exact name or a documented prefix convention.

Also applies to: 89-94

UnityMcpBridge/Editor/MCPForUnityBridge.cs (1)

1071-1086: Remove now-unused special-case manage_scene handler.

HandleManageScene appears unused after registry dispatch. Consider removing to reduce dead code and stale behavior drift.

UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py (3)

16-22: Restore explicit enum description for action.

“Operation” is less helpful to clients than listing allowed values. Include them in the annotation text to align with FastMCP’s parameter metadata guidance.

Apply:

-        ], "Operation"],
+        ], "Operation: one of open_stage | close_stage | save_open_stage | create_from_gameobject"],

24-34: Optional: restrict mode via Literal for clearer contracts.

If only InIsolation is supported, encode it in the type to help LLMs and clients.

Example:

-        mode: Annotated[str | None,
-                        "Optional prefab stage mode (only 'InIsolation' is currently supported)"] = None,
+        mode: Annotated[Literal["InIsolation"] | None,
+                        "Prefab stage mode (currently only 'InIsolation')"] = None,

35-61: Consider lightweight precondition checks for action-specific args.

E.g., require target and prefab_path for create_from_gameobject; return a friendly error before bridging. Keeps Unity logs cleaner without adopting full Pydantic.

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (4)

359-369: Add Annotated parameter descriptions to align with FastMCP metadata.

This tool lacks Annotated[...] metadata for params; adding them improves discoverability and standardization.

Apply minimal updates:

-from typing import Dict, Any, List, Tuple, Optional
+from typing import Dict, Any, List, Tuple, Optional, Annotated
@@
-    def script_apply_edits(
-        ctx: Context,
-        name: str,
-        path: str,
-        edits: List[Dict[str, Any]],
-        options: Optional[Dict[str, Any]] = None,
-        script_type: str = "MonoBehaviour",
-        namespace: str = "",
-    ) -> Dict[str, Any]:
+    def script_apply_edits(
+        ctx: Context,
+        name: Annotated[str, "Script name (with or without .cs)"],
+        path: Annotated[str, "Directory under Assets or full Assets/.../File.cs"],
+        edits: Annotated[List[Dict[str, Any]], "Structured/text edits to apply"],
+        options: Annotated[Optional[Dict[str, Any]], "Options: validate, refresh, preview, confirm"] = None,
+        script_type: Annotated[str, "Unity script type (e.g., MonoBehaviour)"] = "MonoBehaviour",
+        namespace: Annotated[str, "Namespace (optional)"] = "",
+    ) -> Dict[str, Any]:

669-677: Broad excepts are acceptable here; consider narrowing where feasible.

Error mapping is user-facing. If practical, catch regex compilation errors explicitly (ValueError/re.error) to improve diagnostics and keep BLE001 quiet.

Also applies to: 822-835


871-888: Guard oversized diffs for preview to keep responses small.

You already truncate; consider emitting total-changes metadata to help clients decide to fetch more.


883-890: Consistency nit: return shapes.

Some error returns use {"success": False, "message": ...}, others include "code". Consider adding code consistently for easier client handling.

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

101-103: Remove the unused connection assignment

Calling get_unity_connection() purely for its side effect is fine, but assigning it to connection and never using it trips lint and adds noise. Dropping the assignment (or reusing the object) keeps the intent clear.

-        connection = get_unity_connection()
+        get_unity_connection()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f50acf4 and 1dbb527.

📒 Files selected for processing (26)
  • UnityMcpBridge/Editor/MCPForUnityBridge.cs (1 hunks)
  • UnityMcpBridge/Editor/Tools/CommandRegistry.cs (2 hunks)
  • UnityMcpBridge/Editor/Tools/ManageScript.cs (9 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/Dockerfile (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/__init__.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/config.py (2 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/port_discovery.py (6 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/pyproject.toml (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/server.py (2 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/telemetry.py (17 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (5 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/test_telemetry.py (4 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/__init__.py (2 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.py (4 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py (2 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (21 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (26 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py (5 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py (9 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (18 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-04T01:01:11.927Z
Learnt from: dsarno
PR: CoplayDev/unity-mcp#260
File: UnityMcpBridge/UnityMcpServer~/src/server_version.txt:1-1
Timestamp: 2025-09-04T01:01:11.927Z
Learning: The UnityMcpBridge project is not maintaining changelogs yet, so don't suggest adding changelog entries for version bumps.

Applied to files:

  • UnityMcpBridge/UnityMcpServer~/src/Dockerfile
  • UnityMcpBridge/UnityMcpServer~/src/pyproject.toml
  • UnityMcpBridge/UnityMcpServer~/src/__init__.py
🧬 Code graph analysis (15)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
UnityMcpBridge/UnityMcpServer~/src/telemetry.py (4)
  • record_tool_usage (403-429)
  • record_milestone (249-271)
  • record_milestone (398-400)
  • MilestoneType (49-57)
UnityMcpBridge/Editor/MCPForUnityBridge.cs (1)
UnityMcpBridge/Editor/Tools/CommandRegistry.cs (1)
  • CommandRegistry (12-49)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/Editor/Tools/CommandRegistry.cs (1)
UnityMcpBridge/Editor/Tools/Prefabs/ManagePrefabs.cs (1)
  • ManagePrefabs (12-273)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py (1)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (3)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/telemetry.py (2)
  • is_telemetry_enabled (458-460)
  • record_tool_usage (403-429)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (3)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (2)
  • get_unity_connection (378-399)
  • async_send_command_with_retry (439-452)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
tests/test_manage_script_uri.py (1)
  • tool (46-50)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (2)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (2)
  • telemetry_tool (17-107)
  • decorator (19-106)
UnityMcpBridge/UnityMcpServer~/src/test_telemetry.py (1)
UnityMcpBridge/UnityMcpServer~/src/telemetry.py (7)
  • get_telemetry (383-388)
  • record_telemetry (391-395)
  • record_milestone (249-271)
  • record_milestone (398-400)
  • RecordType (37-46)
  • MilestoneType (49-57)
  • is_telemetry_enabled (458-460)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/Editor/Tools/ManageScript.cs (1)
UnityMcpBridge/Editor/Helpers/Response.cs (3)
  • Response (10-61)
  • Error (41-60)
  • Success (18-33)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
UnityMcpBridge/UnityMcpServer~/src/port_discovery.py (1)
  • PortDiscovery (24-160)
UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py (3)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (1)
  • _coerce_int (62-75)
🪛 Ruff (0.13.1)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py

103-103: Do not catch blind exception: Exception

(BLE001)

UnityMcpBridge/UnityMcpServer~/src/port_discovery.py

127-127: Do not catch blind exception: Exception

(BLE001)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py

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

(TRY003)


88-89: Avoid specifying long messages outside the exception class

(TRY003)


681-681: Do not catch blind exception: Exception

(BLE001)


833-833: Do not catch blind exception: Exception

(BLE001)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py

15-15: Unused function argument: ctx

(ARG001)


58-58: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py

16-16: Unused function argument: ctx

(ARG001)


17-18: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


19-19: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


22-22: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


23-23: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


24-24: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


57-57: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py

19-19: Unused function argument: ctx

(ARG001)


101-101: Local variable connection is assigned to but never used

Remove assignment to unused variable connection

(F841)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py

16-16: Unused function argument: ctx

(ARG001)


60-60: Do not catch blind exception: Exception

(BLE001)


62-62: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py

16-16: Unused function argument: tool_name

(ARG001)


92-92: Unused function argument: ctx

(ARG001)


169-170: try-except-pass detected, consider logging the exception

(S110)


169-169: Do not catch blind exception: Exception

(BLE001)


234-235: try-except-pass detected, consider logging the exception

(S110)


234-234: Do not catch blind exception: Exception

(BLE001)


370-370: Unused function argument: ctx

(ARG001)


407-407: Unused function argument: ctx

(ARG001)


421-421: Unused function argument: ctx

(ARG001)


454-454: Unused function argument: ctx

(ARG001)


514-515: try-except-pass detected, consider logging the exception

(S110)


514-514: Do not catch blind exception: Exception

(BLE001)


577-577: Unused function argument: ctx

(ARG001)


602-602: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/test_telemetry.py

25-25: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


57-57: Do not catch blind exception: Exception

(BLE001)


57-57: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


117-117: Consider moving this statement to an else block

(TRY300)


119-119: Do not catch blind exception: Exception

(BLE001)


119-119: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

UnityMcpBridge/UnityMcpServer~/src/telemetry.py

124-124: Do not catch blind exception: Exception

(BLE001)


128-129: try-except-pass detected, consider logging the exception

(S110)


128-128: Do not catch blind exception: Exception

(BLE001)


179-180: Abstract raise to an inner function

(TRY301)


179-180: Avoid specifying long messages outside the exception class

(TRY003)

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

15-15: Unused function argument: ctx

(ARG001)


17-17: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


19-19: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


21-21: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


23-23: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


25-25: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


27-27: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


29-29: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


31-31: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


33-33: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


35-35: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


39-39: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


43-43: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


45-45: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


46-46: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


48-48: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


55-55: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


63-63: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


66-66: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


126-126: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py

18-18: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/unity_connection.py

94-95: Avoid specifying long messages outside the exception class

(TRY003)


128-129: Avoid specifying long messages outside the exception class

(TRY003)


154-155: Abstract raise to an inner function

(TRY301)


154-155: Avoid specifying long messages outside the exception class

(TRY003)


174-175: Abstract raise to an inner function

(TRY301)


174-175: Create your own exception

(TRY002)


174-175: Avoid specifying long messages outside the exception class

(TRY003)


208-208: Consider moving this statement to an else block

(TRY300)


212-212: Do not catch blind exception: Exception

(BLE001)


214-214: Use explicit conversion flag

Replace with conversion flag

(RUF010)


320-320: Abstract raise to an inner function

(TRY301)


320-320: Create your own exception

(TRY002)


396-397: Avoid specifying long messages outside the exception class

(TRY003)

UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py

142-142: Unused function argument: ctx

(ARG001)


194-194: Unused function argument: ctx

(ARG001)


353-353: Unused function argument: ctx

(ARG001)

🔇 Additional comments (2)
UnityMcpBridge/Editor/MCPForUnityBridge.cs (1)

1042-1046: Registry dispatch verified
All expected handlers, including manage_prefabs, are registered in CommandRegistry.

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

18-33: register_all_tools invocation and metadata usage verified: function is only called once and all @mcp.tool definitions include descriptions and Annotated parameters.

Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py Outdated
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py Outdated
Comment on lines 48 to 57
if isinstance(response, dict) and response.get("success"):
# If the response contains base64 encoded content, decode it
if response.get("data", {}).get("contentsEncoded"):
decoded_contents = base64.b64decode(response["data"]["encodedContents"]).decode('utf-8')
decoded_contents = base64.b64decode(
response["data"]["encodedContents"]).decode('utf-8')
response["data"]["contents"] = decoded_contents
del response["data"]["encodedContents"]
del response["data"]["contentsEncoded"]

return {"success": True, "message": response.get("message", "Operation successful."), "data": response.get("data")}
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.

⚠️ Potential issue | 🟠 Major

Normalize data before probing for encoded shader contents

When Unity returns a success payload whose data field is either null or a non-dict (several tools already send strings here), calling .get(...) on it raises an AttributeError. That propagates into the catch block, so the caller only sees "Python error managing shader: '...' " instead of the original Unity response. Guard the branch to run only when data is actually a mapping, and fall back gracefully otherwise.

-            if response.get("data", {}).get("contentsEncoded"):
-                decoded_contents = base64.b64decode(
-                    response["data"]["encodedContents"]).decode('utf-8')
-                response["data"]["contents"] = decoded_contents
-                del response["data"]["encodedContents"]
-                del response["data"]["contentsEncoded"]
+            data = response.get("data")
+            if isinstance(data, dict) and data.get("contentsEncoded"):
+                encoded = data.get("encodedContents")
+                if encoded:
+                    decoded_contents = base64.b64decode(encoded).decode('utf-8')
+                    data = {**data, "contents": decoded_contents}
+                    data.pop("encodedContents", None)
+                    data.pop("contentsEncoded", None)
+                    response["data"] = data
📝 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
if isinstance(response, dict) and response.get("success"):
# If the response contains base64 encoded content, decode it
if response.get("data", {}).get("contentsEncoded"):
decoded_contents = base64.b64decode(response["data"]["encodedContents"]).decode('utf-8')
decoded_contents = base64.b64decode(
response["data"]["encodedContents"]).decode('utf-8')
response["data"]["contents"] = decoded_contents
del response["data"]["encodedContents"]
del response["data"]["contentsEncoded"]
return {"success": True, "message": response.get("message", "Operation successful."), "data": response.get("data")}
if isinstance(response, dict) and response.get("success"):
# If the response contains base64 encoded content, decode it
data = response.get("data")
if isinstance(data, dict) and data.get("contentsEncoded"):
encoded = data.get("encodedContents")
if encoded:
decoded_contents = base64.b64decode(encoded).decode('utf-8')
data = {**data, "contents": decoded_contents}
data.pop("encodedContents", None)
data.pop("contentsEncoded", None)
response["data"] = data
return {"success": True, "message": response.get("message", "Operation successful."), "data": response.get("data")}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ignoring, I only made a formatting change, the logic can be revisited later

@msanatan
Copy link
Copy Markdown
Member Author

Will test, respond to feedback and release tomorrow

Copy link
Copy Markdown
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: 3

Caution

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

⚠️ Outside diff range comments (2)
UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py (1)

174-181: Let callers list non-script assets.

Hard-coding the .cs suffix here defeats the pattern parameter—pattern="*.shader" (or any non-.cs glob) will always return an empty list even though the annotation and description promise configurability. That regresses existing capability to browse other asset types. Please drop the forced suffix check and let the glob drive the filter, defaulting to *.cs only when the caller doesn’t supply one.

-                # Enforce .cs extension regardless of provided pattern
-                if p.suffix.lower() != ".cs":
-                    continue
-                if pattern and not fnmatch.fnmatch(p.name, pattern):
+                glob = pattern or "*.cs"
+                if not fnmatch.fnmatch(p.name, glob):
                     continue
UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py (1)

47-55: Restore the get_unity_connection import or drop the unused call.

get_unity_connection is no longer imported, yet Line 48 still calls it. This will raise NameError at runtime before we ever build params. Either re-import get_unity_connection or, since the result is unused and send_command_with_retry already acquires the connection internally, just remove the call entirely. I recommend the latter to avoid redundant work.

-        # Get the connection instance
-        bridge = get_unity_connection()
-
         # Set defaults if values are None
         action = action if action is not None else 'get'
         types = types if types is not None else ['error', 'warning', 'log']
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dbb527 and 4ca2529.

📒 Files selected for processing (11)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.py (4 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py (2 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (21 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (27 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py (1 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py (5 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (2)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (2)
  • get_unity_connection (378-399)
  • async_send_command_with_retry (439-452)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (3)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/telemetry.py (2)
  • is_telemetry_enabled (458-460)
  • record_tool_usage (403-429)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (1)
  • telemetry_tool (16-19)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (3)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (2)
  • telemetry_tool (17-107)
  • decorator (19-106)
tests/test_script_tools.py (2)
  • decorator (44-46)
  • tool (43-47)
UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py (3)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (1)
  • _coerce_int (62-75)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
🪛 Ruff (0.13.1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py

19-19: Unused function argument: ctx

(ARG001)


101-101: Local variable connection is assigned to but never used

Remove assignment to unused variable connection

(F841)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py

16-16: Unused function argument: ctx

(ARG001)


17-18: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


19-19: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


22-22: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


23-23: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


24-24: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


57-57: Use explicit conversion flag

Replace with conversion flag

(RUF010)

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

15-15: Unused function argument: ctx

(ARG001)


17-17: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


19-19: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


21-21: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


23-23: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


25-25: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


27-27: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


29-29: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


31-31: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


33-33: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


35-35: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


39-39: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


43-43: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


45-45: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


46-46: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


48-48: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


55-55: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


63-63: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


66-66: PEP 484 prohibits implicit Optional

Convert to Optional[T]

(RUF013)


126-126: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py

15-15: Unused function argument: ctx

(ARG001)


58-58: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py

19-19: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py

16-16: Unused function argument: ctx

(ARG001)


60-60: Do not catch blind exception: Exception

(BLE001)


62-62: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py

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

(TRY003)


88-89: Avoid specifying long messages outside the exception class

(TRY003)


681-681: Do not catch blind exception: Exception

(BLE001)


833-833: Do not catch blind exception: Exception

(BLE001)

UnityMcpBridge/UnityMcpServer~/src/tools/read_console.py

18-18: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py

16-16: Unused function argument: tool_name

(ARG001)


92-92: Unused function argument: ctx

(ARG001)


169-170: try-except-pass detected, consider logging the exception

(S110)


169-169: Do not catch blind exception: Exception

(BLE001)


234-235: try-except-pass detected, consider logging the exception

(S110)


234-234: Do not catch blind exception: Exception

(BLE001)


370-370: Unused function argument: ctx

(ARG001)


407-407: Unused function argument: ctx

(ARG001)


421-421: Unused function argument: ctx

(ARG001)


454-454: Unused function argument: ctx

(ARG001)


514-515: try-except-pass detected, consider logging the exception

(S110)


514-514: Do not catch blind exception: Exception

(BLE001)


577-577: Unused function argument: ctx

(ARG001)


602-602: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py

142-142: Unused function argument: ctx

(ARG001)


194-194: Unused function argument: ctx

(ARG001)


353-353: Unused function argument: ctx

(ARG001)

🔇 Additional comments (5)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (1)

455-463: Re-include 'update' in the advertised action Literal.

With 'update' removed from the Literal, FastMCP will now reject legacy manage_script update calls before they can reach the in-function migration path, breaking the compatibility you’re preserving. Please add 'update' back (mark it deprecated in the description if desired) so existing clients keep working.

-        action: Annotated[Literal['create', 'read', 'delete'], "Operations"],
+        action: Annotated[
+            Literal['create', 'read', 'update', 'delete'],
+            "Operations (legacy 'update' is deprecated—prefer apply_text_edits)"
+        ],
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script_edits.py (2)

606-611: Fix encoded payload fallback.

Line 610 still requires both contentsEncoded and encodedContents, so payloads that only send the encoded string keep returning “No contents returned from Unity read.” Please decode whenever either encoded field is present (prefer encodedContents), guarding for non-string values.

-        contents = data.get("contents")
-        if contents is None and data.get("contentsEncoded") and data.get("encodedContents"):
-            contents = base64.b64decode(
-                data["encodedContents"]).decode("utf-8")
+        contents = data.get("contents")
+        if contents is None:
+            encoded_val = data.get("encodedContents")
+            if isinstance(encoded_val, str):
+                contents = base64.b64decode(encoded_val).decode("utf-8")
+            elif isinstance(data.get("contentsEncoded"), str):
+                contents = base64.b64decode(
+                    data["contentsEncoded"]).decode("utf-8")

761-763: Remove anchor_insert from structured_kinds.

Line 762 keeps anchor_insert in the structured set, so the text-conversion branch that advertises anchor support never runs. Dropping it here lets pure anchor inserts reach the text-edits path as intended.

-        structured_kinds = {"replace_class", "delete_class",
-                            "replace_method", "delete_method", "insert_method", "anchor_insert"}
+        structured_kinds = {"replace_class", "delete_class",
+                            "replace_method", "delete_method", "insert_method"}
UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (1)

17-20: Revert name/path to optional parameters.

Requiring name and path makes previously valid calls like manage_scene(action="get_hierarchy") raise TypeError before our logic runs, breaking existing clients and telemetry (see earlier review). These values still behave as optional inside the function, so restore their defaults.

-        name: Annotated[str, "Scene name (no extension) for create/load/save"],
-        path: Annotated[str, "Asset path for scene operations (default: 'Assets/')"],
+        name: Annotated[str | None, "Scene name (no extension) for create/load/save"] = None,
+        path: Annotated[str | None, "Asset path for scene operations (default: 'Assets/')"] = None,
UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py (1)

48-55: Guard response["data"] before decoding shader payloads.

If Unity replies with {success: True, data: None} (or any non-dict), response.get("data", {}).get("contentsEncoded") raises AttributeError, which dumps us into the catch block and hides the real Unity response—exactly the failure called out in the earlier review. Please normalize data to a dict before touching it so we don't crash on valid responses.

-                if response.get("data", {}).get("contentsEncoded"):
-                    decoded_contents = base64.b64decode(
-                        response["data"]["encodedContents"]).decode('utf-8')
-                    response["data"]["contents"] = decoded_contents
-                    del response["data"]["encodedContents"]
-                    del response["data"]["contentsEncoded"]
+                data = response.get("data")
+                if isinstance(data, dict) and data.get("contentsEncoded"):
+                    encoded = data.get("encodedContents")
+                    if encoded:
+                        decoded_contents = base64.b64decode(encoded).decode('utf-8')
+                        data["contents"] = decoded_contents
+                    data.pop("encodedContents", None)
+                    data.pop("contentsEncoded", None)
+                    response["data"] = data

Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py Outdated
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py Outdated
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/resource_tools.py
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Whitespace changes

{ "read_console", ReadConsole.HandleCommand },
{ "manage_menu_item", ManageMenuItem.HandleCommand },
{ "manage_shader", ManageShader.HandleCommand},
{ "manage_prefabs", ManagePrefabs.HandleCommand},
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Issue with earlier merge, this should have been in

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Whitespace changes

Copy link
Copy Markdown
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: 2

Caution

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

⚠️ Outside diff range comments (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (1)

54-57: Preserve full Unity payload on success

When Unity responds with success: True, we now drop every other key except message and data. Existing actions like get_hierarchy and get_build_settings return structured payloads (e.g., hierarchy, scenes) outside of data, and they lose that information after this change. Please keep returning the original dict (or merge in the friendly message without discarding other fields) so downstream clients continue to receive the full payload.

Apply this diff to retain the original response while still exposing the friendly message:

-            if isinstance(response, dict) and response.get("success"):
-                return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")}
-            return response if isinstance(response, dict) else {"success": False, "message": str(response)}
+            if isinstance(response, dict):
+                if response.get("success"):
+                    response.setdefault("message", "Scene operation successful.")
+                return response
+            return {"success": False, "message": str(response)}
🧹 Nitpick comments (4)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (4)

17-26: Optional parameter typing is correct; consider polishing the action’s description.

  • Using Annotated[T] | None for optionals resolves implicit Optional (RUF013) and ensures correct FastMCP schema. Good.
  • Minor: “Operations” is vague; a clearer description (e.g., “Operation to perform”) helps clients render better tooltips.

Suggested change:

-                                  "get_active_tool", "get_selection", "get_prefab_stage", "set_active_tool", "add_tag", "remove_tag", "get_tags", "add_layer", "remove_layer", "get_layers"], "Operations"],
+                                  "get_active_tool", "get_selection", "get_prefab_stage", "set_active_tool", "add_tag", "remove_tag", "get_tags", "add_layer", "remove_layer", "get_layers"], "Operation to perform"],

36-47: Pre‑validate companion params for clearer client errors.

Returning early on missing companion args gives immediate, actionable feedback instead of a round‑trip error from Unity.

             if action == "telemetry_ping":
                 record_tool_usage("diagnostic_ping", True, 1.0, None)
                 return {"success": True, "message": "telemetry ping queued"}
-            # Prepare parameters, removing None values
+            # Validate required companion parameters for certain actions
+            if action == "set_active_tool" and not tool_name:
+                return {"success": False, "message": "tool_name is required when action is 'set_active_tool'."}
+            if action in {"add_tag", "remove_tag"} and not tag_name:
+                return {"success": False, "message": "tag_name is required when action is 'add_tag' or 'remove_tag'."}
+            if action in {"add_layer", "remove_layer"} and not layer_name:
+                return {"success": False, "message": "layer_name is required when action is 'add_layer' or 'remove_layer'."}
+            # Prepare parameters, removing None values

16-16: Rename unused ctx param or suppress to satisfy Ruff ARG001.

Change the param name to underscore‑prefixed:

-        ctx: Context,
+        _ctx: Context,

Alternatively, keep the name and add a no‑op reference before use (less clean): _ = ctx.

Based on static analysis hints


59-59: Use f‑string conversion flag to satisfy RUF010.

-            return {"success": False, "message": f"Python error managing editor: {str(e)}"}
+            return {"success": False, "message": f"Python error managing editor: {e!s}"}

Based on static analysis hints

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca2529 and 8375648.

📒 Files selected for processing (3)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (3 hunks)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py (4)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (2)
  • get_unity_connection (378-399)
  • async_send_command_with_retry (439-452)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
tests/test_manage_script_uri.py (1)
  • tool (46-50)
tests/test_script_tools.py (1)
  • tool (43-47)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py (2)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (1)
  • telemetry_tool (17-107)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
🪛 Ruff (0.13.1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py

19-19: Unused function argument: ctx

(ARG001)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py

16-16: Unused function argument: ctx

(ARG001)


59-59: Use explicit conversion flag

Replace with conversion flag

(RUF010)

UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py

15-15: Unused function argument: ctx

(ARG001)


60-60: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py (1)

1-1: Adopts FastMCP conventions and centralized retry — looks good.

  • Typed imports, named tool decorator with description, camelCase payload keys (toolName/layerName), and None‑filtering all align with FastMCP + Unity payload expectations.

Also applies to: 3-3, 7-7, 13-13, 40-42, 49-49

Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py Outdated
Comment thread UnityMcpBridge/UnityMcpServer~/src/tools/manage_asset.py
@msanatan msanatan self-assigned this Sep 27, 2025
@msanatan
Copy link
Copy Markdown
Member Author

Lots of good feedback, and tested a good bit. Merging in

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

Caution

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

⚠️ Outside diff range comments (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (1)

152-173: Zero-based explicit coords are clamped, not translated (+1); edits land at wrong positions.

When explicit fields are zero-based, the code sets all <1 values to 1 instead of translating to 1-based. This corrupts target ranges. Translate all four coords by +1 (then clamp) in both branches.

Apply this diff in both blocks:

-                        for k in ("startLine", "startCol", "endLine", "endCol"):
-                            try:
-                                if int(e2.get(k, 1)) < 1:
-                                    e2[k] = 1
-                            except Exception:
-                                pass
+                        for k in ("startLine", "startCol", "endLine", "endCol"):
+                            try:
+                                v = int(e2.get(k, 0))
+                                e2[k] = max(1, v + 1)
+                            except Exception:
+                                pass

Also consider logging instead of bare except ...: pass to aid debugging.

Also applies to: 217-239

🧹 Nitpick comments (2)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (2)

459-465: Consolidate Annotated metadata strings per param (cleaner for FastMCP parsers).

Multiple string metadata entries may be ignored by some clients. Prefer a single descriptive string.

-        name: Annotated[str, "Script name (no .cs extension)", "Name of the script to create"],
-        path: Annotated[str, "Asset path (default: 'Assets/')", "Path under Assets/ to create the script at, e.g., 'Assets/Scripts/My.cs'"],
-        contents: Annotated[str, "Contents of the script to create",
-                            "C# code for 'create'/'update'"] | None = None,
-        script_type: Annotated[str, "Script type (e.g., 'C#')",
-                               "Type hint (e.g., 'MonoBehaviour')"] | None = None,
+        name: Annotated[str, "Script name without .cs; for create, becomes the new file's base name."],
+        path: Annotated[str, "Assets-relative path. Example: Assets/Scripts/My.cs"],
+        contents: Annotated[str, "C# contents for create/update (plain text; encoded for transport internally)."] | None = None,
+        script_type: Annotated[str, "Script type (e.g., 'C#') or hint (e.g., 'MonoBehaviour')."] | None = None,

294-307: Remove unused difflib import or return a minimal diff preview.

difflib and locals are unused; keep it lean or compute a small unified diff if needed.

-                import difflib
-                # Apply locally to preview final result
-                lines = []
-                # Build an indexable original from a read if we normalized from read; otherwise skip
-                prev = ""
-                # We cannot guarantee file contents here without a read; return normalized spans only
+                # Return normalized spans only (no local diff build)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e338c7a and 4d50584.

📒 Files selected for processing (1)
  • UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (18 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (2)
UnityMcpBridge/UnityMcpServer~/src/unity_connection.py (1)
  • send_command_with_retry (416-436)
UnityMcpBridge/UnityMcpServer~/src/telemetry_decorator.py (2)
  • telemetry_tool (17-107)
  • decorator (19-106)
🪛 Ruff (0.13.1)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py

16-16: Unused function argument: tool_name

(ARG001)


169-170: try-except-pass detected, consider logging the exception

(S110)


169-169: Do not catch blind exception: Exception

(BLE001)


234-235: try-except-pass detected, consider logging the exception

(S110)


234-234: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (5)
UnityMcpBridge/UnityMcpServer~/src/tools/manage_script.py (5)

76-89: Good FastMCP metadata and UX notes for apply_text_edits.

Clear description, step-by-step guidance, and 1-indexing callout are on point.


380-390: Path hygiene looks solid.

Good normalization and guardrails against traversal/absolute paths; ensures under Assets/.


515-544: Capability surface is concise and consistent.

Clear ops/text_ops and max payload; extras advertise get_sha for clients. LGTM.


547-566: Minimal SHA endpoint is useful and safe.

Returns only sha256/lengthBytes; good for optimistic concurrency without content fetch.


454-466: Restore 'update' in the action Literal to keep legacy calls working.

Function still routes legacy update flows, but the enum excludes it, so FastMCP clients will reject those requests pre-dispatch.

-        action: Annotated[Literal['create', 'read', 'delete'], "Perform CRUD operations on C# scripts."],
+        action: Annotated[Literal['create', 'read', 'update', 'delete'], "Perform CRUD operations on C# scripts. Note: 'update' is deprecated but retained for backward compatibility."],
#!/bin/bash
# Find any in-repo callers still sending 'update'
rg -nP -C2 "['\"]action['\"]\s*:\s*['\"]update['\"]"

@msanatan msanatan merged commit 5acf107 into CoplayDev:main Sep 27, 2025
1 check passed
@msanatan msanatan deleted the standardize-mcp-tools branch September 27, 2025 17:53
dsarno pushed a commit to dsarno/unity-mcp that referenced this pull request Sep 30, 2025
* refactor: migrate command routing to use CommandRegistry lookup instead of switch statement

* style: improve code formatting and indentation consistency

* refactor: clean up imports and type hints across tool modules

* Revert "feat: Implement Asset Store Compliance for Unity MCP Bridge"

This reverts commit 2fca7fc.

* Revert "feat(asset-store): implement post-installation prompt system for Asset Store compliance"

This reverts commit ab25a71.

* chore: upgrade mcp[cli] dependency from 1.4.1 to 1.15.0

* style: fix formatting and whitespace in Python server files

* Remove description, probably a Python versionn change

* feat: add type hints and parameter descriptions to Unity MCP tools

* docs: improve shader management tool parameter descriptions and types

* refactor: add type annotations and improve documentation for script management tools

* refactor: improve type annotations and documentation in manage_scene tool

* refactor: add type annotations and improve parameter descriptions across MCP tools

* feat: add explicit name parameters to all MCP tool decorators

* refactor: remove unused Unity connection instance in manage_asset_tools

* chore: update type hints in manage_editor function parameters for better clarity

* feat: make name and path parameters optional for scene management operations

* refactor: remove unused get_unity_connection import from manage_asset.py

* chore: rename Operation parameter annotation to Operations for consistency

* feat: add logging to MCP clients for tool actions across MCP server components

* chore: add FastMCP type hint to register_all_tools parameter

* style: reformat docstring in apply_text_edits tool to use multiline string syntax

* refactor: update type hints from Dict/List/Tuple/Optional to modern Python syntax

* refactor: clean up imports and add type annotations to script editing tools

* refactor: update type hints to use | None syntax for optional parameters

* Minor fixes

* docs: improve tool descriptions with clearer action explanations

* refactor: remove legacy update action migration code from manage_script.py

* style: replace em dashes with regular hyphens in tool descriptions [skip ci]

* refactor: convert manage_script_capabilities docstring to multiline format [skip ci]
@coderabbitai coderabbitai Bot mentioned this pull request Dec 9, 2025
This was referenced Jan 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 8, 2026
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Mar 15, 2026
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.

Standardize tool calls across LLMs

1 participant