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