* 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 (#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 (#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>beta
parent
ac7f4a3099
commit
cb08b0c59b
|
|
@ -10,10 +10,93 @@ on:
|
|||
- beta
|
||||
paths:
|
||||
- "Server/**"
|
||||
- "MCPForUnity/**"
|
||||
|
||||
jobs:
|
||||
update_unity_beta_version:
|
||||
name: Update Unity package to beta version
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
unity_beta_version: ${{ steps.version.outputs.unity_beta_version }}
|
||||
version_updated: ${{ steps.commit.outputs.updated }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: beta
|
||||
|
||||
- name: Generate beta version for Unity package
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Read current Unity package version
|
||||
CURRENT_VERSION=$(jq -r '.version' MCPForUnity/package.json)
|
||||
echo "Current Unity package version: $CURRENT_VERSION"
|
||||
|
||||
# Strip any existing pre-release suffix for safe parsing
|
||||
# e.g., "9.3.1-beta.1" -> "9.3.1"
|
||||
BASE_VERSION=$(echo "$CURRENT_VERSION" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//')
|
||||
|
||||
# Validate we have a proper X.Y.Z format before arithmetic
|
||||
if ! [[ "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Could not parse version '$CURRENT_VERSION' -> '$BASE_VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if already a beta version
|
||||
if [[ "$CURRENT_VERSION" == *"-beta."* ]]; then
|
||||
echo "Already a beta version, keeping: $CURRENT_VERSION"
|
||||
echo "unity_beta_version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "already_beta=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Create beta version with semver format (e.g., 9.4.0-beta.1)
|
||||
# Bump minor version to ensure beta is "newer" than stable
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
|
||||
NEXT_MINOR=$((MINOR + 1))
|
||||
BETA_VERSION="${MAJOR}.${NEXT_MINOR}.0-beta.1"
|
||||
echo "Generated Unity beta version: $BETA_VERSION"
|
||||
echo "unity_beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "already_beta=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Update Unity package.json with beta version
|
||||
if: steps.version.outputs.already_beta != 'true'
|
||||
env:
|
||||
BETA_VERSION: ${{ steps.version.outputs.unity_beta_version }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Update package.json version
|
||||
jq --arg v "$BETA_VERSION" '.version = $v' MCPForUnity/package.json > tmp.json
|
||||
mv tmp.json MCPForUnity/package.json
|
||||
echo "Updated MCPForUnity/package.json:"
|
||||
jq '.version' MCPForUnity/package.json
|
||||
|
||||
- name: Commit and push beta version
|
||||
id: commit
|
||||
if: steps.version.outputs.already_beta != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
|
||||
if git diff --quiet MCPForUnity/package.json; then
|
||||
echo "No changes to commit"
|
||||
echo "updated=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
git add MCPForUnity/package.json
|
||||
git commit -m "chore: update Unity package to beta version ${{ steps.version.outputs.unity_beta_version }}"
|
||||
git push origin beta
|
||||
echo "updated=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
publish_pypi_prerelease:
|
||||
name: Publish beta to PyPI (pre-release)
|
||||
needs: update_unity_beta_version
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
|
|
@ -25,6 +108,7 @@ jobs:
|
|||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: beta
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
|
|
|
|||
|
|
@ -44,6 +44,50 @@ jobs:
|
|||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Merge beta into main
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
|
||||
# Fetch beta branch
|
||||
git fetch origin beta
|
||||
|
||||
# Check if beta has changes not in main
|
||||
if git merge-base --is-ancestor origin/beta HEAD; then
|
||||
echo "beta is already merged into main. Nothing to merge."
|
||||
else
|
||||
echo "Merging beta into main..."
|
||||
git merge origin/beta --no-edit -m "chore: merge beta into main for release"
|
||||
echo "Beta merged successfully."
|
||||
fi
|
||||
|
||||
- name: Strip beta suffix from version if present
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CURRENT_VERSION=$(jq -r '.version' "MCPForUnity/package.json")
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
|
||||
# Strip beta/alpha/rc suffix if present (e.g., "9.4.0-beta.1" -> "9.4.0")
|
||||
if [[ "$CURRENT_VERSION" == *"-"* ]]; then
|
||||
STABLE_VERSION=$(echo "$CURRENT_VERSION" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//')
|
||||
# Validate we have a proper X.Y.Z format after stripping
|
||||
if ! [[ "$STABLE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Could not parse version '$CURRENT_VERSION' -> '$STABLE_VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Stripping prerelease suffix: $CURRENT_VERSION -> $STABLE_VERSION"
|
||||
jq --arg v "$STABLE_VERSION" '.version = $v' MCPForUnity/package.json > tmp.json
|
||||
mv tmp.json MCPForUnity/package.json
|
||||
|
||||
# Also update pyproject.toml
|
||||
sed -i "s/^version = .*/version = \"${STABLE_VERSION}\"/" Server/pyproject.toml
|
||||
else
|
||||
echo "Version is already stable: $CURRENT_VERSION"
|
||||
fi
|
||||
|
||||
- name: Compute new version
|
||||
id: compute
|
||||
shell: bash
|
||||
|
|
|
|||
|
|
@ -409,18 +409,25 @@ namespace MCPForUnity.Editor.Clients
|
|||
bool useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport;
|
||||
// Resolve claudePath on the main thread (EditorPrefs access)
|
||||
string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath();
|
||||
return CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, attemptAutoRewrite);
|
||||
RuntimePlatform platform = Application.platform;
|
||||
bool isRemoteScope = HttpEndpointUtility.IsRemoteScope();
|
||||
string expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource();
|
||||
return CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, expectedPackageSource, attemptAutoRewrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal thread-safe version of CheckStatus.
|
||||
/// Can be called from background threads because all main-thread-only values are passed as parameters.
|
||||
/// projectDir, useHttpTransport, and claudePath are REQUIRED (non-nullable) to enforce thread safety at compile time.
|
||||
/// projectDir, useHttpTransport, claudePath, platform, isRemoteScope, and expectedPackageSource are REQUIRED
|
||||
/// (non-nullable where applicable) to enforce thread safety at compile time.
|
||||
/// NOTE: attemptAutoRewrite is NOT fully thread-safe because Configure() requires the main thread.
|
||||
/// When called from a background thread, pass attemptAutoRewrite=false and handle re-registration
|
||||
/// on the main thread based on the returned status.
|
||||
/// </summary>
|
||||
internal McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTransport, string claudePath, bool attemptAutoRewrite = false)
|
||||
internal McpStatus CheckStatusWithProjectDir(
|
||||
string projectDir, bool useHttpTransport, string claudePath, RuntimePlatform platform,
|
||||
bool isRemoteScope, string expectedPackageSource,
|
||||
bool attemptAutoRewrite = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -438,11 +445,11 @@ namespace MCPForUnity.Editor.Clients
|
|||
}
|
||||
|
||||
string pathPrepend = null;
|
||||
if (Application.platform == RuntimePlatform.OSXEditor)
|
||||
if (platform == RuntimePlatform.OSXEditor)
|
||||
{
|
||||
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
|
||||
}
|
||||
else if (Application.platform == RuntimePlatform.LinuxEditor)
|
||||
else if (platform == RuntimePlatform.LinuxEditor)
|
||||
{
|
||||
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
|
||||
}
|
||||
|
|
@ -485,7 +492,7 @@ namespace MCPForUnity.Editor.Clients
|
|||
// so infer from the current scope setting when HTTP is detected.
|
||||
if (registeredWithHttp)
|
||||
{
|
||||
client.configuredTransport = HttpEndpointUtility.IsRemoteScope()
|
||||
client.configuredTransport = isRemoteScope
|
||||
? Models.ConfiguredTransport.HttpRemote
|
||||
: Models.ConfiguredTransport.Http;
|
||||
}
|
||||
|
|
@ -504,10 +511,9 @@ namespace MCPForUnity.Editor.Clients
|
|||
// For stdio transport, also check package version
|
||||
bool hasVersionMismatch = false;
|
||||
string configuredPackageSource = null;
|
||||
string expectedPackageSource = null;
|
||||
if (registeredWithStdio)
|
||||
{
|
||||
expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource();
|
||||
// expectedPackageSource was captured on main thread and passed as parameter
|
||||
configuredPackageSource = ExtractPackageSourceFromCliOutput(getStdout);
|
||||
hasVersionMismatch = !string.IsNullOrEmpty(configuredPackageSource) &&
|
||||
!string.Equals(configuredPackageSource, expectedPackageSource, StringComparison.OrdinalIgnoreCase);
|
||||
|
|
@ -566,6 +572,7 @@ namespace MCPForUnity.Editor.Clients
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Warn($"[Claude Code] CheckStatus exception: {ex.GetType().Name}: {ex.Message}");
|
||||
client.SetStatus(McpStatus.Error, ex.Message);
|
||||
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
||||
}
|
||||
|
|
@ -627,27 +634,28 @@ namespace MCPForUnity.Editor.Clients
|
|||
if (useHttpTransport)
|
||||
{
|
||||
// Only include API key header for remote-hosted mode
|
||||
// Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
|
||||
if (serverTransport == Models.ConfiguredTransport.HttpRemote && !string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
string safeKey = SanitizeShellHeaderValue(apiKey);
|
||||
args = $"mcp add --transport http UnityMCP {httpUrl} --header \"{AuthConstants.ApiKeyHeader}: {safeKey}\"";
|
||||
args = $"mcp add --scope local --transport http UnityMCP {httpUrl} --header \"{AuthConstants.ApiKeyHeader}: {safeKey}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
args = $"mcp add --transport http UnityMCP {httpUrl}";
|
||||
args = $"mcp add --scope local --transport http UnityMCP {httpUrl}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
|
||||
string devFlags = shouldForceRefresh ? "--no-cache --refresh " : string.Empty;
|
||||
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{gitUrl}\" {packageName}";
|
||||
// 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}--from \"{gitUrl}\" {packageName}";
|
||||
}
|
||||
|
||||
// Remove any existing registrations - handle both "UnityMCP" and "unityMCP" (legacy)
|
||||
McpLog.Info("Removing any existing UnityMCP registrations before adding...");
|
||||
ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
ExecPath.TryRun(claudePath, "mcp remove unityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
// Remove any existing registrations from ALL scopes to prevent stale config conflicts (#664)
|
||||
McpLog.Info("Removing any existing UnityMCP registrations from all scopes before adding...");
|
||||
RemoveFromAllScopes(claudePath, projectDir, pathPrepend);
|
||||
|
||||
// Now add the registration
|
||||
if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
|
||||
|
|
@ -670,10 +678,9 @@ namespace MCPForUnity.Editor.Clients
|
|||
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
|
||||
}
|
||||
|
||||
// Remove both "UnityMCP" and "unityMCP" (legacy naming)
|
||||
McpLog.Info("Removing all UnityMCP registrations...");
|
||||
ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
ExecPath.TryRun(claudePath, "mcp remove unityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
// Remove from ALL scopes to ensure complete cleanup (#664)
|
||||
McpLog.Info("Removing all UnityMCP registrations from all scopes...");
|
||||
RemoveFromAllScopes(claudePath, projectDir, pathPrepend);
|
||||
|
||||
McpLog.Info("MCP server successfully unregistered from Claude Code.");
|
||||
client.SetStatus(McpStatus.NotConfigured);
|
||||
|
|
@ -696,22 +703,23 @@ namespace MCPForUnity.Editor.Clients
|
|||
{
|
||||
string httpUrl = HttpEndpointUtility.GetMcpRpcUrl();
|
||||
// Only include API key header for remote-hosted mode
|
||||
// Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
|
||||
if (HttpEndpointUtility.IsRemoteScope())
|
||||
{
|
||||
string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);
|
||||
if (!string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
string safeKey = SanitizeShellHeaderValue(apiKey);
|
||||
args = $"mcp add --transport http UnityMCP {httpUrl} --header \"{AuthConstants.ApiKeyHeader}: {safeKey}\"";
|
||||
args = $"mcp add --scope local --transport http UnityMCP {httpUrl} --header \"{AuthConstants.ApiKeyHeader}: {safeKey}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
args = $"mcp add --transport http UnityMCP {httpUrl}";
|
||||
args = $"mcp add --scope local --transport http UnityMCP {httpUrl}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
args = $"mcp add --transport http UnityMCP {httpUrl}";
|
||||
args = $"mcp add --scope local --transport http UnityMCP {httpUrl}";
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -720,7 +728,8 @@ namespace MCPForUnity.Editor.Clients
|
|||
// Use central helper that checks both DevModeForceServerRefresh AND local path detection.
|
||||
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
|
||||
string devFlags = AssetPathUtility.ShouldForceUvxRefresh() ? "--no-cache --refresh " : string.Empty;
|
||||
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{gitUrl}\" {packageName}";
|
||||
// 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}--from \"{gitUrl}\" {packageName}";
|
||||
}
|
||||
|
||||
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
||||
|
|
@ -747,10 +756,9 @@ namespace MCPForUnity.Editor.Clients
|
|||
}
|
||||
catch { }
|
||||
|
||||
// Remove any existing registrations - handle both "UnityMCP" and "unityMCP" (legacy)
|
||||
McpLog.Info("Removing any existing UnityMCP registrations before adding...");
|
||||
ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
ExecPath.TryRun(claudePath, "mcp remove unityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
// Remove any existing registrations from ALL scopes to prevent stale config conflicts (#664)
|
||||
McpLog.Info("Removing any existing UnityMCP registrations from all scopes before adding...");
|
||||
RemoveFromAllScopes(claudePath, projectDir, pathPrepend);
|
||||
|
||||
// Now add the registration with the current transport mode
|
||||
if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
|
||||
|
|
@ -787,10 +795,9 @@ namespace MCPForUnity.Editor.Clients
|
|||
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
|
||||
}
|
||||
|
||||
// Remove both "UnityMCP" and "unityMCP" (legacy naming)
|
||||
McpLog.Info("Removing all UnityMCP registrations...");
|
||||
ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
ExecPath.TryRun(claudePath, "mcp remove unityMCP", projectDir, out _, out _, 7000, pathPrepend);
|
||||
// Remove from ALL scopes to ensure complete cleanup (#664)
|
||||
McpLog.Info("Removing all UnityMCP registrations from all scopes...");
|
||||
RemoveFromAllScopes(claudePath, projectDir, pathPrepend);
|
||||
|
||||
McpLog.Info("MCP server successfully unregistered from Claude Code.");
|
||||
client.SetStatus(McpStatus.NotConfigured);
|
||||
|
|
@ -813,9 +820,11 @@ namespace MCPForUnity.Editor.Clients
|
|||
headerArg = !string.IsNullOrEmpty(apiKey) ? $" --header \"{AuthConstants.ApiKeyHeader}: {SanitizeShellHeaderValue(apiKey)}\"" : "";
|
||||
}
|
||||
return "# Register the MCP server with Claude Code:\n" +
|
||||
$"claude mcp add --transport http UnityMCP {httpUrl}{headerArg}\n\n" +
|
||||
"# Unregister the MCP server:\n" +
|
||||
"claude mcp remove UnityMCP\n\n" +
|
||||
$"claude mcp add --scope local --transport http UnityMCP {httpUrl}{headerArg}\n\n" +
|
||||
"# Unregister the MCP server (from all scopes to clean up any stale configs):\n" +
|
||||
"claude mcp remove --scope local UnityMCP\n" +
|
||||
"claude mcp remove --scope user UnityMCP\n" +
|
||||
"claude mcp remove --scope project UnityMCP\n\n" +
|
||||
"# List registered servers:\n" +
|
||||
"claude mcp list";
|
||||
}
|
||||
|
|
@ -831,9 +840,11 @@ namespace MCPForUnity.Editor.Clients
|
|||
string devFlags = AssetPathUtility.ShouldForceUvxRefresh() ? "--no-cache --refresh " : string.Empty;
|
||||
|
||||
return "# Register the MCP server with Claude Code:\n" +
|
||||
$"claude mcp add --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{packageSource}\" mcp-for-unity\n\n" +
|
||||
"# Unregister the MCP server:\n" +
|
||||
"claude mcp remove UnityMCP\n\n" +
|
||||
$"claude mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{packageSource}\" mcp-for-unity\n\n" +
|
||||
"# Unregister the MCP server (from all scopes to clean up any stale configs):\n" +
|
||||
"claude mcp remove --scope local UnityMCP\n" +
|
||||
"claude mcp remove --scope user UnityMCP\n" +
|
||||
"claude mcp remove --scope project UnityMCP\n\n" +
|
||||
"# List registered servers:\n" +
|
||||
"claude mcp list";
|
||||
}
|
||||
|
|
@ -845,6 +856,28 @@ namespace MCPForUnity.Editor.Clients
|
|||
"Restart Claude Code"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Removes UnityMCP registration from all Claude Code configuration scopes (local, user, project).
|
||||
/// This ensures no stale or conflicting configurations remain across different scopes.
|
||||
/// Also handles legacy "unityMCP" naming convention.
|
||||
/// </summary>
|
||||
private static void RemoveFromAllScopes(string claudePath, string projectDir, string pathPrepend)
|
||||
{
|
||||
// Remove from all three scopes to prevent stale configs causing connection issues.
|
||||
// See GitHub issue #664 - conflicting configs at different scopes can cause
|
||||
// Claude Code to connect with outdated/incorrect configuration.
|
||||
string[] scopes = { "local", "user", "project" };
|
||||
string[] names = { "UnityMCP", "unityMCP" }; // Include legacy naming
|
||||
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
ExecPath.TryRun(claudePath, $"mcp remove --scope {scope} {name}", projectDir, out _, out _, 5000, pathPrepend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizes a value for safe inclusion inside a double-quoted shell argument.
|
||||
/// Escapes characters that are special within double quotes (", \, `, $, !)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ namespace MCPForUnity.Editor.Constants
|
|||
internal const string ResourceEnabledPrefix = "MCPForUnity.ResourceEnabled.";
|
||||
internal const string ResourceFoldoutStatePrefix = "MCPForUnity.ResourceFoldout.";
|
||||
internal const string EditorWindowActivePanel = "MCPForUnity.EditorWindow.ActivePanel";
|
||||
internal const string LastSelectedClientId = "MCPForUnity.LastSelectedClientId";
|
||||
|
||||
internal const string SetupCompleted = "MCPForUnity.SetupCompleted";
|
||||
internal const string SetupDismissed = "MCPForUnity.SetupDismissed";
|
||||
|
|
|
|||
|
|
@ -248,21 +248,38 @@ namespace MCPForUnity.Editor.Helpers
|
|||
/// Handles beta server mode (prerelease from PyPI) vs standard mode (pinned version or override).
|
||||
/// Centralizes the prerelease logic to avoid duplication between HTTP and stdio transports.
|
||||
/// Priority: explicit fromUrl override > beta server mode > default package.
|
||||
/// NOTE: This overload reads from EditorPrefs/cache and MUST be called from the main thread.
|
||||
/// For background threads, use the overload that accepts pre-captured parameters.
|
||||
/// </summary>
|
||||
/// <param name="quoteFromPath">Whether to quote the --from path (needed for command-line strings, not for arg lists)</param>
|
||||
/// <returns>The package source arguments (e.g., "--prerelease explicit --from mcpforunityserver>=0.0.0a0")</returns>
|
||||
public static string GetBetaServerFromArgs(bool quoteFromPath = false)
|
||||
{
|
||||
// Explicit override (local path, git URL, etc.) always wins
|
||||
string fromUrl = GetMcpServerPackageSource();
|
||||
string overrideUrl = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||
if (!string.IsNullOrEmpty(overrideUrl))
|
||||
// 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, packageSource, quoteFromPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe overload that accepts pre-captured values.
|
||||
/// Use this when calling from background threads.
|
||||
/// </summary>
|
||||
/// <param name="useBetaServer">Pre-captured value from EditorConfigurationCache.Instance.UseBetaServer</param>
|
||||
/// <param name="gitUrlOverride">Pre-captured value from EditorPrefs GitUrlOverride</param>
|
||||
/// <param name="packageSource">Pre-captured value from GetMcpServerPackageSource()</param>
|
||||
/// <param name="quoteFromPath">Whether to quote the --from path</param>
|
||||
public static string GetBetaServerFromArgs(bool useBetaServer, string gitUrlOverride, string packageSource, bool quoteFromPath = false)
|
||||
{
|
||||
return $"--from {fromUrl}";
|
||||
// Explicit override (local path, git URL, etc.) always wins
|
||||
if (!string.IsNullOrEmpty(gitUrlOverride))
|
||||
{
|
||||
string fromValue = quoteFromPath ? $"\"{gitUrlOverride}\"" : gitUrlOverride;
|
||||
return $"--from {fromValue}";
|
||||
}
|
||||
|
||||
// Beta server mode: use prerelease from PyPI
|
||||
bool useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
if (useBetaServer)
|
||||
{
|
||||
// Use --prerelease explicit with version specifier to only get prereleases of our package,
|
||||
|
|
@ -272,9 +289,10 @@ namespace MCPForUnity.Editor.Helpers
|
|||
}
|
||||
|
||||
// Standard mode: use pinned version from package.json
|
||||
if (!string.IsNullOrEmpty(fromUrl))
|
||||
if (!string.IsNullOrEmpty(packageSource))
|
||||
{
|
||||
return $"--from {fromUrl}";
|
||||
string fromValue = quoteFromPath ? $"\"{packageSource}\"" : packageSource;
|
||||
return $"--from {fromValue}";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
|
@ -283,24 +301,39 @@ namespace MCPForUnity.Editor.Helpers
|
|||
/// <summary>
|
||||
/// Builds the uvx package source arguments as a list (for JSON config builders).
|
||||
/// Priority: explicit fromUrl override > beta server mode > default package.
|
||||
/// NOTE: This overload reads from EditorPrefs/cache and MUST be called from the main thread.
|
||||
/// For background threads, use the overload that accepts pre-captured parameters.
|
||||
/// </summary>
|
||||
/// <returns>List of arguments to add to uvx command</returns>
|
||||
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, packageSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe overload that accepts pre-captured values.
|
||||
/// Use this when calling from background threads.
|
||||
/// </summary>
|
||||
/// <param name="useBetaServer">Pre-captured value from EditorConfigurationCache.Instance.UseBetaServer</param>
|
||||
/// <param name="gitUrlOverride">Pre-captured value from EditorPrefs GitUrlOverride</param>
|
||||
/// <param name="packageSource">Pre-captured value from GetMcpServerPackageSource()</param>
|
||||
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
|
||||
string fromUrl = GetMcpServerPackageSource();
|
||||
string overrideUrl = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||
if (!string.IsNullOrEmpty(overrideUrl))
|
||||
if (!string.IsNullOrEmpty(gitUrlOverride))
|
||||
{
|
||||
args.Add("--from");
|
||||
args.Add(fromUrl);
|
||||
args.Add(gitUrlOverride);
|
||||
return args;
|
||||
}
|
||||
|
||||
// Beta server mode: use prerelease from PyPI
|
||||
bool useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
if (useBetaServer)
|
||||
{
|
||||
args.Add("--prerelease");
|
||||
|
|
@ -311,10 +344,10 @@ namespace MCPForUnity.Editor.Helpers
|
|||
}
|
||||
|
||||
// Standard mode: use pinned version from package.json
|
||||
if (!string.IsNullOrEmpty(fromUrl))
|
||||
if (!string.IsNullOrEmpty(packageSource))
|
||||
{
|
||||
args.Add("--from");
|
||||
args.Add(fromUrl);
|
||||
args.Add(packageSource);
|
||||
}
|
||||
|
||||
return args;
|
||||
|
|
@ -426,5 +459,31 @@ namespace MCPForUnity.Editor.Helpers
|
|||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the installed package version is a prerelease (beta, alpha, rc, etc.).
|
||||
/// Used to auto-enable beta server mode for beta package users.
|
||||
/// </summary>
|
||||
public static bool IsPreReleaseVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
string version = GetPackageVersion();
|
||||
if (string.IsNullOrEmpty(version) || version == "unknown")
|
||||
return false;
|
||||
|
||||
// Check for common prerelease indicators in semver format
|
||||
// e.g., "9.3.0-beta.1", "9.3.0-alpha", "9.3.0-rc.2", "9.3.0-preview"
|
||||
return version.Contains("-beta", StringComparison.OrdinalIgnoreCase) ||
|
||||
version.Contains("-alpha", StringComparison.OrdinalIgnoreCase) ||
|
||||
version.Contains("-rc", StringComparison.OrdinalIgnoreCase) ||
|
||||
version.Contains("-preview", StringComparison.OrdinalIgnoreCase) ||
|
||||
version.Contains("-pre", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,23 @@ namespace MCPForUnity.Editor.Services
|
|||
/// </summary>
|
||||
public int UnitySocketPort => _unitySocketPort;
|
||||
|
||||
/// <summary>
|
||||
/// Gets UseBetaServer value with dynamic default based on package version.
|
||||
/// If the pref hasn't been explicitly set, defaults to true for prerelease packages
|
||||
/// (beta, alpha, rc, etc.) and false for stable releases.
|
||||
/// </summary>
|
||||
private static bool GetUseBetaServerWithDynamicDefault()
|
||||
{
|
||||
// If user has explicitly set the pref, use that value
|
||||
if (EditorPrefs.HasKey(EditorPrefKeys.UseBetaServer))
|
||||
{
|
||||
return EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, false);
|
||||
}
|
||||
|
||||
// Otherwise, default based on whether this is a prerelease package
|
||||
return Helpers.AssetPathUtility.IsPreReleaseVersion();
|
||||
}
|
||||
|
||||
private EditorConfigurationCache()
|
||||
{
|
||||
Refresh();
|
||||
|
|
@ -137,7 +154,7 @@ namespace MCPForUnity.Editor.Services
|
|||
{
|
||||
_useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
|
||||
_debugLogs = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
|
||||
_useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
_useBetaServer = GetUseBetaServerWithDynamicDefault();
|
||||
_devModeForceServerRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
_uvxPathOverride = EditorPrefs.GetString(EditorPrefKeys.UvxPathOverride, string.Empty);
|
||||
_gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, string.Empty);
|
||||
|
|
@ -312,7 +329,7 @@ namespace MCPForUnity.Editor.Services
|
|||
_debugLogs = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
|
||||
break;
|
||||
case nameof(UseBetaServer):
|
||||
_useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
_useBetaServer = GetUseBetaServerWithDynamicDefault();
|
||||
break;
|
||||
case nameof(DevModeForceServerRefresh):
|
||||
_devModeForceServerRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -21,13 +20,6 @@ using UnityEngine;
|
|||
|
||||
namespace MCPForUnity.Editor.Services.Transport.Transports
|
||||
{
|
||||
class Outbound
|
||||
{
|
||||
public byte[] Payload;
|
||||
public string Tag;
|
||||
public int? ReqId;
|
||||
}
|
||||
|
||||
class QueuedCommand
|
||||
{
|
||||
public string CommandJson;
|
||||
|
|
@ -44,7 +36,6 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
|
|||
private static readonly object startStopLock = new();
|
||||
private static readonly object clientsLock = new();
|
||||
private static readonly HashSet<TcpClient> activeClients = new();
|
||||
private static readonly BlockingCollection<Outbound> _outbox = new(new ConcurrentQueue<Outbound>());
|
||||
private static CancellationTokenSource cts;
|
||||
private static Task listenerTask;
|
||||
private static int processingCommands = 0;
|
||||
|
|
@ -61,7 +52,6 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
|
|||
private const ulong MaxFrameBytes = 64UL * 1024 * 1024;
|
||||
private const int FrameIOTimeoutMs = 30000;
|
||||
|
||||
private static long _ioSeq = 0;
|
||||
private static void IoInfo(string s) { McpLog.Info(s, always: false); }
|
||||
|
||||
private static bool IsDebugEnabled()
|
||||
|
|
@ -123,30 +113,6 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
|
|||
static StdioBridgeHost()
|
||||
{
|
||||
try { mainThreadId = Thread.CurrentThread.ManagedThreadId; } catch { mainThreadId = 0; }
|
||||
try
|
||||
{
|
||||
var writerThread = new Thread(() =>
|
||||
{
|
||||
foreach (var item in _outbox.GetConsumingEnumerable())
|
||||
{
|
||||
try
|
||||
{
|
||||
long seq = Interlocked.Increment(ref _ioSeq);
|
||||
IoInfo($"[IO] ➜ write start seq={seq} tag={item.Tag} len={(item.Payload?.Length ?? 0)} reqId={(item.ReqId?.ToString() ?? "?")}");
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
sw.Stop();
|
||||
IoInfo($"[IO] ✓ write end tag={item.Tag} len={(item.Payload?.Length ?? 0)} reqId={(item.ReqId?.ToString() ?? "?")} durMs={sw.Elapsed.TotalMilliseconds:F1}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IoInfo($"[IO] ✗ write FAIL tag={item.Tag} reqId={(item.ReqId?.ToString() ?? "?")} {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
})
|
||||
{ IsBackground = true, Name = "MCP-Writer" };
|
||||
writerThread.Start();
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (Application.isBatchMode && string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITY_MCP_ALLOW_BATCH")))
|
||||
{
|
||||
|
|
@ -633,12 +599,10 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
|
|||
{
|
||||
try { McpLog.Info("[MCP] sending framed response", always: false); } catch { }
|
||||
}
|
||||
long seq = Interlocked.Increment(ref _ioSeq);
|
||||
byte[] responseBytes;
|
||||
try
|
||||
{
|
||||
responseBytes = System.Text.Encoding.UTF8.GetBytes(response);
|
||||
IoInfo($"[IO] ➜ write start seq={seq} tag=response len={responseBytes.Length} reqId=?");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -646,12 +610,9 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
|
|||
throw;
|
||||
}
|
||||
|
||||
var swDirect = System.Diagnostics.Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
await WriteFrameAsync(stream, responseBytes);
|
||||
swDirect.Stop();
|
||||
IoInfo($"[IO] ✓ write end tag=response len={responseBytes.Length} reqId=? durMs={swDirect.Elapsed.TotalMilliseconds:F1}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
|||
McpLog.SetDebugLoggingEnabled(debugEnabled);
|
||||
|
||||
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
useBetaServerToggle.value = EditorConfigurationCache.Instance.UseBetaServer;
|
||||
UpdatePathOverrides();
|
||||
UpdateDeploymentSection();
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
|||
|
||||
useBetaServerToggle.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, evt.newValue);
|
||||
EditorConfigurationCache.Instance.SetUseBetaServer(evt.newValue);
|
||||
OnHttpServerCommandUpdateRequested?.Invoke();
|
||||
OnBetaModeChanged?.Invoke(evt.newValue);
|
||||
});
|
||||
|
|
@ -292,7 +292,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
|||
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||
debugLogsToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
|
||||
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
useBetaServerToggle.value = EditorConfigurationCache.Instance.UseBetaServer;
|
||||
UpdateDeploymentSection();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,22 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
|||
clientDropdown.choices = clientNames;
|
||||
if (clientNames.Count > 0)
|
||||
{
|
||||
clientDropdown.index = 0;
|
||||
// Restore last selected client from EditorPrefs
|
||||
string lastClientId = EditorPrefs.GetString(EditorPrefKeys.LastSelectedClientId, string.Empty);
|
||||
int restoredIndex = 0;
|
||||
if (!string.IsNullOrEmpty(lastClientId))
|
||||
{
|
||||
for (int i = 0; i < configurators.Count; i++)
|
||||
{
|
||||
if (string.Equals(configurators[i].Id, lastClientId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
restoredIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clientDropdown.index = restoredIndex;
|
||||
selectedClientIndex = restoredIndex;
|
||||
}
|
||||
|
||||
claudeCliPathRow.style.display = DisplayStyle.None;
|
||||
|
|
@ -111,6 +126,11 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
|||
clientDropdown.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
selectedClientIndex = clientDropdown.index;
|
||||
// Persist the selected client so it's restored on next window open
|
||||
if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count)
|
||||
{
|
||||
EditorPrefs.SetString(EditorPrefKeys.LastSelectedClientId, configurators[selectedClientIndex].Id);
|
||||
}
|
||||
UpdateClientStatus();
|
||||
UpdateManualConfiguration();
|
||||
UpdateClaudeCliPathVisibility();
|
||||
|
|
@ -458,6 +478,9 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
|||
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
||||
bool useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport;
|
||||
string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath();
|
||||
RuntimePlatform platform = Application.platform;
|
||||
bool isRemoteScope = HttpEndpointUtility.IsRemoteScope();
|
||||
string expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
|
@ -466,7 +489,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
|||
if (client is ClaudeCliMcpConfigurator claudeConfigurator)
|
||||
{
|
||||
// Use thread-safe version with captured main-thread values
|
||||
claudeConfigurator.CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, attemptAutoRewrite: false);
|
||||
claudeConfigurator.CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, expectedPackageSource, attemptAutoRewrite: false);
|
||||
}
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
}
|
||||
|
||||
// Initialize version label
|
||||
UpdateVersionLabel(EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true));
|
||||
UpdateVersionLabel(EditorConfigurationCache.Instance.UseBetaServer);
|
||||
|
||||
SetupTabs();
|
||||
|
||||
|
|
|
|||
|
|
@ -256,8 +256,9 @@ class TestUnityInstanceMiddlewareInjection:
|
|||
async def mock_call_next(_ctx):
|
||||
return {"status": "ok"}
|
||||
|
||||
# Mock PluginHub as unavailable - this is sufficient for auto-select to fail
|
||||
# Mock PluginHub as unavailable AND legacy connection pool to prevent fallback discovery
|
||||
with patch("transport.unity_instance_middleware.PluginHub.is_configured", return_value=False):
|
||||
with patch("transport.legacy.unity_connection.get_unity_connection_pool", return_value=None):
|
||||
await middleware.on_call_tool(middleware_ctx, mock_call_next)
|
||||
|
||||
# set_state should not be called for unity_instance if no instance found
|
||||
|
|
@ -329,6 +330,7 @@ class TestAutoSelectInstance:
|
|||
|
||||
with patch("transport.unity_instance_middleware.PluginHub.is_configured", return_value=True):
|
||||
with patch("transport.unity_instance_middleware.PluginHub.get_sessions", new_callable=AsyncMock) as mock_get:
|
||||
with patch("transport.legacy.unity_connection.get_unity_connection_pool", return_value=None):
|
||||
mock_get.return_value = fake_sessions
|
||||
|
||||
instance = await middleware._maybe_autoselect_instance(mock_context)
|
||||
|
|
@ -345,6 +347,7 @@ class TestAutoSelectInstance:
|
|||
|
||||
with patch("transport.unity_instance_middleware.PluginHub.is_configured", return_value=True):
|
||||
with patch("transport.unity_instance_middleware.PluginHub.get_sessions", new_callable=AsyncMock) as mock_get:
|
||||
with patch("transport.legacy.unity_connection.get_unity_connection_pool", return_value=None):
|
||||
mock_get.side_effect = ConnectionError("Plugin hub unavailable")
|
||||
|
||||
# When PluginHub fails, auto-select returns None (graceful fallback)
|
||||
|
|
@ -916,6 +919,7 @@ class TestTransportEdgeCases:
|
|||
|
||||
with patch("transport.unity_instance_middleware.PluginHub.is_configured", return_value=True):
|
||||
with patch("transport.unity_instance_middleware.PluginHub.get_sessions", new_callable=AsyncMock) as mock_get:
|
||||
with patch("transport.legacy.unity_connection.get_unity_connection_pool", return_value=None):
|
||||
mock_get.side_effect = RuntimeError("Unexpected error")
|
||||
|
||||
# Should not raise, just return None
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ namespace MCPForUnityTests.Editor.Helpers
|
|||
private bool _originalHttpTransport;
|
||||
private bool _hadDevForceRefresh;
|
||||
private bool _originalDevForceRefresh;
|
||||
private bool _hadUseBetaServer;
|
||||
private bool _originalUseBetaServer;
|
||||
private IPlatformService _originalPlatformService;
|
||||
|
||||
[OneTimeSetUp]
|
||||
|
|
@ -45,6 +47,8 @@ namespace MCPForUnityTests.Editor.Helpers
|
|||
_originalHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
|
||||
_hadDevForceRefresh = EditorPrefs.HasKey(EditorPrefKeys.DevModeForceServerRefresh);
|
||||
_originalDevForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
_hadUseBetaServer = EditorPrefs.HasKey(EditorPrefKeys.UseBetaServer);
|
||||
_originalUseBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
_originalPlatformService = MCPServiceLocator.Platform;
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +62,10 @@ namespace MCPForUnityTests.Editor.Helpers
|
|||
// Ensure deterministic uvx args ordering for these tests regardless of editor settings
|
||||
// (dev-mode inserts --no-cache/--refresh, which changes the first args).
|
||||
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||
// Tests expect beta server mode (--prerelease explicit --from mcpforunityserver>=0.0.0a0)
|
||||
EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, true);
|
||||
// Refresh the cache so it picks up the test's pref values
|
||||
EditorConfigurationCache.Instance.Refresh();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
@ -108,6 +116,15 @@ namespace MCPForUnityTests.Editor.Helpers
|
|||
{
|
||||
EditorPrefs.DeleteKey(EditorPrefKeys.DevModeForceServerRefresh);
|
||||
}
|
||||
|
||||
if (_hadUseBetaServer)
|
||||
{
|
||||
EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, _originalUseBetaServer);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPrefs.DeleteKey(EditorPrefKeys.UseBetaServer);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue