diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 8467b02c5..b316c3194 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -1,4 +1,4 @@ -name: Beta Release (TestPyPI) +name: Beta Release (PyPI Pre-release) concurrency: group: beta-release @@ -12,12 +12,12 @@ on: - "Server/**" jobs: - publish_testpypi: - name: Publish beta to TestPyPI + publish_pypi_prerelease: + name: Publish beta to PyPI (pre-release) runs-on: ubuntu-latest environment: - name: testpypi - url: https://test.pypi.org/p/mcpforunityserver + name: pypi + url: https://pypi.org/p/mcpforunityserver permissions: contents: read id-token: write @@ -33,27 +33,38 @@ jobs: enable-cache: true cache-dependency-glob: "Server/uv.lock" - - name: Generate dev version + - name: Generate beta version id: version shell: bash run: | set -euo pipefail - BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml) - # Use date for unique dev version (PEP 440 compliant: X.Y.Z.devN) - # Note: PyPI/TestPyPI don't support local version identifiers (+...) - DEV_NUMBER="$(date +%Y%m%d%H%M%S)" - DEV_VERSION="${BASE_VERSION}.dev${DEV_NUMBER}" + RAW_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml) + # Strip any existing pre-release suffix (a, b, rc, dev, post) for safe parsing + # e.g., "9.2.0b1" -> "9.2.0", "9.2.0.dev1" -> "9.2.0" + BASE_VERSION=$(echo "$RAW_VERSION" | sed -E 's/(a|b|rc|\.dev|\.post)[0-9]+$//') + # Validate we have a proper X.Y.Z format + if ! [[ "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Could not parse version '$RAW_VERSION' -> '$BASE_VERSION'" >&2 + exit 1 + fi + # Bump minor version and use beta suffix (PEP 440 compliant: X.Y+1.0bN) + # This ensures beta is "newer" than the stable release + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION" + NEXT_MINOR=$((MINOR + 1)) + BETA_NUMBER="$(date +%Y%m%d%H%M%S)" + BETA_VERSION="${MAJOR}.${NEXT_MINOR}.0b${BETA_NUMBER}" + echo "Raw version: $RAW_VERSION" echo "Base version: $BASE_VERSION" - echo "Dev version: $DEV_VERSION" - echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT" + echo "Beta version: $BETA_VERSION" + echo "beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT" - name: Update version for beta release env: - DEV_VERSION: ${{ steps.version.outputs.dev_version }} + BETA_VERSION: ${{ steps.version.outputs.beta_version }} shell: bash run: | set -euo pipefail - sed -i "s/^version = .*/version = \"${DEV_VERSION}\"/" Server/pyproject.toml + sed -i "s/^version = .*/version = \"${BETA_VERSION}\"/" Server/pyproject.toml echo "Updated pyproject.toml:" grep "^version" Server/pyproject.toml @@ -62,8 +73,7 @@ jobs: run: uv build working-directory: ./Server - - name: Publish distribution to TestPyPI + - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: Server/dist/ - repository-url: https://test.pypi.org/legacy/ diff --git a/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta index 8094fb1d0..c3b334d51 100644 --- a/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta +++ b/MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: 489f99ffb7e6743e88e3203552c8b37b \ No newline at end of file +guid: 489f99ffb7e6743e88e3203552c8b37b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs index a6e81b4ac..60f81c1be 100644 --- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs +++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs @@ -28,6 +28,7 @@ internal static class EditorPrefKeys internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl"; internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride"; internal const string DevModeForceServerRefresh = "MCPForUnity.DevModeForceServerRefresh"; + internal const string UseBetaServer = "MCPForUnity.UseBetaServer"; internal const string ProjectScopedToolsLocalHttp = "MCPForUnity.ProjectScopedTools.LocalHttp"; internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath"; diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index c4169da5b..69c017ef2 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -243,6 +243,83 @@ public static (string uvxPath, string fromUrl, string packageName) GetUvxCommand return (uvxPath, fromUrl, packageName); } + /// + /// Builds the uvx package source arguments for the MCP server. + /// 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. + /// + /// Whether to quote the --from path (needed for command-line strings, not for arg lists) + /// The package source arguments (e.g., "--prerelease explicit --from mcpforunityserver>=0.0.0a0") + 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)) + { + return $"--from {fromUrl}"; + } + + // 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, + // not of dependencies (which can be broken on PyPI). + string fromValue = quoteFromPath ? "\"mcpforunityserver>=0.0.0a0\"" : "mcpforunityserver>=0.0.0a0"; + return $"--prerelease explicit --from {fromValue}"; + } + + // Standard mode: use pinned version from package.json + if (!string.IsNullOrEmpty(fromUrl)) + { + return $"--from {fromUrl}"; + } + + return string.Empty; + } + + /// + /// Builds the uvx package source arguments as a list (for JSON config builders). + /// Priority: explicit fromUrl override > beta server mode > default package. + /// + /// List of arguments to add to uvx command + public static System.Collections.Generic.IList GetBetaServerFromArgsList() + { + var args = new System.Collections.Generic.List(); + + // Explicit override (local path, git URL, etc.) always wins + string fromUrl = GetMcpServerPackageSource(); + string overrideUrl = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); + if (!string.IsNullOrEmpty(overrideUrl)) + { + args.Add("--from"); + args.Add(fromUrl); + return args; + } + + // Beta server mode: use prerelease from PyPI + bool useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true); + 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 + if (!string.IsNullOrEmpty(fromUrl)) + { + args.Add("--from"); + args.Add(fromUrl); + } + + return args; + } + /// /// Determines whether uvx should use --no-cache --refresh flags. /// Returns true if DevModeForceServerRefresh is enabled OR if the server URL is a local path. diff --git a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs index dd00d73d0..118ef083b 100644 --- a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs +++ b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Clients.Configurators; +using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Models; using Newtonsoft.Json; @@ -170,10 +170,11 @@ private static IList BuildUvxArgs(string fromUrl, string packageName) args.Add("--no-cache"); args.Add("--refresh"); } - if (!string.IsNullOrEmpty(fromUrl)) + + // Use centralized helper for beta server / prerelease args + foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList()) { - args.Add("--from"); - args.Add(fromUrl); + args.Add(arg); } args.Add(packageName); diff --git a/MCPForUnity/Editor/Services/ServerManagementService.cs b/MCPForUnity/Editor/Services/ServerManagementService.cs index b9e5ecd69..bb512832d 100644 --- a/MCPForUnity/Editor/Services/ServerManagementService.cs +++ b/MCPForUnity/Editor/Services/ServerManagementService.cs @@ -1317,9 +1317,13 @@ private bool TryGetLocalHttpServerCommandParts(out string fileName, out string a true ); string scopedFlag = projectScopedTools ? " --project-scoped-tools" : string.Empty; - string args = string.IsNullOrEmpty(fromUrl) + + // Use centralized helper for beta server / prerelease args + string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true); + + string args = string.IsNullOrEmpty(fromArgs) ? $"{devFlags}{packageName} --transport http --http-url {httpUrl}{scopedFlag}" - : $"{devFlags}--from {fromUrl} {packageName} --transport http --http-url {httpUrl}{scopedFlag}"; + : $"{devFlags}{fromArgs} {packageName} --transport http --http-url {httpUrl}{scopedFlag}"; fileName = uvxPath; arguments = args; diff --git a/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs index 26f610167..8898fcf23 100644 --- a/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs @@ -26,6 +26,7 @@ public class McpAdvancedSection private Button clearGitUrlButton; private Toggle debugLogsToggle; private Toggle devModeForceRefreshToggle; + private Toggle useBetaServerToggle; private TextField deploySourcePath; private Button browseDeploySourceButton; private Button clearDeploySourceButton; @@ -42,6 +43,7 @@ public class McpAdvancedSection public event Action OnGitUrlChanged; public event Action OnHttpServerCommandUpdateRequested; public event Action OnTestConnectionRequested; + public event Action OnBetaModeChanged; public VisualElement Root { get; private set; } @@ -64,6 +66,7 @@ private void CacheUIElements() clearGitUrlButton = Root.Q