Skip to content

Bug fix and log feature update#919

Merged
Scriptwonder merged 2 commits intoCoplayDev:betafrom
Scriptwonder:beta
Mar 11, 2026
Merged

Bug fix and log feature update#919
Scriptwonder merged 2 commits intoCoplayDev:betafrom
Scriptwonder:beta

Conversation

@Scriptwonder
Copy link
Collaborator

@Scriptwonder Scriptwonder commented Mar 11, 2026

  1. Add McpLog that could log mcp calls and errors under Asset/
  2. Solve issues Unable to Set Component Reference Properties via manage_components Tool #816 via incluging str in annotation, extracted AssignedObjectreference() helper that verify assignments and resolve component types from gameobjects.

Related Issues

#816

Additional Notes

Summary by Sourcery

Add configurable MCP execution logging and improve component/property handling and annotations across Unity and server tools.

New Features:

  • Introduce McpLogRecord utility to log MCP tool executions, including errors and durations, to asset log files.
  • Expose an editor preference and UI toggle to enable or disable MCP execution logging from the advanced settings panel.

Bug Fixes:

  • Improve object reference assignment in ComponentOps to correctly resolve and assign compatible components from GameObjects, including when values are provided as strings or with component filters.
  • Allow server-side tools to accept string-encoded property dictionaries, fixing issues when annotations were limited to dict-only types.

Enhancements:

  • Centralize SerializedProperty object assignment logic via a reusable AssignObjectReference helper to reduce duplication and ensure consistent behavior in scene and asset lookups.
  • Extend TransportCommandDispatcher to record execution metadata for both synchronous and asynchronous commands, including success and error outcomes.
  • Add a new editor preference key to persist MCP execution logging state across sessions.

Summary by CodeRabbit

  • New Features

    • Structured MCP execution logging (session headers, separate error log, file rotation) with an Advanced-settings toggle and a new editor preference to enable it.
    • Per-command performance and status logging for tool operations (including async completions).
  • Refactor

    • Improved object/component reference assignment with component-level fallback and better numeric-ID/name resolution.
    • Server tool APIs now accept either structured objects or string forms for property inputs.

1. Add McpLog that could log mcp calls and errors under Asset/
2. Solve issues CoplayDev#816 via incluging str in annotation, extracted AssignedObjectreference() helper that verify assignments and resolve component types from gameobjects.
Copilot AI review requested due to automatic review settings March 11, 2026 05:10
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 11, 2026

Reviewer's Guide

Introduces structured MCP execution logging to asset files and refactors Unity object assignment to support string IDs and component filtering while relaxing several Python tool parameter annotations to also accept plain strings.

Sequence diagram for MCP command execution logging

sequenceDiagram
    actor EditorUser
    participant McpAdvancedSection
    participant TransportCommandDispatcher
    participant CommandRegistry
    participant PendingCommand
    participant McpLogRecord

    EditorUser->>McpAdvancedSection: Toggle logRecordToggle
    McpAdvancedSection->>McpLogRecord: Set IsEnabled

    EditorUser->>TransportCommandDispatcher: Execute MCP command
    TransportCommandDispatcher->>CommandRegistry: ExecuteCommand(type, parameters, completionSource)
    alt Async command (result is null)
        TransportCommandDispatcher->>TransportCommandDispatcher: Start Stopwatch (sw)
        CommandRegistry-->>PendingCommand: completionSource.Task
        PendingCommand-->>TransportCommandDispatcher: Task completion callback
        TransportCommandDispatcher->>TransportCommandDispatcher: Stop sw
        TransportCommandDispatcher->>McpLogRecord: Log(commandType, parameters, logType, status, durationMs, error)
    else Sync command (result not null)
        TransportCommandDispatcher->>TransportCommandDispatcher: Start Stopwatch (sw)
        CommandRegistry-->>TransportCommandDispatcher: result
        TransportCommandDispatcher->>TransportCommandDispatcher: Stop sw
        TransportCommandDispatcher->>McpLogRecord: Log(commandType, parameters, logType, SUCCESS, durationMs)
    end
Loading

Class diagram for McpLogRecord and ComponentOps updates

classDiagram
    class McpLogRecord {
        - string LogPath
        - string ErrorLogPath
        - long MaxLogSizeBytes
        - bool _sessionStarted
        + bool IsEnabled
        + void Log(string commandType, JObject parameters, string type, string status, long durationMs, string error)
        - void AppendLine(string path, string line)
        - void RotateIfNeeded(string path)
    }

    class EditorPrefKeys {
        <<static>>
        + string LogRecordEnabled
    }

    class TransportCommandDispatcher {
        - void ProcessCommand(string id, PendingCommand pending)
    }

    class ComponentOps {
        - bool SetObjectReference(SerializedProperty prop, JToken value, string error)
        - bool AssignObjectReference(SerializedProperty prop, UnityEngineObject resolved, string componentFilter, string error)
        - bool ResolveSceneObjectByName(SerializedProperty prop, string name, string error)
    }

    class McpAdvancedSection {
        - Toggle debugLogsToggle
        - Toggle logRecordToggle
        + void CacheUIElements()
        + void InitializeUI()
        + void RegisterCallbacks()
        + void UpdatePathOverrides()
    }

    class SerializedProperty {
        + UnityEngineObject objectReferenceValue
    }

    class GameObject {
        + string name
        + Component[] GetComponents()
    }

    class Component {
    }

    class GameObjectLookup {
        + GameObject ResolveInstanceID(int id)
        + GameObject ResolveSceneObjectByName(string name)
    }

    class AssetDatabase {
        + UnityEngineObject LoadAssetAtPath(string path)
    }

    class AssetPathUtility {
        + string SanitizeAssetPath(string path)
    }

    class McpLog {
        + void Warn(string message)
        + void SetDebugLoggingEnabled(bool enabled)
    }

    McpLogRecord ..> EditorPrefKeys : uses
    McpLogRecord ..> McpLog : logsWarnings
    McpAdvancedSection ..> McpLogRecord : togglesLogging
    TransportCommandDispatcher ..> McpLogRecord : recordsExecution
    ComponentOps ..> GameObjectLookup : resolvesGameObjects
    ComponentOps ..> AssetDatabase : loadsAssets
    ComponentOps ..> AssetPathUtility : sanitizesPaths
    ComponentOps --> SerializedProperty : assignsReferences
    ComponentOps --> GameObject : inspectsComponents
    GameObject --> Component : contains
Loading

File-Level Changes

Change Details Files
Refactor Unity object reference assignment into a reusable helper that supports component-type filtering and string instance IDs.
  • Replace direct SerializedProperty.objectReferenceValue assignments with a centralized AssignObjectReference helper that handles nulls and compatibility errors.
  • Support an optional "component" field in JSON inputs to resolve and assign a specific component on a GameObject by type name.
  • Allow string values that are purely numeric to be treated as instance IDs before falling back to asset path or name-based resolution.
  • Reuse AssignObjectReference in scene name resolution to share component-selection behavior and error messages.
MCPForUnity/Editor/Helpers/ComponentOps.cs
Add configurable MCP execution recording to log files with an editor toggle.
  • Introduce McpLogRecord helper to append compact JSON lines to Assets/mcp.log and Assets/mcpError.log with basic log rotation.
  • Expose an IsEnabled flag backed by a new EditorPref key to control recording.
  • Add a UI toggle in the advanced MCP settings to enable/disable recording and keep it synchronized with preferences.
  • Instrument TransportCommandDispatcher to time command execution and log tool/resource invocations, status, duration, and errors via McpLogRecord.
MCPForUnity/Editor/Helpers/McpLogRecord.cs
MCPForUnity/Editor/Constants/EditorPrefKeys.cs
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml
MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
MCPForUnity/Editor/Helpers/McpLogRecord.cs.meta
Relax several Python MCP tool parameter type annotations to accept either dicts or raw strings.
  • Update manage_asset "properties" annotation to allow dict[str, Any] or str.
  • Update manage_components "properties" annotation to allow dict[str, Any] or str.
  • Update manage_gameobject "component_properties" annotation to allow dict[str, dict[str, Any]] or str.
  • Update manage_material "properties" annotation to allow dict[str, Any] or str.
Server/src/services/tools/manage_asset.py
Server/src/services/tools/manage_components.py
Server/src/services/tools/manage_gameobject.py
Server/src/services/tools/manage_material.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6d61d91-9d42-4430-8200-154f99acd504

📥 Commits

Reviewing files that changed from the base of the PR and between e4b131b and f86a4d9.

📒 Files selected for processing (3)
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
  • MCPForUnity/Editor/Helpers/McpLogRecord.cs
  • MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs

📝 Walkthrough

Walkthrough

Adds editor-side structured logging (mcp.log) with a UI toggle, introduces a helper for component-aware SerializedProperty assignments, and widens several server tool function parameters to accept either dicts or JSON strings.

Changes

Cohort / File(s) Summary
Editor prefs & logging
MCPForUnity/Editor/Constants/EditorPrefKeys.cs
Adds internal EditorPrefs key LogRecordEnabled.
Logging implementation
MCPForUnity/Editor/Helpers/McpLogRecord.cs, MCPForUnity/Editor/Helpers/McpLogRecord.cs.meta
New internal logger that writes JSON entries to mcp.log, emits a session_start entry, rotates logs at ~1MB, and duplicates ERROR entries to mcpError.log; gated by EditorPrefs key.
Command instrumentation
MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
Wraps command handling with timing and logs synchronous and asynchronous results (captures type, params, status, elapsed, error).
Component assignment helper
MCPForUnity/Editor/Helpers/ComponentOps.cs
Adds AssignObjectReference and updates resolution flows to apply an optional component filter when assigning GameObject references to SerializedProperty (tries direct assignment, component by name, and compatible component fallback).
UI: Advanced settings
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs, .../McpAdvancedSection.uxml
Adds logRecordToggle to Advanced UI, syncs its state with McpLogRecord.IsEnabled, persists changes and updates related UI/state refresh.
Server: tool APIs (type widening)
Server/src/services/tools/manage_asset.py, .../manage_components.py, .../manage_gameobject.py, .../manage_material.py
Widened properties/component_properties parameter annotations to accept `dict[...]

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor Process
    participant Dispatcher as TransportCommandDispatcher
    participant Logger as McpLogRecord
    participant FS as FileSystem

    Editor->>Dispatcher: Execute command (sync/async)
    Dispatcher->>Dispatcher: Start stopwatch

    alt sync
        Dispatcher->>Dispatcher: Get result
        Dispatcher->>Logger: Log SUCCESS (type, params, ms)
    else async
        Dispatcher->>Dispatcher: Capture info, await task
        Dispatcher->>Logger: Log SUCCESS/ERROR (type, params, ms, status, error?)
    end

    Logger->>Logger: If IsEnabled?
    alt enabled
        Logger->>Logger: Build JSON entry (ts, tool, type, status, ms, ...)
        alt first session log
            Logger->>FS: Write session_start entry to mcp.log
        end
        Logger->>FS: Append entry to mcp.log
        Logger->>Logger: RotateIfNeeded (>1MB)
        alt status == ERROR
            Logger->>FS: Append to mcpError.log
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • justinpbarnett

Poem

🐰 I hop through logs with tiny feet,

I mark each command and every beat.
A toggle here, a JSON line,
Components matched — the trees align.
Hooray, the mcp.log hums so sweet!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Bug fix and log feature update' is overly broad and generic. It mentions both a bug fix and a log feature but does not specify what bug is being fixed or what the log feature entails. Replace with a more specific title that highlights the primary change, such as 'Add MCP execution logging and fix component assignment handling' or 'Implement McpLogRecord utility and improve object reference resolution'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is partially complete but lacks several required template sections. Missing or incomplete: Type of Change selection, Changes Made details, Testing/Screenshots, and Documentation Updates checklist.

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

✨ Finishing Touches
🧪 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

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In AssignObjectReference, the componentFilter is only applied when resolved is a GameObject; if the resolver ever returns a Component directly, the filter will be silently ignored—consider either validating the filter in that case or enforcing that componentFilter is only used with GameObject resolutions.
  • The new McpLogRecord logger writes to the same files from async continuations and potentially the main thread without any locking; adding a simple synchronization mechanism (e.g., a lock around AppendLine/RotateIfNeeded) would reduce the risk of interleaved or corrupted log writes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `AssignObjectReference`, the `componentFilter` is only applied when `resolved` is a `GameObject`; if the resolver ever returns a `Component` directly, the filter will be silently ignored—consider either validating the filter in that case or enforcing that `componentFilter` is only used with `GameObject` resolutions.
- The new `McpLogRecord` logger writes to the same files from async continuations and potentially the main thread without any locking; adding a simple synchronization mechanism (e.g., a `lock` around `AppendLine`/`RotateIfNeeded`) would reduce the risk of interleaved or corrupted log writes.

## Individual Comments

### Comment 1
<location path="MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs" line_range="374-377" />
<code_context>
+                    var capturedType = command.type;
+                    var capturedParams = parameters;
+                    var capturedLogType = logType;
+                    pending.CompletionSource.Task.ContinueWith(t =>
                     {
+                        sw?.Stop();
+                        McpLogRecord.Log(capturedType, capturedParams, capturedLogType,
+                            t.IsFaulted ? "ERROR" : "SUCCESS",
+                            sw?.ElapsedMilliseconds ?? 0,
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid calling McpLogRecord (and Unity APIs transitively) from a background thread in the async continuation.

Because this continuation uses `TaskScheduler.Default`, it will typically run on a thread‑pool thread. `McpLogRecord.Log` reads Unity state (`Application.unityVersion`, `EditorPrefs` via `IsEnabled`, and possibly `McpLog.Warn`), which is generally only safe from the main thread in the editor and may lead to subtle bugs or crashes when called off‑thread.

Please either:
- Marshal the logging work to the main thread (e.g., via `EditorApplication.delayCall`), or
- Capture only plain data here and forward it to a main‑thread logger.

Also ensure the synchronous `.Result` path uses the same main‑thread logging model for consistency.
</issue_to_address>

### Comment 2
<location path="MCPForUnity/Editor/Helpers/McpLogRecord.cs" line_range="65-68" />
<code_context>
+                var line = entry.ToString(Formatting.None);
+                AppendLine(LogPath, line);
+
+                if (status == "ERROR")
+                {
+                    RotateIfNeeded(ErrorLogPath);
+                    AppendLine(ErrorLogPath, line);
+                }
+            }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider synchronizing log writes/rotation to avoid races when multiple commands log concurrently.

`Log` may now be invoked concurrently from multiple async continuations. `RotateIfNeeded` rewrites the file while `AppendLine` appends, but these aren’t synchronized. Under load, that can corrupt logs or throw IO exceptions. To harden this, consider serializing access per file (e.g., a lock around `RotateIfNeeded` + `AppendLine`, or a dedicated logging queue).

Suggested implementation:

```csharp
                if (parameters != null)
                    entry["params"] = parameters;

                if (error != null)
                    entry["error"] = error;

                var line = entry.ToString(Formatting.None);

                // Serialize writes to the main log file to avoid races between concurrent log calls.
                lock (_logFileLock)
                {
                    AppendLine(LogPath, line);
                }

                if (status == "ERROR")
                {
                    // Serialize rotation + append for the error log to avoid interleaving of rewrites and appends.
                    lock (_errorLogFileLock)
                    {
                        RotateIfNeeded(ErrorLogPath);
                        AppendLine(ErrorLogPath, line);
                    }
                }

```

```csharp
        // Synchronization objects to serialize access to log files across concurrent log invocations.
        private static readonly object _logFileLock = new object();
        private static readonly object _errorLogFileLock = new object();

        internal static void Log(string commandType, JObject parameters, string type, string status, long durationMs, string error = null)
        {
            if (!IsEnabled) return;

            try
            {
                if (!_sessionStarted)
                {
                    _sessionStarted = true;

```

1. If `AppendLine` or `RotateIfNeeded` are called on `LogPath` / `ErrorLogPath` from other methods in this class, those call sites should also be wrapped in the corresponding `lock` to ensure all access to a given file is serialized.
2. If there are additional log files beyond `LogPath` and `ErrorLogPath`, consider introducing corresponding lock objects and following the same pattern.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +374 to +377
pending.CompletionSource.Task.ContinueWith(t =>
{
sw?.Stop();
McpLogRecord.Log(capturedType, capturedParams, capturedLogType,
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Avoid calling McpLogRecord (and Unity APIs transitively) from a background thread in the async continuation.

Because this continuation uses TaskScheduler.Default, it will typically run on a thread‑pool thread. McpLogRecord.Log reads Unity state (Application.unityVersion, EditorPrefs via IsEnabled, and possibly McpLog.Warn), which is generally only safe from the main thread in the editor and may lead to subtle bugs or crashes when called off‑thread.

Please either:

  • Marshal the logging work to the main thread (e.g., via EditorApplication.delayCall), or
  • Capture only plain data here and forward it to a main‑thread logger.

Also ensure the synchronous .Result path uses the same main‑thread logging model for consistency.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in MCP execution file logger in the Unity Editor and improves server/tool parameter handling + Unity-side reference assignment to address #816 (setting Component/ObjectReference properties via manage_components).

Changes:

  • Broadened several Python tool schemas to accept JSON strings for properties/component_properties payloads.
  • Added Unity Editor “Log Record” toggle and McpLogRecord to write per-command execution entries (including duration/status).
  • Refactored Unity ObjectReference assignment to centralize validation and improve instanceID/string handling + component resolution.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Server/src/services/tools/manage_material.py Accept properties as dict or JSON string in tool schema.
Server/src/services/tools/manage_gameobject.py Accept component_properties as dict or JSON string in tool schema.
Server/src/services/tools/manage_components.py Accept properties as dict or JSON string in tool schema.
Server/src/services/tools/manage_asset.py Accept properties as dict or JSON string in tool schema.
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml Adds UI toggle for enabling file-based execution logging.
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs Wires the new toggle to EditorPrefs via McpLogRecord.IsEnabled.
MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs Measures/logs tool/resource executions and async completion results.
MCPForUnity/Editor/Helpers/McpLogRecord.cs New helper that appends JSONL log entries and rotates logs at 1MB.
MCPForUnity/Editor/Helpers/ComponentOps.cs Adds AssignObjectReference helper with component fallback/filter and improved string handling.
MCPForUnity/Editor/Constants/EditorPrefKeys.cs Adds LogRecordEnabled preference key.
Comments suppressed due to low confidence (1)

MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs:398

  • Exceptions caught in ProcessCommand (sync execution errors) are not written to McpLogRecord, so the file log will miss exactly the failures it’s meant to capture. Consider calling McpLogRecord.Log(..., status="ERROR", ...) inside the catch block as well (including a useful error message/stack info as appropriate).
                sw?.Stop();
                McpLogRecord.Log(command.type, parameters, logType, "SUCCESS", sw?.ElapsedMilliseconds ?? 0);

                var response = new { status = "success", result };
                pending.TrySetResult(JsonConvert.SerializeObject(response));
                RemovePending(id, pending);
            }
            catch (Exception ex)
            {
                McpLog.Error($"Error processing command: {ex.Message}\n{ex.StackTrace}");
                pending.TrySetResult(SerializeError(ex.Message, "Unknown (error during processing)", ex.StackTrace));
                RemovePending(id, pending);
            }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +13 to +15
private static readonly string LogPath = Path.Combine(Application.dataPath, "mcp.log");
private static readonly string ErrorLogPath = Path.Combine(Application.dataPath, "mcpError.log");
private const long MaxLogSizeBytes = 1024 * 1024; // 1 MB
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Log files are written under Application.dataPath (Assets/), which will trigger frequent asset-database refresh/import when enabled and can significantly slow the editor + create noisy VCS changes. Consider writing to Library/ (or Application.persistentDataPath/Application.temporaryCachePath) and/or ensuring these files are excluded from version control.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +60
if (parameters != null)
entry["params"] = parameters;

if (error != null)
entry["error"] = error;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This logs the full parameters object into the file. Tool params can include very large blobs (e.g., base64 file contents/previews) and potentially sensitive values, so this can quickly bloat logs and leak data into plain-text files under the project. Consider logging only a whitelist of safe keys (or only param keys), and truncating large values.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +93
private static void AppendLine(string path, string line)
{
File.AppendAllText(path, line + Environment.NewLine);
}

private static void RotateIfNeeded(string path)
{
try
{
if (!File.Exists(path)) return;
var info = new FileInfo(path);
if (info.Length <= MaxLogSizeBytes) return;

var lines = File.ReadAllLines(path);
var half = lines.Length / 2;
File.WriteAllLines(path, lines[half..]);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

AppendLine/RotateIfNeeded are not synchronized. If multiple commands complete concurrently, log writes and rotations can interleave (e.g., one thread rotating while another appends), causing lost/corrupted lines or IO exceptions. Consider guarding file operations with a lock, or using a single-producer queue/background writer so rotation and appends are serialized.

Copilot uses AI. Check for mistakes.
Comment on lines +377 to +380
McpLogRecord.Log(capturedType, capturedParams, capturedLogType,
t.IsFaulted ? "ERROR" : "SUCCESS",
sw?.ElapsedMilliseconds ?? 0,
t.IsFaulted ? t.Exception?.InnerException?.Message : null);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

In the async-command path, canceled tasks will currently be logged as SUCCESS because the status only checks t.IsFaulted. Consider handling t.IsCanceled explicitly (e.g., log CANCELED/ERROR) so logs don’t incorrectly report cancellations as successes.

Suggested change
McpLogRecord.Log(capturedType, capturedParams, capturedLogType,
t.IsFaulted ? "ERROR" : "SUCCESS",
sw?.ElapsedMilliseconds ?? 0,
t.IsFaulted ? t.Exception?.InnerException?.Message : null);
var status = t.IsCanceled
? "CANCELED"
: t.IsFaulted
? "ERROR"
: "SUCCESS";
var errorMessage = t.IsFaulted
? t.Exception?.InnerException?.Message
: t.IsCanceled
? "Task was canceled."
: null;
McpLogRecord.Log(capturedType, capturedParams, capturedLogType,
status,
sw?.ElapsedMilliseconds ?? 0,
errorMessage);

Copilot uses AI. Check for mistakes.
Comment on lines +749 to +760
foreach (var comp in components)
{
if (comp == null) continue;
if (string.Equals(comp.GetType().Name, componentFilter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(comp.GetType().FullName, componentFilter, StringComparison.OrdinalIgnoreCase))
{
prop.objectReferenceValue = comp;
if (prop.objectReferenceValue != null)
return true;
}
}
error = $"Component '{componentFilter}' not found on GameObject '{filterGo.name}'.";
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

When a componentFilter is provided and the target property does not accept Components (e.g., the field is a GameObject), this branch will fail and return Component '<name>' not found even if the component exists—because assignment is rejected by the property type. Consider improving the failure message to distinguish “component not found” vs “component found but incompatible”, and/or falling back to assigning the GameObject if it’s compatible.

Suggested change
foreach (var comp in components)
{
if (comp == null) continue;
if (string.Equals(comp.GetType().Name, componentFilter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(comp.GetType().FullName, componentFilter, StringComparison.OrdinalIgnoreCase))
{
prop.objectReferenceValue = comp;
if (prop.objectReferenceValue != null)
return true;
}
}
error = $"Component '{componentFilter}' not found on GameObject '{filterGo.name}'.";
var foundMatchingComponent = false;
foreach (var comp in components)
{
if (comp == null) continue;
if (string.Equals(comp.GetType().Name, componentFilter, StringComparison.OrdinalIgnoreCase) ||
string.Equals(comp.GetType().FullName, componentFilter, StringComparison.OrdinalIgnoreCase))
{
foundMatchingComponent = true;
// First, try assigning the component itself.
prop.objectReferenceValue = comp;
if (prop.objectReferenceValue != null)
return true;
// If the property does not accept this component type, try assigning
// the GameObject itself in case the property expects a GameObject.
prop.objectReferenceValue = filterGo;
if (prop.objectReferenceValue != null)
return true;
}
}
if (foundMatchingComponent)
{
error = $"Component '{componentFilter}' found on GameObject '{filterGo.name}' but it is not compatible with the property type.";
}
else
{
error = $"Component '{componentFilter}' not found on GameObject '{filterGo.name}'.";
}

Copilot uses AI. Check for mistakes.
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: 4

🧹 Nitpick comments (2)
Server/src/services/tools/manage_gameobject.py (1)

14-38: Consider validating the nested dict structure.

The _normalize_component_properties function accepts any dict without validating that the values are themselves dicts. According to the annotation on lines 92-97, the expected structure is dict[str, dict[str, Any]]. Input like {"MyScript": "not_a_dict"} would pass validation but may cause errors on the Unity side.

🛡️ Optional: Add nested structure validation
 def _normalize_component_properties(value: Any) -> tuple[dict[str, dict[str, Any]] | None, str | None]:
     """
     Robustly normalize component_properties to a dict.
     Returns (parsed_dict, error_message). If error_message is set, parsed_dict is None.
     """
     if value is None:
         return None, None
 
-    # Already a dict - validate structure
+    # Already a dict - validate nested structure
     if isinstance(value, dict):
+        for key, val in value.items():
+            if not isinstance(val, dict):
+                return None, f"component_properties['{key}'] must be a dict, got {type(val).__name__}"
         return value, None
 
     # Try parsing as JSON string
     if isinstance(value, str):
         # Check for obviously invalid values
         if value in ("[object Object]", "undefined", "null", ""):
             return None, f"component_properties received invalid value: '{value}'. Expected a JSON object like {{\"ComponentName\": {{\"property\": value}}}}"
 
         parsed = parse_json_payload(value)
         if isinstance(parsed, dict):
+            for key, val in parsed.items():
+                if not isinstance(val, dict):
+                    return None, f"component_properties['{key}'] must be a dict, got {type(val).__name__}"
             return parsed, None
 
         return None, f"component_properties must be a JSON object (dict), got string that parsed to {type(parsed).__name__}"
 
     return None, f"component_properties must be a dict or JSON string, got {type(value).__name__}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Server/src/services/tools/manage_gameobject.py` around lines 14 - 38, The
function _normalize_component_properties should enforce that when a dict (or a
parsed JSON dict) is accepted, each value is itself a dict[str, Any]; update
_normalize_component_properties to iterate over the top-level dict (after
parse_json_payload when value is a string) and validate that every value is a
dict, returning (None, error_message) if any entry has a non-dict value (include
the offending key and its type in the message) and otherwise return the
validated dict; preserve existing special-string checks and error formats so
callers still receive the same return types.
MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs (1)

371-377: Snapshot the request params before deferring the log.

Line 372 captures the same mutable params object that gets passed into CommandRegistry.ExecuteCommand(). If a handler normalizes or rewrites that object before completion, the recorded entry no longer reflects the incoming request. Capture a snapshot before execution and log that snapshot in both branches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs` around
lines 371 - 377, The code currently captures the mutable parameters object
(parameters) into capturedParams and defers logging until
pending.CompletionSource.Task finishes, which can record a mutated request;
create an immutable snapshot of the request parameters (e.g.,
capturedParamsSnapshot) immediately before calling
CommandRegistry.ExecuteCommand (or before creating pending) and use that
snapshot in the ContinueWith callback and in both success/failure log branches
when calling McpLogRecord.Log(capturedType, capturedParamsSnapshot,
capturedLogType,...). Ensure the snapshot is a deep copy/JSON clone so later
handler mutations don't affect the logged data and replace all uses of
capturedParams in the deferred logging with the snapshot.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@MCPForUnity/Editor/Helpers/ComponentOps.cs`:
- Around line 600-601: The component filter parsed into the variable
componentFilter is not being forwarded when the payload uses the { "name": ... }
branch, and ResolveSceneObjectByName(...) is being called with a hardcoded null
component; update the name-branch code path to pass componentFilter through to
the same resolution logic (i.e. call ResolveSceneObjectByName(name,
componentFilter) or otherwise forward componentFilter) so that
ResolveSceneObjectByName receives the requested component type instead of null;
ensure all analogous spots noted (the blocks around the { "name": ... } branch
and the call sites that currently pass null) are updated to use the
componentFilter variable.

In `@MCPForUnity/Editor/Helpers/McpLogRecord.cs`:
- Around line 30-40: The log rotation is only invoked when starting a session
(_sessionStarted check), so subsequent AppendLine calls (AppendLine(LogPath,
...)) never recheck size and mcp.log can grow past the limit; fix by ensuring
RotateIfNeeded(LogPath) is called before every AppendLine call (i.e., move or
add RotateIfNeeded(LogPath) so it runs both inside the session start path and
the normal append path or incorporate rotation into AppendLine itself),
referencing the RotateIfNeeded, _sessionStarted, AppendLine and LogPath symbols
when you update the code.
- Around line 24-69: Concurrent calls to Log() can race on _sessionStarted,
RotateIfNeeded(), and AppendLine(), causing duplicate session_start records or
lost lines; protect the entire critical section by introducing and using a
single private static lock object (e.g., _logLock) and wrap the code paths that
read/modify _sessionStarted, call RotateIfNeeded(LogPath/ErrorLogPath) and call
AppendLine(...) in a single lock statement inside Log(string, JObject, ...).
Apply the same locking to the other logging method that touches the same state
(the other Log overload referenced in the comment) so session rotation and
appends are serialized.

In `@MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs`:
- Around line 364-387: The async logging currently only checks t.IsFaulted and
logs SUCCESS for handlers that return a JSON payload with status="error"; update
the continuation that runs on pending.CompletionSource (and the synchronous path
after CommandRegistry.ExecuteCommand returns non-null, plus any early returns
such as SerializeError) to derive the outcome from the completed payload instead
of only IsFaulted — inspect the Task result
(pending.CompletionSource.Task.Result or the object passed via TrySetResult) to
read the response status field and map it to "SUCCESS"/"ERROR", include any
error message when status indicates failure, and call McpLogRecord.Log with that
derived status and message; alternatively, change the call sites where handlers
complete to pass an explicit outcome into the continuation and ensure
RemovePending is still scheduled after logging. Ensure all failure flows (sync
exceptions, SerializeError returns, and async responses with status="error")
invoke McpLogRecord.Log using the same mapping logic.

---

Nitpick comments:
In `@MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs`:
- Around line 371-377: The code currently captures the mutable parameters object
(parameters) into capturedParams and defers logging until
pending.CompletionSource.Task finishes, which can record a mutated request;
create an immutable snapshot of the request parameters (e.g.,
capturedParamsSnapshot) immediately before calling
CommandRegistry.ExecuteCommand (or before creating pending) and use that
snapshot in the ContinueWith callback and in both success/failure log branches
when calling McpLogRecord.Log(capturedType, capturedParamsSnapshot,
capturedLogType,...). Ensure the snapshot is a deep copy/JSON clone so later
handler mutations don't affect the logged data and replace all uses of
capturedParams in the deferred logging with the snapshot.

In `@Server/src/services/tools/manage_gameobject.py`:
- Around line 14-38: The function _normalize_component_properties should enforce
that when a dict (or a parsed JSON dict) is accepted, each value is itself a
dict[str, Any]; update _normalize_component_properties to iterate over the
top-level dict (after parse_json_payload when value is a string) and validate
that every value is a dict, returning (None, error_message) if any entry has a
non-dict value (include the offending key and its type in the message) and
otherwise return the validated dict; preserve existing special-string checks and
error formats so callers still receive the same return types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cd2e6757-eb3d-4085-b54b-d8c3d19123a1

📥 Commits

Reviewing files that changed from the base of the PR and between 7e57986 and e4b131b.

📒 Files selected for processing (11)
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
  • MCPForUnity/Editor/Helpers/McpLogRecord.cs
  • MCPForUnity/Editor/Helpers/McpLogRecord.cs.meta
  • MCPForUnity/Editor/Services/Transport/TransportCommandDispatcher.cs
  • MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs
  • MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml
  • Server/src/services/tools/manage_asset.py
  • Server/src/services/tools/manage_components.py
  • Server/src/services/tools/manage_gameobject.py
  • Server/src/services/tools/manage_material.py

Comment on lines +600 to +601
// Optional component type filter — e.g. {"instanceID": 123, "component": "Button"}
string componentFilter = jObj["component"]?.ToString();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pass componentFilter through the { "name": ... } path.

componentFilter is parsed at Lines 600-601, but the { "name": ... } branch at Line 689 drops it and ResolveSceneObjectByName() hardcodes null at Line 819. A payload like { "name": "Canvas", "component": "Button" } will therefore ignore the requested component or fall back to the first compatible one.

🧩 Proposed fix
-                    return ResolveSceneObjectByName(prop, nameToken.ToString(), out error);
+                    return ResolveSceneObjectByName(prop, nameToken.ToString(), componentFilter, out error);
...
-        private static bool ResolveSceneObjectByName(SerializedProperty prop, string name, out string error)
+        private static bool ResolveSceneObjectByName(SerializedProperty prop, string name, string componentFilter, out string error)
         {
             error = null;
             if (string.IsNullOrWhiteSpace(name))
             {
                 error = "Cannot resolve object reference from empty name.";
@@
-            return AssignObjectReference(prop, go, null, out error);
+            return AssignObjectReference(prop, go, componentFilter, out error);
         }

Also applies to: 686-690, 794-819

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Helpers/ComponentOps.cs` around lines 600 - 601, The
component filter parsed into the variable componentFilter is not being forwarded
when the payload uses the { "name": ... } branch, and
ResolveSceneObjectByName(...) is being called with a hardcoded null component;
update the name-branch code path to pass componentFilter through to the same
resolution logic (i.e. call ResolveSceneObjectByName(name, componentFilter) or
otherwise forward componentFilter) so that ResolveSceneObjectByName receives the
requested component type instead of null; ensure all analogous spots noted (the
blocks around the { "name": ... } branch and the call sites that currently pass
null) are updated to use the componentFilter variable.

@Scriptwonder Scriptwonder merged commit 3becd15 into CoplayDev:beta Mar 11, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants