fix: speed up Claude Code config check by reading JSON directly#682
fix: speed up Claude Code config check by reading JSON directly#682dsarno merged 8 commits intoCoplayDev:betafrom
Conversation
Reviewer's GuideReplaces the slow CLI-based Claude MCP status check with a direct read of ~/.claude.json, adding JSON-based project lookup (with path normalization, directory walking, and duplicate handling) plus clearer transport/version mismatch detection, including beta vs stable differentiation. Sequence diagram for Claude MCP status check using JSON configsequenceDiagram
actor UnityDeveloper
participant UnityEditor
participant McpClientConfiguratorBase
participant FileSystem
UnityDeveloper->>UnityEditor: Open MCP window
UnityEditor->>McpClientConfiguratorBase: CheckStatusWithProjectDir(projectDir, useHttpTransport, expectedPackageSource)
McpClientConfiguratorBase->>McpClientConfiguratorBase: ReadClaudeCodeConfig(projectDir)
activate McpClientConfiguratorBase
McpClientConfiguratorBase->>FileSystem: Read ~/.claude.json
FileSystem-->>McpClientConfiguratorBase: config or error
alt Config read error
McpClientConfiguratorBase-->>UnityEditor: status = NotConfigured, error
else UnityMCP not found in config
McpClientConfiguratorBase-->>UnityEditor: status = NotConfigured
else UnityMCP found
McpClientConfiguratorBase->>McpClientConfiguratorBase: Determine registered transport type
McpClientConfiguratorBase->>McpClientConfiguratorBase: Compare with useHttpTransport
McpClientConfiguratorBase->>McpClientConfiguratorBase: ExtractPackageSourceFromConfig(serverConfig)
McpClientConfiguratorBase->>McpClientConfiguratorBase: IsBetaPackageSource(configured)
McpClientConfiguratorBase->>McpClientConfiguratorBase: IsBetaPackageSource(expected)
McpClientConfiguratorBase->>McpClientConfiguratorBase: Detect transport/version mismatch
alt Mismatch and auto rewrite allowed
McpClientConfiguratorBase->>McpClientConfiguratorBase: SetStatus(IncorrectPath)
McpClientConfiguratorBase->>McpClientConfiguratorBase: Configure()
McpClientConfiguratorBase-->>UnityEditor: status after reconfigure
else Mismatch but no auto rewrite
McpClientConfiguratorBase->>McpClientConfiguratorBase: SetStatus(Error or IncorrectPath with message)
McpClientConfiguratorBase-->>UnityEditor: mismatched status
else No mismatch
McpClientConfiguratorBase->>McpClientConfiguratorBase: SetStatus(Configured)
McpClientConfiguratorBase-->>UnityEditor: status = Configured
end
end
deactivate McpClientConfiguratorBase
Updated class diagram for McpClientConfiguratorBase configuration logicclassDiagram
class McpClientConfiguratorBase {
- Models.ConfiguredTransport configuredTransport
+ McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTransport, string expectedPackageSource, bool attemptAutoRewrite, bool isRemoteScope, RuntimePlatform platform)
+ void Configure()
+ void SetStatus(McpStatus status)
+ void SetStatus(McpStatus status, string message)
- static JObject serverConfig
- static string error
- static JObject ReadClaudeCodeConfig(string projectDir)
- static string NormalizePath(string path)
- static string ExtractPackageSourceFromConfig(JObject serverConfig)
- static bool IsBetaPackageSource(string packageSource)
- static string ExtractPackageSourceFromCliOutput(string cliOutput)
}
class McpClient {
+ McpStatus status
+ Models.ConfiguredTransport configuredTransport
+ void SetStatus(McpStatus status)
+ void SetStatus(McpStatus status, string message)
}
class McpLog {
+ static void Info(string message)
+ static void Warn(string message)
}
class Models_ConfiguredTransport {
<<enumeration>>
Http
HttpRemote
Stdio
Unknown
}
class McpStatus {
<<enumeration>>
Configured
NotConfigured
IncorrectPath
Error
}
McpClientConfiguratorBase o-- McpClient : client
McpClientConfiguratorBase ..> McpLog : logging
McpClientConfiguratorBase ..> Models_ConfiguredTransport : sets
McpClientConfiguratorBase ..> McpStatus : sets
McpClient --> Models_ConfiguredTransport
McpClient --> McpStatus
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughReads Claude Code config from Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Editor UI
participant Config as McpClientConfiguratorBase
participant File as ~/.claude.json
participant Main as Main Thread (Configure)
UI->>Config: Refresh status (forceImmediate)
Config->>File: ReadClaudeCodeConfig(projectDir)
File-->>Config: ClaudeCode JSON (stdio args / --from)
Config->>Config: NormalizePath / ExtractPackageSourceFromConfig
Config->>Config: Determine configuredTransport, expectedPackageSource
alt transport or version mismatch detected
Config-->>UI: Report VersionMismatch + mismatchReason via OnClientConfigMismatch
opt auto-rewrite allowed
UI->>Main: Invoke Configure()
Main->>Config: Configure()
Config-->>UI: Status -> Configuring... -> Configured/NotConfigured
end
else no mismatch
Config-->>UI: Report Configured/NotConfigured
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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:
- Consider treating a missing ~/.claude.json as a normal "not configured" case (e.g., returning (null, null)) rather than an error string, so the UI/state continues to distinguish between "no Claude Code installed" and actual config read/parsing failures.
- ReadClaudeCodeConfig is now called on every status check and always re-reads/parses the JSON file; you might want to add simple caching (e.g., by path + last-write time) to avoid repeated disk IO and JSON parsing when the config hasn’t changed.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider treating a missing ~/.claude.json as a normal "not configured" case (e.g., returning (null, null)) rather than an error string, so the UI/state continues to distinguish between "no Claude Code installed" and actual config read/parsing failures.
- ReadClaudeCodeConfig is now called on every status check and always re-reads/parses the JSON file; you might want to add simple caching (e.g., by path + last-write time) to avoid repeated disk IO and JSON parsing when the config hasn’t changed.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Instead of running `claude mcp list` (15+ seconds due to health checks), read the config directly from ~/.claude.json (instant). Changes: - Add ReadClaudeCodeConfig() to parse Claude's JSON config file - Walk up directory tree to find config at parent directories - Handle duplicate path entries (forward/backslash variants) - Add beta/stable version mismatch detection with clear messages - Add IsBetaPackageSource() to detect PyPI beta versions and prerelease ranges - Change button label from "Register" to "Configure" for consistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ddcc053 to
3ecee71
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs`:
- Around line 1082-1116: The ExtractPackageSourceFromConfig method misses args
formatted as "--from=VALUE"; update the parsing in
ExtractPackageSourceFromConfig to also detect arguments that start with
"--from=" by checking argStr.StartsWith("--from=") and returning the substring
after the '=' (trim surrounding quotes/whitespace), while keeping the existing
behavior for a separate "--from" token followed by the next arg; operate on the
same args JArray and ensure null/empty checks remain.
- Around line 470-571: The code only checks HTTP vs stdio but misses HTTP URL
mismatches; update the logic around registeredType/registeredWithHttp to extract
the registered HTTP URL from serverConfig (e.g., look for a "url" or similar
field), derive client.configuredTransport as HttpRemote vs Http based on the
registered URL (not currentUseHttp), and add a URL comparison: compute
expectedUrl (from the same place you derive
expectedPackageSource/expectedPackageSource), set hasTransportMismatch or a new
hasUrlMismatch when the registered URL differs (case-insensitive/normalized),
and treat that as a configuration mismatch that triggers the same auto-rewrite
path (using Configure(), client.SetStatus, McpStatus) or shows the appropriate
error message via McpLog.Warn; keep existing stdio/version checks
(ExtractPackageSourceFromConfig, IsBetaPackageSource) intact.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs`:
- Around line 528-571: Replace the hardcoded main-thread check
(Thread.CurrentThread.ManagedThreadId == 1) with a captured main thread id
field: add a static or instance field (e.g., s_mainThreadId or _mainThreadId) to
McpClientConfiguratorBase and initialize it on startup (static constructor or
class constructor) by capturing
System.Threading.Thread.CurrentThread.ManagedThreadId (same pattern as
TransportCommandDispatcher.cs / StdioBridgeHost.cs), then change the local
isMainThread assignment to compare Thread.CurrentThread.ManagedThreadId ==
s_mainThreadId; update any references in the current method (the block using
attemptAutoRewrite, isMainThread, Configure(), client.SetStatus) to use that
field.
| // If there's any mismatch and auto-rewrite is enabled, re-register | ||
| if (hasTransportMismatch || hasVersionMismatch) | ||
| { | ||
| // Configure() requires main thread (accesses EditorPrefs, Application.dataPath) | ||
| // Only attempt auto-rewrite if we're on the main thread | ||
| bool isMainThread = System.Threading.Thread.CurrentThread.ManagedThreadId == 1; | ||
| if (attemptAutoRewrite && isMainThread) | ||
| { | ||
| string reason = hasTransportMismatch | ||
| ? $"Transport mismatch (registered: {(registeredWithHttp ? "HTTP" : "stdio")}, expected: {(currentUseHttp ? "HTTP" : "stdio")})" | ||
| : mismatchReason ?? $"Package version mismatch"; | ||
| McpLog.Info($"{reason}. Re-registering..."); | ||
| try | ||
| { | ||
| // Force re-register by ensuring status is not Configured (which would toggle to Unregister) | ||
| client.SetStatus(McpStatus.IncorrectPath); | ||
| Configure(); | ||
| return client.status; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| McpLog.Warn($"Auto-reregister failed: {ex.Message}"); | ||
| client.SetStatus(McpStatus.IncorrectPath, $"Configuration mismatch. Click Configure to re-register."); | ||
| return client.status; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| if (hasTransportMismatch) | ||
| { | ||
| string errorMsg = $"Transport mismatch: Claude Code is registered with {(registeredWithHttp ? "HTTP" : "stdio")} but current setting is {(currentUseHttp ? "HTTP" : "stdio")}. Click Configure to re-register."; | ||
| client.SetStatus(McpStatus.Error, errorMsg); | ||
| McpLog.Warn(errorMsg); | ||
| } | ||
| else | ||
| { | ||
| client.SetStatus(McpStatus.IncorrectPath, mismatchReason); | ||
| } | ||
| return client.status; | ||
| } | ||
| } | ||
|
|
||
| client.SetStatus(McpStatus.NotConfigured); | ||
| client.configuredTransport = Models.ConfiguredTransport.Unknown; | ||
| client.SetStatus(McpStatus.Configured); | ||
| return client.status; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the context around the line in question
cat -n MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs | sed -n '520,580p'Repository: CoplayDev/unity-mcp
Length of output: 3674
🏁 Script executed:
# Search for ManagedThreadId usage in the codebase
rg "ManagedThreadId" --type csRepository: CoplayDev/unity-mcp
Length of output: 1005
🏁 Script executed:
# Search for other thread detection patterns in the codebase
rg "Thread\." --type cs -A 2 -B 2 | head -100Repository: CoplayDev/unity-mcp
Length of output: 7810
🏁 Script executed:
# Look for Unity.EditorApplication or Editor thread related checks
rg "EditorApplication|MainThread|ThreadPool" --type cs | head -30Repository: CoplayDev/unity-mcp
Length of output: 3430
🏁 Script executed:
# Check what's in the using statements and imports of this file
head -30 MCPForUnity/Editor/Clients/McpClientConfiguratorBase.csRepository: CoplayDev/unity-mcp
Length of output: 977
Use a captured main-thread ID instead of hardcoded ManagedThreadId == 1.
The main thread ID is not guaranteed to be 1 across platforms. Other parts of this codebase (e.g., TransportCommandDispatcher.cs, StdioBridgeHost.cs) already demonstrate the correct pattern: capture the thread ID at initialization and compare against it. Apply the same approach here.
🤖 Prompt for AI Agents
In `@MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs` around lines 528 -
571, Replace the hardcoded main-thread check
(Thread.CurrentThread.ManagedThreadId == 1) with a captured main thread id
field: add a static or instance field (e.g., s_mainThreadId or _mainThreadId) to
McpClientConfiguratorBase and initialize it on startup (static constructor or
class constructor) by capturing
System.Threading.Thread.CurrentThread.ManagedThreadId (same pattern as
TransportCommandDispatcher.cs / StdioBridgeHost.cs), then change the local
isMainThread assignment to compare Thread.CurrentThread.ManagedThreadId ==
s_mainThreadId; update any references in the current method (the block using
attemptAutoRewrite, isMainThread, Configure(), client.SetStatus) to use that
field.
Instead of running `claude mcp list` (15+ seconds due to health checks), read the config directly from ~/.claude.json (instant). Changes: - Add ReadClaudeCodeConfig() to parse Claude's JSON config file - Walk up directory tree to find config at parent directories - Handle duplicate path entries (forward/backslash variants) - Add beta/stable version mismatch detection with clear messages - Add IsBetaPackageSource() to detect PyPI beta versions and prerelease ranges - Change button label from "Register" to "Configure" for consistency - Refresh client status when switching to Connect tab or toggling beta mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add McpStatus.VersionMismatch enum value for version mismatch cases - Show "Version Mismatch" with yellow warning indicator instead of "Error" - Use VersionMismatch for beta/stable package source mismatches - Keep Error status for transport mismatches and general errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add version-mismatch-warning banner to McpConnectionSection.uxml - Add UpdateVersionMismatchWarning method to show/hide the banner - Fire OnClientConfigMismatch event when VersionMismatch status detected - Wire up event in main window to update the warning banner - Store mismatch details in configStatus for both Error and VersionMismatch Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Before: "Beta/stable mismatch: registered with beta 'mcpforunityserver>=0.0.0a0' but plugin is stable 'mcpforunityserver==9.4.0'." After: "Configured for beta server, but 'Use Beta Server' is disabled in Advanced settings." 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/Clients/McpClientConfiguratorBase.cs`:
- Around line 451-456: The current branch sets McpStatus.NotConfigured and
clears configuredTransport but drops configResult.error/mismatchReason because
SetStatus only records errorDetails for McpStatus.Error; update the flow so
those details are preserved and surfaced: either extend the client to hold a
dedicated statusDetails field (e.g., add/assign client.statusDetails =
configResult.error or mismatchReason) before calling
SetStatus(McpStatus.NotConfigured, ...), or explicitly propagate them through
the existing mismatch event/logger (invoke the mismatch event with
configResult.error/mismatchReason or call a logging helper). Make the change in
the block that uses SetStatus and the similar block at the other occurrence so
non-Error statuses retain actionable details for users.
- Treat missing ~/.claude.json as "not configured" instead of error (distinguishes "no Claude Code installed" from actual read failures) - Handle --from=VALUE format in ExtractPackageSourceFromConfig (in addition to existing --from VALUE format) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ients - Move GetExpectedPackageSourceForValidation() and IsBetaPackageSource() to base class so all configurators can use them - Update JsonFileMcpConfigurator.CheckStatus() to use beta-aware comparison - Show VersionMismatch status with clear messaging for Claude Desktop, Cursor, Windsurf, VS Code, and other JSON-based clients - Auto-rewrite still attempts to fix mismatches automatically 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/Clients/McpClientConfiguratorBase.cs`:
- Around line 596-612: CheckStatusWithProjectDir currently only detects
transport type changes via client.configuredTransport and hasTransportMismatch;
when registeredWithHttp is true also read the registered URL from
serverConfig["url"] and compare it to HttpEndpointUtility.GetMcpRpcUrl(), and if
they differ mark the client as mismatched (same semantics used for stdio
mismatch detection) so the configured state reflects an endpoint URL change;
update the logic around registeredWithHttp/currentUseHttp to include this URL
equality check (use the same pattern as
OpenCodeConfigurator/CodexMcpConfigurator) and surface the mismatch where
hasTransportMismatch is evaluated.
🧹 Nitpick comments (2)
MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs (2)
96-108: Consider reusingEditorConfigurationCache.GetUseBetaServerWithDynamicDefault()to avoid logic duplication.This duplicates the beta mode detection logic from
EditorConfigurationCache. If the beta detection logic changes in one place, it could drift from the other. Consider extracting a shared helper or calling the cache method directly (if main-thread access is guaranteed).
428-434: CodexMcpConfigurator lacks beta-aware validation.
JsonFileMcpConfiguratorandClaudeCliMcpConfiguratornow useGetExpectedPackageSourceForValidation()for beta-aware comparison, butCodexMcpConfiguratorstill usesAssetPathUtility.GetMcpServerPackageSource()directly. This inconsistency means Codex clients won't detect beta/stable mismatches.♻️ Suggested fix
else if (args != null && args.Length > 0) { - string expected = AssetPathUtility.GetMcpServerPackageSource(); + string expected = GetExpectedPackageSourceForValidation(); string configured = McpConfigurationHelper.ExtractUvxUrl(args); matches = !string.IsNullOrEmpty(configured) && McpConfigurationHelper.PathsEqual(configured, expected); }Note: You may also want to add version mismatch detection and messaging similar to
JsonFileMcpConfigurator.
CodexMcpConfigurator was still using the non-beta-aware package source comparison. Now uses GetExpectedPackageSourceForValidation() and shows VersionMismatch status with clear messaging like other configurators. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ntion Cherry-pick PR CoplayDev#682: Read ~/.claude.json directly instead of running `claude mcp list` (15+ seconds). Makes config check instant. Fix TcpListener leak on Windows: - 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 Fixes CoplayDev#664 Related: CoplayDev#643 Co-Authored-By: Claude <noreply@anthropic.com>
…ntion Cherry-pick PR CoplayDev#682: Read ~/.claude.json directly instead of running `claude mcp list` (15+ seconds). Makes config check instant. Fix TcpListener leak on Windows: - 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 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>
- 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>
Summary
claude mcp list(15+ seconds due to health checks on every configured server), read the config directly from~/.claude.json(instant)Problem
The Claude Code configurator was calling
claude mcp listwhich performs health checks on all MCP servers, taking 15+ seconds. This made the status check extremely slow and sometimes timeout.Solution
Read
~/.claude.jsondirectly and parse the JSON to find the UnityMCP configuration. This is instant and provides all the same information.Test plan
🤖 Generated with Claude Code
Summary by Sourcery
Speed up Claude Code Unity MCP status checks by reading configuration directly from the local JSON config instead of invoking the CLI, while improving transport/version validation.
New Features:
Enhancements:
Summary by CodeRabbit
New Features
Improvements