fix: Claude Code registration, thread-safety, and auto-detect beta server (#664)#667
Conversation
Updated the Git URL for adding the package to include the branch name.
…fig conflicts (CoplayDev#664) - Add RemoveFromAllScopes helper to remove from local/user/project scopes - Use explicit --scope local when registering - Update manual snippets to show multi-scope cleanup - Handle legacy 'unityMCP' naming in all scopes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The background thread status check was accessing main-thread-only Unity APIs (Application.platform, EditorPrefs via HttpEndpointUtility and AssetPathUtility), causing "GetString can only be called from main thread" errors. Now all main-thread-only values are captured before Task.Run() and passed as parameters to CheckStatusWithProjectDir(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remember last selected client in EditorPrefs so it restores on window reopen (prevents hiding config issues by defaulting to first client) - Remove dead Outbound class, _outbox BlockingCollection, and writer thread that was never used (nothing ever enqueued to outbox) - Keep only failure IO logs, remove verbose success logging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reviewer's GuideRefactors Claude CLI status checking to be fully thread-safe by capturing main-thread-only values up front, tightens Claude MCP registration/unregistration to use local scope and clear all scopes to avoid stale configs, simplifies stdio transport logging, and adds UX improvements like persisting the last selected client plus a small README URL fix. Sequence diagram for thread-safe Claude CLI status check with captured main-thread valuessequenceDiagram
actor User
participant UnityEditor as UnityEditorWindow
participant McpClientConfigSection
participant ClaudeCliMcpConfigurator
participant BackgroundTask as Task_CheckStatus
User->>UnityEditor: Open MCP Client Config window
UnityEditor->>McpClientConfigSection: InitializeUI()
McpClientConfigSection->>McpClientConfigSection: RestoreSelectedClient()
User->>UnityEditor: Click Refresh Status
UnityEditor->>McpClientConfigSection: RefreshClaudeCliStatus(client, forceImmediate)
rect rgb(235,235,255)
note over McpClientConfigSection: Capture main-thread-only values
McpClientConfigSection->>McpClientConfigSection: projectDir = Path.GetDirectoryName(Application.dataPath)
McpClientConfigSection->>McpClientConfigSection: useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport
McpClientConfigSection->>McpClientConfigSection: claudePath = MCPServiceLocator.Paths.GetClaudeCliPath()
McpClientConfigSection->>McpClientConfigSection: platform = Application.platform
McpClientConfigSection->>McpClientConfigSection: isRemoteScope = HttpEndpointUtility.IsRemoteScope()
McpClientConfigSection->>McpClientConfigSection: expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource()
end
McpClientConfigSection->>BackgroundTask: Task.Run(CheckStatusWithProjectDir(...captured values..., attemptAutoRewrite=false))
activate BackgroundTask
BackgroundTask->>ClaudeCliMcpConfigurator: CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, expectedPackageSource, attemptAutoRewrite=false)
ClaudeCliMcpConfigurator->>ClaudeCliMcpConfigurator: Determine pathPrepend from platform
ClaudeCliMcpConfigurator->>ClaudeCliMcpConfigurator: Run claude mcp list / status
alt HTTP transport
ClaudeCliMcpConfigurator->>ClaudeCliMcpConfigurator: configuredTransport = isRemoteScope ? HttpRemote : Http
else STDIO transport
ClaudeCliMcpConfigurator->>ClaudeCliMcpConfigurator: ExtractPackageSourceFromCliOutput(stdout)
ClaudeCliMcpConfigurator->>ClaudeCliMcpConfigurator: Compare with expectedPackageSource
end
ClaudeCliMcpConfigurator-->>BackgroundTask: McpStatus
BackgroundTask-->>McpClientConfigSection: ContinueWith(update UI on main thread)
deactivate BackgroundTask
McpClientConfigSection->>McpClientConfigSection: UpdateClientStatus()
McpClientConfigSection->>UnityEditor: Refresh status label and controls
Class diagram for updated MCP client configurator, config UI, and stdio hostclassDiagram
class McpClientConfiguratorBase {
+McpStatus CheckStatus(bool attemptAutoRewrite)
+string GetManualSnippet()
+void Register()
+void Unregister()
-void RegisterWithCapturedValues(string projectDir, string claudePath, bool useHttpTransport, Models.ConfiguredTransport serverTransport, string httpUrl, string uvxPath, string gitUrl, string packageName, string pathPrepend, bool shouldForceRefresh)
-void UnregisterWithCapturedValues(string projectDir, string claudePath, string pathPrepend)
-static void RemoveFromAllScopes(string claudePath, string projectDir, string pathPrepend)
-string SanitizeShellHeaderValue(string value)
-string ExtractPackageSourceFromCliOutput(string stdout)
-void ValidateClaudeCli()
-void Configure()
-void ConfigureInternal()
-void ApplyConfiguration()
-void ValidateProjectDir(string projectDir)
-void UpdateClientStatus(McpStatus status)
-void LogStatusChange(McpStatus status)
-void HandleConfigurationError(Exception ex)
-void SetConfiguredTransport(Models.ConfiguredTransport transport)
-void UpdateEditorPrefs()
-void RefreshClient()
-void ShowStatusNotification(McpStatus status)
-void ShowErrorDialog(string message)
-void ShowInfoDialog(string message)
-void ShowWarningDialog(string message)
-void LogInfo(string message)
-void LogWarning(string message)
-void LogError(string message)
-void EnsureClaudeCliPath()
-void EnsureProjectDir()
-void EnsureTransportMode()
-void EnsureHttpEndpoint()
-void EnsureStdioEndpoint()
-void EnsureApiKey()
-void EnsureUvxPath()
-void EnsureGitUrl()
-void EnsurePackageName()
-void EnsurePathPrepend(RuntimePlatform platform)
-void EnsureExpectedPackageSource()
-bool IsRegistrationRequired()
-bool IsUnregistrationRequired()
-bool ShouldForceRefresh()
-bool IsRemoteScope()
-bool IsHttpTransportEnabled()
-bool IsStdioTransportEnabled()
-bool IsConfigured()
-bool IsError()
-bool IsNotConfigured()
-bool IsConfigOutOfDate()
-bool IsConfigMismatch()
-bool IsTransportMismatch()
-bool IsPackageVersionMismatch()
-bool HasValidClaudeCliPath()
-bool HasValidProjectDir()
-bool HasValidTransportMode()
-bool HasValidHttpEndpoint()
-bool HasValidStdioEndpoint()
-bool HasValidApiKey()
-bool HasValidUvxPath()
-bool HasValidGitUrl()
-bool HasValidPackageName()
-bool HasValidPathPrepend()
-bool HasValidExpectedPackageSource()
-bool TryParseStatusFromOutput(string stdout)
-bool TryExtractTransportInfo(string stdout)
-bool TryExtractPackageInfo(string stdout)
-bool TryExtractScopeInfo(string stdout)
-bool TryExtractErrorInfo(string stderr)
-bool TryRunClaudeCli(string args, string projectDir, out string stdout, out string stderr, int timeoutMs, string pathPrepend)
-bool TryRunRegistration(string args, string projectDir, out string stdout, out string stderr, int timeoutMs, string pathPrepend)
-bool TryRunUnregistration(string args, string projectDir, out string stdout, out string stderr, int timeoutMs, string pathPrepend)
-bool TryRunList(string projectDir, out string stdout, out string stderr, int timeoutMs, string pathPrepend)
-McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTransport, string claudePath, RuntimePlatform platform, bool isRemoteScope, string expectedPackageSource, bool attemptAutoRewrite)
}
class McpClientConfigSection {
-List~IMcpClientConfigurator~ configurators
-int selectedClientIndex
-DropdownField clientDropdown
-VisualElement root
-Label statusLabel
-Button registerButton
-Button unregisterButton
-Button refreshStatusButton
-VisualElement claudeCliPathRow
-TextField claudeCliPathField
-Toggle useHttpTransportToggle
-Label manualConfigLabel
-TextField manualConfigField
-Button copyManualConfigButton
-Label httpEndpointLabel
-Label stdioEndpointLabel
-Label transportModeLabel
-Label apiKeyLabel
-Label uvxPathLabel
-Label gitUrlLabel
-Label packageNameLabel
-Label pathPrependLabel
-Label expectedPackageSourceLabel
-Label logOutputLabel
-ScrollView logScrollView
-Button openLogsButton
-Button clearLogsButton
-Button helpButton
-Button docsButton
-Button reportIssueButton
-Button openSettingsButton
-Button resetConfigButton
-Button openProjectFolderButton
-Button openClaudeCliFolderButton
-Button openUnityPreferencesButton
-Button openPackageManagerButton
-void InitializeUI()
-void RegisterCallbacks()
-void UpdateClientStatus()
-void UpdateManualConfiguration()
-void UpdateClaudeCliPathVisibility()
-void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImmediate)
-void RefreshUiFromConfig()
-void OnTransportModeChanged()
-void OnApiKeyChanged()
-void OnClaudeCliPathChanged()
-void OnRegisterClicked()
-void OnUnregisterClicked()
-void OnRefreshStatusClicked()
-void OnHelpClicked()
-void OnDocsClicked()
-void OnReportIssueClicked()
-void OnOpenSettingsClicked()
-void OnResetConfigClicked()
-void OnOpenProjectFolderClicked()
-void OnOpenClaudeCliFolderClicked()
-void OnOpenUnityPreferencesClicked()
-void OnOpenPackageManagerClicked()
-void AppendLog(string message)
-void ClearLog()
-void LoadConfigurators()
-void SaveSelectedClient()
-void RestoreSelectedClient()
}
class StdioBridgeHost {
<<static>>
-int mainThreadId
-object startStopLock
-object clientsLock
-HashSet~TcpClient~ activeClients
-CancellationTokenSource cts
-Task listenerTask
-int processingCommands
-int pendingCommands
-int nextCommandId
-Dictionary~int,QueuedCommand~ pendingCommandMap
-Dictionary~int,QueuedCommand~ inFlightCommands
-Dictionary~string,QueuedCommand~ notificationHandlers
-IPEndPoint listenEndpoint
-TcpListener tcpListener
-bool isShuttingDown
-bool allowBatchMode
-ulong MaxFrameBytes
-int FrameIOTimeoutMs
-void Start()
-void Stop()
-void StopInternal()
-void StartListenerLoop()
-Task ListenAsync(CancellationToken token)
-Task HandleClientAsync(TcpClient client, CancellationToken token)
-Task<string> ReadFrameAsync(NetworkStream stream)
-Task WriteFrameAsync(NetworkStream stream, byte[] payload)
-void IoInfo(string message)
-bool IsDebugEnabled()
-void LogDebug(string message)
-void LogError(string message)
-void LogInfo(string message)
-void OnCommandReceived(string json)
-void OnNotificationReceived(string json)
-void OnResponseReceived(string json)
-void DispatchCommand(QueuedCommand cmd)
-void CompleteCommand(int id, string response)
-void FailCommand(int id, string error)
-void RegisterNotificationHandler(string method, QueuedCommand handler)
-void UnregisterNotificationHandler(string method)
-bool FolderExists(string path)
}
McpClientConfigSection --> McpClientConfiguratorBase : uses
StdioBridgeHost --> McpClientConfiguratorBase : transports for
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughThread-safety and scope-awareness added to Claude CLI status checks and registration/unregistration flows; cross-scope cleanup introduced; UI persists last-selected client; stdio transport per-frame IO logging and writer thread removed; beta/prerelease handling centralized and CI release workflows updated. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as "Editor UI"
participant FS as "Project Dir / EditorPrefs"
participant Config as "ClaudeCliMcpConfigurator"
participant CLI as "Claude CLI / Package Manager"
UI->>FS: capture platform, isRemoteScope, expectedPackageSource
UI->>Config: CheckStatus(projectDir, useHttp, claudePath, platform, isRemoteScope, expectedPackageSource)
Config->>CLI: run CLI status check (background thread) with captured args
CLI-->>Config: status/result or exception
Config-->>UI: marshal and update status on main thread
UI->>FS: persist LastSelectedClientId (on selection change)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The manual snippets for unregistering (
GetManualSnippet) now showclaude mcp removeforUnityMCPonly, but the code inRemoveFromAllScopesalso cleans up the legacyunityMCPname; consider updating the snippet or adding a note so users can manually remove legacy registrations as well. - The scope strings (
local,user,project) are hard-coded both inRemoveFromAllScopesand in the manual snippet; centralizing these as shared constants or a single helper will reduce the chance of future drift or typos between behavior and documentation.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The manual snippets for unregistering (`GetManualSnippet`) now show `claude mcp remove` for `UnityMCP` only, but the code in `RemoveFromAllScopes` also cleans up the legacy `unityMCP` name; consider updating the snippet or adding a note so users can manually remove legacy registrations as well.
- The scope strings (`local`, `user`, `project`) are hard-coded both in `RemoveFromAllScopes` and in the manual snippet; centralizing these as shared constants or a single helper will reduce the chance of future drift or typos between behavior and documentation.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…ates - Add IsPreReleaseVersion() helper to detect beta/alpha/rc package versions - UseBetaServer now defaults to true only for prerelease package versions - Main branch users get false default, beta branch users get true default - Update beta-release.yml to set Unity package version with -beta.1 suffix - Update release.yml to merge beta → main and strip beta suffix - Fix CodexConfigHelperTests to explicitly set UseBetaServer for determinism - Use EditorConfigurationCache consistently for UseBetaServer access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 265-266: GetBetaServerFromArgs() currently reads
EditorConfigurationCache.Instance.UseBetaServer from a background thread which
may trigger EditorConfigurationCache.Refresh() and call EditorPrefs.GetBool()
(main-thread-only). Change the call site to capture useBetaServer on the main
thread (like expectedPackageSource in CheckStatusWithProjectDir()) and modify
GetBetaServerFromArgs() signature (or its helper) to accept a bool useBetaServer
parameter, then use that passed value instead of accessing
EditorConfigurationCache.Instance inside the background task; alternatively
ensure EditorConfigurationCache is initialized on the main thread before
spawning the Task.Run().
🧹 Nitpick comments (3)
MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs (1)
186-191: Consider using the cache setter for consistency.While the reads at lines 141 and 295 now use
EditorConfigurationCache.Instance.UseBetaServer, the write in the toggle callback still writes directly toEditorPrefs:EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, evt.newValue);This bypasses
EditorConfigurationCache.Instance.SetUseBetaServer(value), which would update both the cache andEditorPrefsatomically. Until the cache is refreshed or the key is invalidated, subsequent reads from the cache may return stale values.♻️ Suggested refactor to use the cache setter
useBetaServerToggle.RegisterValueChangedCallback(evt => { - EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, evt.newValue); + EditorConfigurationCache.Instance.SetUseBetaServer(evt.newValue); OnHttpServerCommandUpdateRequested?.Invoke(); OnBetaModeChanged?.Invoke(evt.newValue); });.github/workflows/beta-release.yml (1)
30-57: Add semver validation before arithmetic.
IfBASE_VERSIONisn’tX.Y.Z, the minor bump can break; consider validating like the PyPI step.♻️ Suggested guard
BASE_VERSION=$(echo "$CURRENT_VERSION" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//') + if ! [[ "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Could not parse version '$CURRENT_VERSION' -> '$BASE_VERSION'" >&2 + exit 1 + fi.github/workflows/release.yml (1)
66-84: Guard against non-semver after stripping prerelease.
If the suffix isn’t matched, the next version bump can fail; consider validatingSTABLE_VERSIONand failing fast.♻️ Suggested guard
STABLE_VERSION=$(echo "$CURRENT_VERSION" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//') + if ! [[ "$STABLE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Could not parse version '$CURRENT_VERSION' -> '$STABLE_VERSION'" >&2 + exit 1 + fi
- Add thread-safe overloads for GetBetaServerFromArgs/List that accept pre-captured useBetaServer and gitUrlOverride parameters - Use EditorConfigurationCache.SetUseBetaServer() in McpAdvancedSection for atomic cache + EditorPrefs update - Add semver validation guard in beta-release.yml before version arithmetic - Add semver validation guard in release.yml after stripping prerelease suffix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Helpers/AssetPathUtility.cs (1)
288-295:⚠️ Potential issue | 🟠 MajorThread-safe overload contract violated: parameterized versions still call unsafe main-thread-only APIs.
The parameterized
GetBetaServerFromArgs(useBetaServer, gitUrlOverride, quoteFromPath)andGetBetaServerFromArgsList(useBetaServer, gitUrlOverride)overloads are designed to accept pre-captured values, suggesting they are safe for off-main-thread usage. However, both still callGetMcpServerPackageSource()at lines 289 and 342, which is unsafe: it readsEditorPrefs(main-thread only) and callsGetPackageVersion()→PackageInfo.FindForAssembly()(UnityEditor API, main-thread only).If these parameterized overloads are invoked from a background thread, they will crash with "can only be called from the main thread" exceptions.
Fix: Add
string packageSourceparameter to the parameterized overloads, pre-captureGetMcpServerPackageSource()in the non-parameterized wrappers on the main thread, and pass it through. This ensures all unsafe calls remain on the main thread.🔧 Proposed fix (introduce pre-captured packageSource)
- public static string GetBetaServerFromArgs(bool quoteFromPath = false) + public static string GetBetaServerFromArgs(bool quoteFromPath = false) { // Read values from cache/EditorPrefs on main thread bool useBetaServer = Services.EditorConfigurationCache.Instance.UseBetaServer; string gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); + string packageSource = GetMcpServerPackageSource(); - return GetBetaServerFromArgs(useBetaServer, gitUrlOverride, quoteFromPath); + return GetBetaServerFromArgs(useBetaServer, gitUrlOverride, packageSource, quoteFromPath); } - public static string GetBetaServerFromArgs(bool useBetaServer, string gitUrlOverride, bool quoteFromPath = false) + public static string GetBetaServerFromArgs(bool useBetaServer, string gitUrlOverride, string packageSource, bool quoteFromPath = false) { // Explicit override (local path, git URL, etc.) always wins if (!string.IsNullOrEmpty(gitUrlOverride)) { return $"--from {gitUrlOverride}"; } // Beta server mode: use prerelease from PyPI if (useBetaServer) { string fromValue = quoteFromPath ? "\"mcpforunityserver>=0.0.0a0\"" : "mcpforunityserver>=0.0.0a0"; return $"--prerelease explicit --from {fromValue}"; } // Standard mode: use pinned version from package.json - string fromUrl = GetMcpServerPackageSource(); + string fromUrl = packageSource; if (!string.IsNullOrEmpty(fromUrl)) { return $"--from {fromUrl}"; } return string.Empty; } - public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList() + public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList() { // Read values from cache/EditorPrefs on main thread bool useBetaServer = Services.EditorConfigurationCache.Instance.UseBetaServer; string gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); + string packageSource = GetMcpServerPackageSource(); - return GetBetaServerFromArgsList(useBetaServer, gitUrlOverride); + return GetBetaServerFromArgsList(useBetaServer, gitUrlOverride, packageSource); } - public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList(bool useBetaServer, string gitUrlOverride) + public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList(bool useBetaServer, string gitUrlOverride, string packageSource) { var args = new System.Collections.Generic.List<string>(); // Explicit override (local path, git URL, etc.) always wins if (!string.IsNullOrEmpty(gitUrlOverride)) { args.Add("--from"); args.Add(gitUrlOverride); return args; } // Beta server mode: use prerelease from PyPI if (useBetaServer) { args.Add("--prerelease"); args.Add("explicit"); args.Add("--from"); args.Add("mcpforunityserver>=0.0.0a0"); return args; } // Standard mode: use pinned version from package.json - string fromUrl = GetMcpServerPackageSource(); + string fromUrl = packageSource; if (!string.IsNullOrEmpty(fromUrl)) { args.Add("--from"); args.Add(fromUrl); } return args; }Also applies to: 342–347
🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Helpers/AssetPathUtility.cs`:
- Around line 273-277: The explicit override branch returns an unquoted path
which breaks for local paths with spaces; update the branch that checks
gitUrlOverride to pass gitUrlOverride through the existing quoteFromPath helper
(i.e., use quoteFromPath(gitUrlOverride) when building the "--from" argument) so
the generated command string is correctly quoted; locate the gitUrlOverride
check in the same method and replace the raw interpolation with the quoted form
using the quoteFromPath function.
- Add packageSource parameter to thread-safe overloads to avoid calling GetMcpServerPackageSource() (which uses EditorPrefs) from background threads - Apply quoteFromPath logic to gitUrlOverride and packageSource paths to handle local paths with spaces correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…Unity discovery The auto-select tests were failing because they only patched PluginHub but not the fallback legacy connection pool discovery. When PluginHub returns no results, the middleware falls back to discovering instances via get_unity_connection_pool(), which found the real running Unity. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove ExclusiveAddressUse=false to prevent duplicate listeners binding to the same port after Domain Reload - Increase port binding retry from 3x75ms to 10x200ms to allow old socket cleanup during Domain Reload Based on beta branch (post-PR CoplayDev#682) which includes: - CheckStatus speed-up (read ~/.claude.json directly) - Thread-safety fixes (PR CoplayDev#667) - Beta mode validation (PR CoplayDev#671) Fixes CoplayDev#664 Related: CoplayDev#643 Co-Authored-By: Claude <noreply@anthropic.com>
- Remove ExclusiveAddressUse=false to prevent duplicate listeners binding to the same port after Domain Reload - Increase port binding retry from 3x75ms to 10x200ms to allow old socket cleanup during Domain Reload Based on beta branch (post-PR CoplayDev#682) which includes: - CheckStatus speed-up (read ~/.claude.json directly) - Thread-safety fixes (PR CoplayDev#667) - Beta mode validation (PR CoplayDev#671) Fixes CoplayDev#664 Related: CoplayDev#643 Co-Authored-By: Claude <noreply@anthropic.com>
- Remove ExclusiveAddressUse=false to prevent duplicate listeners binding to the same port after Domain Reload - Increase port binding retry from 3x75ms to 10x200ms to allow old socket cleanup during Domain Reload Based on beta branch (post-PR CoplayDev#682) which includes: - CheckStatus speed-up (read ~/.claude.json directly) - Thread-safety fixes (PR CoplayDev#667) - Beta mode validation (PR CoplayDev#671) Fixes CoplayDev#664 Related: CoplayDev#643 Co-Authored-By: Claude <noreply@anthropic.com>
- Remove ExclusiveAddressUse=false to prevent duplicate listeners binding to the same port after Domain Reload - Increase port binding retry from 3x75ms to 10x200ms to allow old socket cleanup during Domain Reload Based on beta branch (post-PR CoplayDev#682) which includes: - CheckStatus speed-up (read ~/.claude.json directly) - Thread-safety fixes (PR CoplayDev#667) - Beta mode validation (PR CoplayDev#671) Fixes CoplayDev#664 Related: CoplayDev#643 Co-Authored-By: Claude <noreply@anthropic.com>
…rver (CoplayDev#664) (CoplayDev#667) * Fix Git URL in README for package installation Updated the Git URL for adding the package to include the branch name. * fix: Clean up Claude Code config from all scopes to prevent stale config conflicts (CoplayDev#664) - Add RemoveFromAllScopes helper to remove from local/user/project scopes - Use explicit --scope local when registering - Update manual snippets to show multi-scope cleanup - Handle legacy 'unityMCP' naming in all scopes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Make Claude Code status check thread-safe (CoplayDev#664) The background thread status check was accessing main-thread-only Unity APIs (Application.platform, EditorPrefs via HttpEndpointUtility and AssetPathUtility), causing "GetString can only be called from main thread" errors. Now all main-thread-only values are captured before Task.Run() and passed as parameters to CheckStatusWithProjectDir(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Persist client dropdown selection and remove dead IO code - Remember last selected client in EditorPrefs so it restores on window reopen (prevents hiding config issues by defaulting to first client) - Remove dead Outbound class, _outbox BlockingCollection, and writer thread that was never used (nothing ever enqueued to outbox) - Keep only failure IO logs, remove verbose success logging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Auto-detect beta package to enable UseBetaServer + workflow updates - Add IsPreReleaseVersion() helper to detect beta/alpha/rc package versions - UseBetaServer now defaults to true only for prerelease package versions - Main branch users get false default, beta branch users get true default - Update beta-release.yml to set Unity package version with -beta.1 suffix - Update release.yml to merge beta → main and strip beta suffix - Fix CodexConfigHelperTests to explicitly set UseBetaServer for determinism - Use EditorConfigurationCache consistently for UseBetaServer access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Address code review feedback for thread-safety and validation - Add thread-safe overloads for GetBetaServerFromArgs/List that accept pre-captured useBetaServer and gitUrlOverride parameters - Use EditorConfigurationCache.SetUseBetaServer() in McpAdvancedSection for atomic cache + EditorPrefs update - Add semver validation guard in beta-release.yml before version arithmetic - Add semver validation guard in release.yml after stripping prerelease suffix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Complete thread-safety for GetBetaServerFromArgs overloads - Add packageSource parameter to thread-safe overloads to avoid calling GetMcpServerPackageSource() (which uses EditorPrefs) from background threads - Apply quoteFromPath logic to gitUrlOverride and packageSource paths to handle local paths with spaces correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Patch legacy connection pool in transport tests to prevent real Unity discovery The auto-select tests were failing because they only patched PluginHub but not the fallback legacy connection pool discovery. When PluginHub returns no results, the middleware falls back to discovering instances via get_unity_connection_pool(), which found the real running Unity. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Description
Fixes issues with Claude Code MCP client registration causing connection errors due to stale configs at different scopes, thread-safety errors during status checks, and UX issues with client selection.
Type of Change
Changes Made
Multi-scope config cleanup
RemoveFromAllScopeshelper to remove configs from local/user/project scopes before registering--scope localwhen registering to prevent scope ambiguityThread-safe status check
Application.platform,HttpEndpointUtility.IsRemoteScope(), andAssetPathUtility.GetMcpServerPackageSource()on main thread beforeTask.Run()CheckStatusWithProjectDir()UX improvements
Auto-detect beta package for UseBetaServer
IsPreReleaseVersion()helper to detect-beta,-alpha,-rc,-preview,-presuffixes in package versionUseBetaServernow defaults totrueonly for prerelease package versionsfalsedefault, beta branch users gettruedefault automaticallyEditorConfigurationCacheconsistently forUseBetaServeraccessCodexConfigHelperTeststo explicitly setUseBetaServerfor deterministic testsBeta mode status validation fix
mcpforunityserver>=0.0.0a0for beta)CheckStatus) and background-thread (McpClientConfigSection) code pathsEditorPrefs Manager improvements
Workflow updates for beta/main version management
beta-release.ymlto set Unity package version with semver beta suffix (e.g.,9.4.0-beta.1)release.ymlto merge beta -> main before version bumprelease.ymlto strip beta suffix from version before stable releaseCode cleanup
Outboundclass,_outboxBlockingCollection, and writer thread (nothing ever enqueued)Version Flow
Testing/Screenshots/Recordings
IsPreReleaseVersion()correctly detects beta versionsDocumentation Updates
tools/UPDATE_DOCS_PROMPT.md(recommended)tools/UPDATE_DOCS.mdNote: Only manual configuration snippets updated (showing multi-scope cleanup). No tool/resource API changes.
Related Issues
Fixes #664
Additional Notes
The original issue reported WebSocket disconnect errors on Windows. This PR addresses: