Skip to content

Comments

Add --offline to uvx launches for faster startup (#760)#768

Merged
dsarno merged 4 commits intoCoplayDev:betafrom
dsarno:feature/uvx-offline-760
Feb 17, 2026
Merged

Add --offline to uvx launches for faster startup (#760)#768
dsarno merged 4 commits intoCoplayDev:betafrom
dsarno:feature/uvx-offline-760

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Feb 17, 2026

Summary

  • When the uvx cache already has the package, passes --offline to skip the network dependency check that can hang for 30+ seconds on slower/absent networks (international internet, corporate firewalls, etc)
  • A lightweight probe (uvx --offline ... --help) with a 3-second timeout determines cache warmth before building the command
  • First run / new version pin: probe fails → normal network resolution (current behavior)
  • Force refresh enabled: probe skipped → --no-cache --refresh (current behavior)

Changes

  • AssetPathUtility.cs: New ShouldUseUvxOffline() method with cache warmth probe
  • ServerCommandBuilder.cs: if/else if/else for force-refresh, offline, default
  • ConfigJsonBuilder.cs: --offline branch in BuildUvxArgs()
  • CodexConfigHelper.cs: Renamed AddDevModeArgsAddUvxModeFlags, added offline branch
  • McpClientConfiguratorBase.cs: --offline support in Register(), RegisterWithCapturedValues(), GetManualSnippet()
  • McpClientConfigSection.cs: Captures shouldUseOffline on main thread for async config
  • ManageGameObjectTests.cs: Fixed stale LogAssert expectations from ComponentOps refactor

Test plan

  • 661 Unity EditMode tests passing (0 failures)
  • 666 Python tests passing (0 failures)
  • Manual test — warm cache: --offline present in generated command
  • Manual test — cold cache (uv cache clean): --offline absent, normal network resolution
  • Manual test — local path override: --no-cache --refresh used (not --offline)

Closes #760

🤖 Generated with Claude Code

Summary by Sourcery

Add conditional use of uvx --offline mode for Unity MCP server launches to improve startup performance when packages are already cached while preserving existing behavior for refreshes and first-time installs.

New Features:

  • Introduce a cache-based probe to decide when uvx can run in --offline mode for MCP server commands.

Enhancements:

  • Route uvx invocation paths (CLI config, MCP registration, and server command building) through shared logic that selects between --no-cache --refresh, --offline, or no extra flags.
  • Adjust manual snippet generation and Codex config building to include the new offline mode flag when appropriate.
  • Capture offline-mode decisions on the main thread before running asynchronous client configuration.

Tests:

  • Add unit tests for the new offline decision helper in AssetPathUtility, including force-refresh handling and robustness against exceptions.
  • Update ManageGameObject component property tests to match the current logging behavior after recent refactors.

Summary by CodeRabbit

  • New Features

    • Centralized dev-flag generation with explicit offline-mode detection for server/CLI flows
  • Changes

    • CLI configuration now receives explicit dev-flag information for registration and snippet paths
    • Dev-flag derivation consolidated across build, server command, and config flows
    • Stdio migration skips incompatible clients when HTTP transport is selected
  • Tests

    • Added offline-mode validation tests
    • Updated test assertions for property error handling

…arm (CoplayDev#760)

When the uvx cache already has the package, pass --offline to skip the
network dependency check that can hang for 30+ seconds on poor connections.
A lightweight probe (uvx --offline ... --help) with a 3-second timeout
determines cache warmth before building the command.

Also fixes stale LogAssert expectations in SetComponentProperties test
to match current error message format from ComponentOps refactor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 17, 2026

Reviewer's Guide

Implements conditional use of uvx --offline for faster startup when the MCP Unity package is already cached, wiring this flag through all launch/registration/config paths while preserving existing behavior for force-refresh and local overrides, plus updating tests to cover the new logic and adjusted logging expectations.

Sequence diagram for uvx command construction with offline/refresh flags

sequenceDiagram
    actor Developer
    participant UnityEditor as UnityEditor_UI
    participant McpClientConfigSection
    participant AssetPathUtility
    participant McpClientConfiguratorBase

    Developer->>UnityEditor: Open MCP client configuration
    UnityEditor->>McpClientConfigSection: ConfigureClaudeCliAsync(client)

    McpClientConfigSection->>AssetPathUtility: GetUvxCommandParts()
    AssetPathUtility-->>McpClientConfigSection: uvxPath, fromArgs, packageName

    McpClientConfigSection->>AssetPathUtility: ShouldForceUvxRefresh()
    AssetPathUtility-->>McpClientConfigSection: shouldForceRefresh

    McpClientConfigSection->>AssetPathUtility: ShouldUseUvxOffline()
    AssetPathUtility-->>McpClientConfigSection: shouldUseOffline

    McpClientConfigSection->>McpClientConfiguratorBase: ConfigureWithCapturedValues(..., uvxPath, fromArgs, packageName, shouldForceRefresh, shouldUseOffline, apiKey, serverTransport)

    activate McpClientConfiguratorBase
    McpClientConfiguratorBase->>McpClientConfiguratorBase: RegisterWithCapturedValues(..., shouldForceRefresh, shouldUseOffline,...)

    alt shouldForceRefresh
        McpClientConfiguratorBase->>McpClientConfiguratorBase: devFlags = "--no-cache --refresh "
    else shouldUseOffline
        McpClientConfiguratorBase->>McpClientConfiguratorBase: devFlags = "--offline "
    else
        McpClientConfiguratorBase->>McpClientConfiguratorBase: devFlags = ""
    end

    McpClientConfiguratorBase-->>Developer: uvx command with appropriate flags
Loading

Class diagram for uvx offline flag propagation

classDiagram
    class AssetPathUtility {
        +bool ShouldForceUvxRefresh()
        +bool ShouldUseUvxOffline()
        +string GetUvxPath()
        +string GetBetaServerFromArgs(bool quoteFromPath)
        +List~string~ GetBetaServerFromArgsList()
    }

    class CodexConfigHelper {
        +string BuildCodexServerBlock(string uvPath)
        +TomlTable CreateUnityMcpTable(string uvPath)
        -void AddUvxModeFlags(TomlArray args)
    }

    class ServerCommandBuilder {
        +bool TryBuildCommand(out string fileName, out string arguments, out string workingDirectory)
        -string BuildUvxDevFlags()
    }

    class ConfigJsonBuilder {
        -IList~string~ BuildUvxArgs(string fromUrl, string packageName)
    }

    class McpClientConfigSection {
        -void ConfigureClaudeCliAsync(IMcpClientConfigurator client)
    }

    class IMcpClientConfigurator {
        <<interface>>
        +void ConfigureWithCapturedValues(string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, bool shouldUseOffline, string apiKey, Models_ConfiguredTransport serverTransport)
    }

    class McpClientConfiguratorBase {
        +void ConfigureWithCapturedValues(string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, bool shouldUseOffline, string apiKey, Models_ConfiguredTransport serverTransport)
        -void RegisterWithCapturedValues(string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, bool shouldUseOffline, string apiKey, Models_ConfiguredTransport serverTransport)
        -void Register()
        +string GetManualSnippet()
    }

    AssetPathUtility <.. CodexConfigHelper : uses
    AssetPathUtility <.. ServerCommandBuilder : uses
    AssetPathUtility <.. ConfigJsonBuilder : uses
    AssetPathUtility <.. McpClientConfigSection : uses
    McpClientConfigSection --> IMcpClientConfigurator : configures
    McpClientConfiguratorBase ..|> IMcpClientConfigurator : implements
    McpClientConfigSection ..> McpClientConfiguratorBase : typical implementation
    CodexConfigHelper ..> AssetPathUtility : mode flags
    ServerCommandBuilder ..> AssetPathUtility : mode flags
    ConfigJsonBuilder ..> AssetPathUtility : mode flags
    McpClientConfiguratorBase ..> AssetPathUtility : mode flags
Loading

File-Level Changes

Change Details Files
Add ShouldUseUvxOffline helper to probe uvx cache warmth and decide when to run in offline mode.
  • Introduce ShouldUseUvxOffline() in the asset path utility that skips offline mode when force refresh is requested.
  • Run a lightweight uvx --offline ... mcp-for-unity --help probe with a 3-second timeout using ExecPath.TryRun to detect a warm cache.
  • Handle missing uvx path and any probe exceptions by returning false, preserving current network behavior.
MCPForUnity/Editor/Helpers/AssetPathUtility.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs
Thread the offline/refresh decision into uvx-based registration and server launch commands.
  • Extend configurator methods to accept a shouldUseOffline flag captured on the main thread for async configuration.
  • Update registration command construction to choose between --no-cache --refresh, --offline, or no dev flags based on force-refresh and offline decisions.
  • Apply the same offline/refresh branching logic to server command building and manual snippet generation to keep UX consistent.
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs
MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs
Update config-generation helpers to emit --offline when appropriate in uvx argument lists.
  • Rename the dev-mode argument helper to AddUvxModeFlags to reflect its broader responsibility.
  • Have the helper append --no-cache/--refresh when force-refresh is enabled, otherwise append --offline when the offline probe succeeds.
  • Use the new helper wherever uvx args are constructed for Codex/Unity MCP config generation.
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
Adjust and extend tests to cover new offline logic and updated logging behavior.
  • Add edit mode tests ensuring ShouldUseUvxOffline returns false when force refresh is enabled and does not throw in normal conditions.
  • Fix ManageGameObject tests to match the new error/warning logging pattern after the ComponentOps refactor.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs.meta
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#760 Enable uvx-based server startup to skip online dependency checks (using an offline mode) when dependencies are already available, to avoid hangs on poor/absent networks.
#760 Ensure the offline behavior is integrated consistently across all uvx launch paths (CLI/server start, registration, config generation) while preserving existing force-refresh behavior.

Possibly linked issues

  • #: PR adds conditional uvx --offline usage on startup, achieving the requested skip of dependency checks.

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 Feb 17, 2026

📝 Walkthrough

Walkthrough

Centralizes UVX dev-flag derivation and offline probing via new AssetPathUtility APIs, replaces scattered shouldForceRefresh heuristics with a uvxDevFlags string propagated through Claude CLI configure/register flows, updates builders to emit flags from the helper, and adds editor tests for offline detection. (≈44 words)

Changes

Cohort / File(s) Summary
Configurator
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
Public/private Configure/Register signatures changed to accept string uvxDevFlags (replacing bool shouldForceRefresh); call sites updated to propagate the string.
Asset helpers
MCPForUnity/Editor/Helpers/AssetPathUtility.cs
Adds ShouldUseUvxOffline() with cached probe/TTL and probe helper; adds GetUvxDevFlags(), GetUvxDevFlags(bool,bool), and GetUvxDevFlagsList() to centralize dev-flag and offline logic.
Dev-flag usage in builders
MCPForUnity/Editor/Helpers/CodexConfigHelper.cs, MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs, MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs
Replaces ad-hoc refresh checks and inline --no-cache/--refresh additions with calls to the new AssetPathUtility APIs and iterates over returned flags when building UVX/stdio args.
UI / caller update
MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs
Removes local shouldForceRefresh; obtains uvxDevFlags = AssetPathUtility.GetUvxDevFlags() and passes it into ConfigureWithCapturedValues.
Stdio migration guard
MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs
Adds conditional skip of configurator during stdio migration when editor config uses HTTP transport and client doesn't support HTTP.
Tests
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs, .../AssetPathUtilityOfflineTests.cs.meta, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs
Adds offline detection editor tests and meta; adjusts log expectations in a GameObject test to match changed logging behavior.

Sequence Diagram

sequenceDiagram
    participant User
    participant McpClientConfigSection
    participant AssetPathUtility
    participant ConfigBuilders as ConfigJsonBuilder/ServerCommandBuilder
    participant UVX as UVX Service

    User->>McpClientConfigSection: Trigger Claude CLI configuration
    McpClientConfigSection->>AssetPathUtility: GetUvxDevFlags()
    AssetPathUtility->>UVX: (optional) Run offline probe (<=3s)
    UVX-->>AssetPathUtility: Probe result
    AssetPathUtility-->>McpClientConfigSection: Return uvxDevFlags
    McpClientConfigSection->>ConfigBuilders: Pass uvxDevFlags into Register/Build flows
    ConfigBuilders->>AssetPathUtility: GetUvxDevFlagsList()
    ConfigBuilders-->>UVX: Start/register using constructed args
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • Scriptwonder

Poem

🐇
I hopped through flags both near and far,
I sniffed for offline, checked each jar,
I passed a string where booleans fell,
So UVX can start or skip its spell—
A rabbit's hop to ship it well.

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding --offline flag to uvx launches to improve startup performance when cached.
Description check ✅ Passed The PR description covers all template sections with detailed implementation specifics, testing results, and changes made across multiple files.
Linked Issues check ✅ Passed The PR fully implements the objective from #760: adding offline mode to skip dependency checks on slow networks while preserving normal behavior for first-time installs and refreshes.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the offline probe and offline flag logic for uvx launches, plus necessary test updates.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into beta

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@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 1 issue, and left some high level feedback:

  • The devFlags selection logic (--no-cache --refresh vs --offline vs empty) is duplicated across ServerCommandBuilder, McpClientConfiguratorBase (multiple spots), and ConfigJsonBuilder—consider extracting a shared helper in AssetPathUtility (or similar) to keep the behavior consistent and easier to adjust.
  • ShouldUseUvxOffline() may be called multiple times in a session (e.g., config JSON, server command, registration, manual snippet), each time spawning a uvx process; consider memoizing the probe result per editor session or for a short interval to avoid repeated external calls and potential 3-second timeouts.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `devFlags` selection logic (`--no-cache --refresh` vs `--offline` vs empty) is duplicated across `ServerCommandBuilder`, `McpClientConfiguratorBase` (multiple spots), and `ConfigJsonBuilder`—consider extracting a shared helper in `AssetPathUtility` (or similar) to keep the behavior consistent and easier to adjust.
- `ShouldUseUvxOffline()` may be called multiple times in a session (e.g., config JSON, server command, registration, manual snippet), each time spawning a `uvx` process; consider memoizing the probe result per editor session or for a short interval to avoid repeated external calls and potential 3-second timeouts.

## Individual Comments

### Comment 1
<location> `MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs:795-801` </location>
<code_context>
             {
                 // Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
-                string devFlags = shouldForceRefresh ? "--no-cache --refresh " : string.Empty;
+                string devFlags;
+                if (shouldForceRefresh)
+                    devFlags = "--no-cache --refresh ";
+                else if (shouldUseOffline)
+                    devFlags = "--offline ";
+                else
+                    devFlags = string.Empty;
                 // Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
                 args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}";
</code_context>

<issue_to_address>
**suggestion:** Centralize `uvx` flag selection logic instead of duplicating `devFlags` branches.

This `devFlags` selection (between `--no-cache --refresh`, `--offline`, or nothing) now appears here, in `McpClientConfiguratorBase.Register()`, `ServerCommandBuilder.TryBuildCommand`, and in the `ConfigJsonBuilder.BuildUvxArgs` / `CodexConfigHelper.AddUvxModeFlags` pair. Consider extracting a shared helper (e.g., `AssetPathUtility.GetUvxDevFlags()` returning a string or string list) so flag precedence and behavior are defined in one place instead of being manually kept in sync across multiple branches.

Suggested implementation:

```csharp
                // Note: --reinstall is not supported by uvx, use --no-cache --refresh instead.
                // Use shared helper so uvx flag precedence is defined in one place.
                var devFlags = AssetPathUtility.GetUvxDevFlags(
                    forceRefresh: shouldForceRefresh,
                    useOffline: shouldUseOffline);
                // Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
                args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}";

```

```csharp
                var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
                // Use central helper that checks both DevModeForceServerRefresh AND local path detection.
                var devFlags = AssetPathUtility.GetUvxDevFlags(
                    forceRefresh: AssetPathUtility.ShouldForceUvxRefresh(),
                    useOffline: AssetPathUtility.ShouldUseUvxOffline());

```

To fully implement the review suggestion, you should also:

1. Add a shared helper to `AssetPathUtility` (e.g. in `MCPForUnity/Editor/Utilities/AssetPathUtility.cs` or wherever it lives):

   ```csharp
   public static string GetUvxDevFlags(bool forceRefresh, bool useOffline)
   {
       // Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
       if (forceRefresh)
           return "--no-cache --refresh ";
       if (useOffline)
           return "--offline ";
       return string.Empty;
   }
   ```

   If some call sites should infer `forceRefresh` / `useOffline` from global settings, you can optionally add a parameterless overload:
   ```csharp
   public static string GetUvxDevFlags()
       => GetUvxDevFlags(ShouldForceUvxRefresh(), ShouldUseUvxOffline());
   ```

2. Update other locations that currently duplicate the uvx flag logic (`ServerCommandBuilder.TryBuildCommand`, `ConfigJsonBuilder.BuildUvxArgs`, `CodexConfigHelper.AddUvxModeFlags`, and any others) to call `AssetPathUtility.GetUvxDevFlags(...)` instead of hard-coding the `--no-cache --refresh` / `--offline` branching.

3. Ensure all usages preserve the desired precedence (force-refresh over offline) by relying on the centralized helper rather than local if/else chains.

These changes will make uvx flag behavior consistent and easier to modify in one place.
</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.

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.

🧹 Nitpick comments (4)
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs (1)

24-36: Test coverage is minimal — consider adding a null-uvx-path scenario.

The existing tests verify the force-refresh guard and the no-throw guarantee, which are good baseline checks. However, neither test verifies the false return when GetUvxPath() returns null/empty (a common CI/test-environment condition). Since the PathResolverService is registered via MCPServiceLocator, you could register a mock that returns null to cover that branch without needing a real uvx binary.

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

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs`
around lines 24 - 36, Add a test in AssetPathUtilityOfflineTests that registers
a mock PathResolverService with MCPServiceLocator which returns null (or empty)
from GetUvxPath, ensure EditorPrefKeys.DevModeForceServerRefresh is false, then
assert AssetPathUtility.ShouldUseUvxOffline() returns false to cover the
null-uvx-path branch; use the existing test harness patterns for registering
services in MCPServiceLocator so the test cleans up after itself.
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (2)

459-462: Verify that GetBetaServerFromArgs (EditorPrefs access) is safe here.

GetBetaServerFromArgs() at Line 459 reads EditorPrefs and must be called from the main Unity thread. This is currently true for all direct callers. However, in McpClientConfigSection.ConfigureClaudeCliAsync, the result of ShouldUseUvxOffline() is captured before Task.Run (Line 308), which is correct. Just ensure no future caller invokes this from a background thread.

The method's XML doc should note the main-thread requirement, consistent with how GetBetaServerFromArgs() documents this constraint.

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

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs` around lines 459 - 462,
GetBetaServerFromArgs reads EditorPrefs and must only be called from Unity's
main thread; update the XML doc on GetBetaServerFromArgs to explicitly state the
main-thread requirement, and add a brief XML note on ShouldUseUvxOffline (and/or
the AssetPathUtility method that calls it) indicating callers must invoke it on
the main thread (or capture its result before spawning background tasks). Also
verify McpClientConfigSection.ConfigureClaudeCliAsync already captures
ShouldUseUvxOffline before Task.Run and add a comment referencing
ConfigureClaudeCliAsync to remind future maintainers to preserve that pattern.

448-470: Multiple callers may spawn redundant probe processes.

ShouldUseUvxOffline() spawns a subprocess with a 3-second timeout. It's called independently from ConfigJsonBuilder.BuildUvxArgs, ServerCommandBuilder.TryBuildCommand, CodexConfigHelper.AddUvxModeFlags (×2), McpClientConfiguratorBase.Register(), and GetManualSnippet(). In flows like "Configure All Clients", several of these paths may execute in quick succession, each spawning a fresh probe process.

Consider caching the probe result for a short duration (e.g., 30–60 seconds) so that repeated calls within the same configuration flow reuse the first result instead of spawning a new process each time.

💡 Example: lightweight time-based cache
+        private static bool _cachedOfflineResult;
+        private static DateTime _cachedOfflineTimestamp = DateTime.MinValue;
+        private static readonly TimeSpan OfflineCacheDuration = TimeSpan.FromSeconds(30);
+
         public static bool ShouldUseUvxOffline()
         {
             if (ShouldForceUvxRefresh())
                 return false;
 
+            if ((DateTime.UtcNow - _cachedOfflineTimestamp) < OfflineCacheDuration)
+                return _cachedOfflineResult;
+
             try
             {
                 string uvxPath = MCPServiceLocator.Paths.GetUvxPath();
                 if (string.IsNullOrEmpty(uvxPath))
                     return false;
 
                 string fromArgs = GetBetaServerFromArgs(quoteFromPath: false);
                 string probeArgs = string.IsNullOrEmpty(fromArgs)
                     ? "--offline mcp-for-unity --help"
                     : $"--offline {fromArgs} mcp-for-unity --help";
 
-                return ExecPath.TryRun(uvxPath, probeArgs, null, out _, out _, timeoutMs: 3000);
+                _cachedOfflineResult = ExecPath.TryRun(uvxPath, probeArgs, null, out _, out _, timeoutMs: 3000);
+                _cachedOfflineTimestamp = DateTime.UtcNow;
+                return _cachedOfflineResult;
             }
             catch
             {
                 return false;
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs` around lines 448 - 470,
ShouldUseUvxOffline currently launches a short-lived probe process each call,
causing redundant probes when multiple callers (ConfigJsonBuilder.BuildUvxArgs,
ServerCommandBuilder.TryBuildCommand, CodexConfigHelper.AddUvxModeFlags,
McpClientConfiguratorBase.Register, GetManualSnippet) call it; add a lightweight
time-based cache inside AssetPathUtility to store the last probe result and
timestamp (e.g., 30–60s) and return the cached boolean if still fresh. Implement
cache invalidation when ShouldForceUvxRefresh() returns true or when
MCPServiceLocator.Paths.GetUvxPath() returns a different path than the one used
for the cached probe. Ensure the cache is stored in static fields and access is
thread-safe (lock or Interlocked) so concurrent callers reuse the same probe
result instead of spawning new subprocesses.
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs (1)

996-1006: --offline in user-facing manual snippet may confuse users who copy it to a cold-cache machine.

GetManualSnippet() bakes the current offline probe result into the displayed CLI command. If a user copies this snippet to a different machine (or after clearing the uvx cache), the --offline flag would cause a failure. Consider appending a brief comment in the snippet noting that --offline is auto-detected and may need removal.

This is a minor UX concern — not a blocker.

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

In `@MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs` around lines 996 -
1006, The manual snippet produced by GetManualSnippet currently bakes the
runtime-detected "--offline" into devFlags which can break when pasted to a
cold-cache machine; update GetManualSnippet so that when devFlags contains
"--offline " it appends a short user-facing comment to the returned string (near
the line combining uvxPath, devFlags and fromArgs) such as "(auto-detected
--offline; remove if running on a fresh machine)" instead of silently embedding
the flag, keeping all other behavior the same and referencing uvxPath, devFlags
and fromArgs when composing the final string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs`:
- Around line 996-1006: The manual snippet produced by GetManualSnippet
currently bakes the runtime-detected "--offline" into devFlags which can break
when pasted to a cold-cache machine; update GetManualSnippet so that when
devFlags contains "--offline " it appends a short user-facing comment to the
returned string (near the line combining uvxPath, devFlags and fromArgs) such as
"(auto-detected --offline; remove if running on a fresh machine)" instead of
silently embedding the flag, keeping all other behavior the same and referencing
uvxPath, devFlags and fromArgs when composing the final string.

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 459-462: GetBetaServerFromArgs reads EditorPrefs and must only be
called from Unity's main thread; update the XML doc on GetBetaServerFromArgs to
explicitly state the main-thread requirement, and add a brief XML note on
ShouldUseUvxOffline (and/or the AssetPathUtility method that calls it)
indicating callers must invoke it on the main thread (or capture its result
before spawning background tasks). Also verify
McpClientConfigSection.ConfigureClaudeCliAsync already captures
ShouldUseUvxOffline before Task.Run and add a comment referencing
ConfigureClaudeCliAsync to remind future maintainers to preserve that pattern.
- Around line 448-470: ShouldUseUvxOffline currently launches a short-lived
probe process each call, causing redundant probes when multiple callers
(ConfigJsonBuilder.BuildUvxArgs, ServerCommandBuilder.TryBuildCommand,
CodexConfigHelper.AddUvxModeFlags, McpClientConfiguratorBase.Register,
GetManualSnippet) call it; add a lightweight time-based cache inside
AssetPathUtility to store the last probe result and timestamp (e.g., 30–60s) and
return the cached boolean if still fresh. Implement cache invalidation when
ShouldForceUvxRefresh() returns true or when
MCPServiceLocator.Paths.GetUvxPath() returns a different path than the one used
for the cached probe. Ensure the cache is stored in static fields and access is
thread-safe (lock or Interlocked) so concurrent callers reuse the same probe
result instead of spawning new subprocesses.

In
`@TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs`:
- Around line 24-36: Add a test in AssetPathUtilityOfflineTests that registers a
mock PathResolverService with MCPServiceLocator which returns null (or empty)
from GetUvxPath, ensure EditorPrefKeys.DevModeForceServerRefresh is false, then
assert AssetPathUtility.ShouldUseUvxOffline() returns false to cover the
null-uvx-path branch; use the existing test harness patterns for registering
services in MCPServiceLocator so the test cleans up after itself.

Extract GetUvxDevFlags() and GetUvxDevFlagsList() helpers to
AssetPathUtility, replacing duplicated if/else if/else blocks
across 6 call sites. Add 30-second TTL cache to ShouldUseUvxOffline()
to avoid redundant subprocess spawns. Simplify
ConfigureWithCapturedValues signature from two bools to a single
pre-captured flags string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

🧹 Nitpick comments (2)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (2)

466-468: Consider declaring cache fields closer to the method that uses them, or grouping at class top.

The static fields _offlineCacheResult, _offlineCacheTimestamp, and OfflineCacheTtlSeconds are declared after ShouldUseUvxOffline() and RunOfflineProbe() that use them. This is valid C# but placing state after its usage can reduce readability. A minor nit — grouping them just above ShouldUseUvxOffline() or at the top of the class would be more conventional.

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

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs` around lines 466 - 468, Move
the static cache fields so their declarations are colocated with the methods
that use them: relocate _offlineCacheResult, _offlineCacheTimestamp, and
OfflineCacheTtlSeconds to be either grouped at the top of the class with other
static state or directly above the ShouldUseUvxOffline() and RunOfflineProbe()
methods; update any existing comments and ensure their accessibility (static)
and initializers remain unchanged so the methods (ShouldUseUvxOffline,
RunOfflineProbe) continue to reference them without code changes.

442-489: Well-designed offline probe with safe fallback behavior.

The cache-probe approach with TTL is solid. A few observations:

  1. Main-thread blocking: RunOfflineProbe() can block the Unity editor main thread for up to 3 seconds on a cold probe. With the 30s TTL this should be rare, but it could cause a noticeable hitch when the cache expires and uvx is slow to respond. Consider whether this could eventually move to a background task that pre-warms the cache.

  2. ShouldForceUvxRefresh() is evaluated twice when called via GetUvxDevFlags() or GetUvxDevFlagsList() — once at the call site and again inside ShouldUseUvxOffline() (Line 453). Not a bug (it's a cheap EditorPrefs read), but you could avoid it by inlining the check:

♻️ Optional: Avoid redundant ShouldForceUvxRefresh() calls in list variant
 public static IReadOnlyList<string> GetUvxDevFlagsList()
 {
-    if (ShouldForceUvxRefresh()) return new[] { "--no-cache", "--refresh" };
-    if (ShouldUseUvxOffline()) return new[] { "--offline" };
-    return Array.Empty<string>();
+    bool forceRefresh = ShouldForceUvxRefresh();
+    if (forceRefresh) return new[] { "--no-cache", "--refresh" };
+    if (!forceRefresh && ShouldUseUvxOfflineSkipRefreshCheck()) return new[] { "--offline" };
+    return Array.Empty<string>();
 }

That said, the current implementation is correct and the overhead is negligible.

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

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs` around lines 442 - 489, The
issue is redundant calls to ShouldForceUvxRefresh() (once at the caller and
again inside ShouldUseUvxOffline()), so update the API to avoid duplicate
EditorPrefs reads by adding an overload or parameter to
ShouldUseUvxOffline(e.g., ShouldUseUvxOffline(bool skipForceCheck) or
ShouldUseUvxOffline(bool forceRefreshFlag) ) and change callers like
GetUvxDevFlags()/GetUvxDevFlagsList() to compute ShouldForceUvxRefresh() once
and pass the result into the new overload; keep existing behavior when caller
doesn’t pass the flag by having the parameter default to false or calling the
original path that invokes ShouldForceUvxRefresh() internally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 466-468: Move the static cache fields so their declarations are
colocated with the methods that use them: relocate _offlineCacheResult,
_offlineCacheTimestamp, and OfflineCacheTtlSeconds to be either grouped at the
top of the class with other static state or directly above the
ShouldUseUvxOffline() and RunOfflineProbe() methods; update any existing
comments and ensure their accessibility (static) and initializers remain
unchanged so the methods (ShouldUseUvxOffline, RunOfflineProbe) continue to
reference them without code changes.
- Around line 442-489: The issue is redundant calls to ShouldForceUvxRefresh()
(once at the caller and again inside ShouldUseUvxOffline()), so update the API
to avoid duplicate EditorPrefs reads by adding an overload or parameter to
ShouldUseUvxOffline(e.g., ShouldUseUvxOffline(bool skipForceCheck) or
ShouldUseUvxOffline(bool forceRefreshFlag) ) and change callers like
GetUvxDevFlags()/GetUvxDevFlagsList() to compute ShouldForceUvxRefresh() once
and pass the result into the new overload; keep existing behavior when caller
doesn’t pass the flag by having the parameter default to false or calling the
original path that invokes ShouldForceUvxRefresh() internally.

…resh calls

Address CodeRabbit nitpicks: relocate static cache fields above the
methods that use them for readability, extract GetCachedOfflineProbeResult()
so GetUvxDevFlags()/GetUvxDevFlagsList() evaluate ShouldForceUvxRefresh()
only once per call instead of twice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

🧹 Nitpick comments (1)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (1)

511-516: Trailing spaces in returned flag strings are fragile.

GetUvxDevFlags(bool, bool) returns strings with trailing spaces ("--no-cache --refresh ", "--offline "). This works when callers always concatenate more content, but breaks if the result is used as a standalone value (e.g., trimmed comparisons in tests, or as the final segment of a command). Consider returning without trailing spaces and letting callers handle joining, or at minimum add a doc note that the trailing space is intentional.

Suggested approach
-        public static string GetUvxDevFlags(bool forceRefresh, bool useOffline)
-        {
-            if (forceRefresh) return "--no-cache --refresh ";
-            if (useOffline) return "--offline ";
-            return string.Empty;
-        }
+        public static string GetUvxDevFlags(bool forceRefresh, bool useOffline)
+        {
+            if (forceRefresh) return "--no-cache --refresh";
+            if (useOffline) return "--offline";
+            return string.Empty;
+        }

Callers that concatenate would use e.g. $"{devFlags} {remainingArgs}".TrimStart() or check for empty before inserting a space.

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

In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs` around lines 511 - 516,
GetUvxDevFlags(bool forceRefresh, bool useOffline) currently returns flag
strings with trailing spaces which is fragile; change the method to return flags
without trailing spaces (e.g. "--no-cache --refresh" and "--offline") and update
callers to handle spacing when concatenating (e.g., insert a single space only
when the returned string is non-empty or use string.Join/Trim when building
command arguments); keep the method signature and name GetUvxDevFlags unchanged
and add a brief comment that callers are responsible for joining with
surrounding arguments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 511-516: GetUvxDevFlags(bool forceRefresh, bool useOffline)
currently returns flag strings with trailing spaces which is fragile; change the
method to return flags without trailing spaces (e.g. "--no-cache --refresh" and
"--offline") and update callers to handle spacing when concatenating (e.g.,
insert a single space only when the returned string is non-empty or use
string.Join/Trim when building command arguments); keep the method signature and
name GetUvxDevFlags unchanged and add a brief comment that callers are
responsible for joining with surrounding arguments.

When HTTP transport is enabled, skip Configure() for clients that
do not support HTTP (e.g., Claude Desktop). Previously this threw
and logged a spurious warning on every editor launch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

🧹 Nitpick comments (1)
MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs (1)

77-81: Guard logic looks correct.

This properly prevents the migration from attempting to configure clients that don't support the active HTTP transport, avoiding the exception path noted in the comment.

One minor nit: EditorConfigurationCache.Instance.UseHttpTransport is loop-invariant — consider hoisting it above the foreach to make that clear.

,

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

In `@MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs` around lines 77 - 81,
Hoist the loop-invariant lookup of
EditorConfigurationCache.Instance.UseHttpTransport out of the foreach: retrieve
it once into a local bool (instead of calling
EditorConfigurationCache.Instance.UseHttpTransport repeatedly) before iterating
over the configurators in StdIoVersionMigration, then use that local (previously
named useHttp) inside the loop to check
configurator.Client.SupportsHttpTransport; this clarifies intent and avoids
redundant property access.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs`:
- Around line 77-81: Hoist the loop-invariant lookup of
EditorConfigurationCache.Instance.UseHttpTransport out of the foreach: retrieve
it once into a local bool (instead of calling
EditorConfigurationCache.Instance.UseHttpTransport repeatedly) before iterating
over the configurators in StdIoVersionMigration, then use that local (previously
named useHttp) inside the loop to check
configurator.Client.SupportsHttpTransport; this clarifies intent and avoids
redundant property access.

@dsarno dsarno merged commit 3613fee into CoplayDev:beta Feb 17, 2026
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.

add a force offline option to make uvx start service skip dependency check

1 participant