feat: Add beta server mode with PyPI pre-release support (#640)
* feat: add TestPyPI toggle for pre-release server package testing - Add UseTestPyPI editor preference key - Add TestPyPI toggle to Advanced settings UI with tooltip - Configure uvx to use test.pypi.org when TestPyPI mode enabled - Skip version pinning in TestPyPI mode to get latest pre-release - Update ConfigJsonBuilder to handle TestPyPI index URL * Update .meta file * fix: Use PyPI pre-release versions instead of TestPyPI for beta server TestPyPI has polluted packages (broken httpx, mcp, fastapi) that cause server startup failures. Switch to publishing beta versions directly to PyPI as pre-releases (e.g., 9.3.0b20260127). Key changes: - beta-release.yml: Publish to PyPI instead of TestPyPI, use beta suffix - Use --prerelease explicit with version specifier (>=0.0.0a0) to only get prereleases of our package, not broken dependency prereleases - Default "Use Beta Server" toggle to true on beta branch - Rename UI label from "Use TestPyPI" to "Use Beta Server" - Add UseTestPyPI to EditorPrefsWindow known prefs - Add search field and refresh button to EditorPrefsWindow Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: Add beta mode indicator to UI badge and server version logging - Show "β" suffix on version badge when beta server mode is enabled - Badge updates dynamically when toggle changes - Add server version to startup log: "MCP for Unity Server v9.2.0 starting up" - Add version field to /health endpoint response Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: Rename UseTestPyPI to UseBetaServer and fix EditorPrefs margin - Rename EditorPref key from UseTestPyPI to UseBetaServer for clarity - Rename all related variables and UXML element names - Increase bottom margin on EditorPrefs search bar to prevent clipping first entry Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * refactor: Address code review feedback - Centralize beta server uvx args in AssetPathUtility.GetBetaServerFromArgs() to avoid duplication between HTTP and stdio transports - Cache server version at startup instead of calling get_package_version() on every /health request - Add robustness to beta version parsing in workflow: strip existing pre-release suffix and validate X.Y.Z format before parsing Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Prioritize explicit fromUrl override and optimize search filter - GetBetaServerFromArgs/GetBetaServerFromArgsList now check for explicit GitUrlOverride before applying beta server mode, ensuring local dev paths and custom URLs are honored - EditorPrefsWindow search filter uses IndexOf with OrdinalIgnoreCase instead of ToLowerInvariant().Contains() for fewer allocations Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Marcus Sanatan <msanatan@gmail.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>main
parent
7685db8844
commit
17c6a36c8d
|
|
@ -1,4 +1,4 @@
|
||||||
name: Beta Release (TestPyPI)
|
name: Beta Release (PyPI Pre-release)
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: beta-release
|
group: beta-release
|
||||||
|
|
@ -12,12 +12,12 @@ on:
|
||||||
- "Server/**"
|
- "Server/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish_testpypi:
|
publish_pypi_prerelease:
|
||||||
name: Publish beta to TestPyPI
|
name: Publish beta to PyPI (pre-release)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment:
|
environment:
|
||||||
name: testpypi
|
name: pypi
|
||||||
url: https://test.pypi.org/p/mcpforunityserver
|
url: https://pypi.org/p/mcpforunityserver
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
@ -33,27 +33,38 @@ jobs:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: "Server/uv.lock"
|
cache-dependency-glob: "Server/uv.lock"
|
||||||
|
|
||||||
- name: Generate dev version
|
- name: Generate beta version
|
||||||
id: version
|
id: version
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
BASE_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
|
RAW_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
|
||||||
# Use date for unique dev version (PEP 440 compliant: X.Y.Z.devN)
|
# Strip any existing pre-release suffix (a, b, rc, dev, post) for safe parsing
|
||||||
# Note: PyPI/TestPyPI don't support local version identifiers (+...)
|
# e.g., "9.2.0b1" -> "9.2.0", "9.2.0.dev1" -> "9.2.0"
|
||||||
DEV_NUMBER="$(date +%Y%m%d%H%M%S)"
|
BASE_VERSION=$(echo "$RAW_VERSION" | sed -E 's/(a|b|rc|\.dev|\.post)[0-9]+$//')
|
||||||
DEV_VERSION="${BASE_VERSION}.dev${DEV_NUMBER}"
|
# 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 "Base version: $BASE_VERSION"
|
||||||
echo "Dev version: $DEV_VERSION"
|
echo "Beta version: $BETA_VERSION"
|
||||||
echo "dev_version=$DEV_VERSION" >> "$GITHUB_OUTPUT"
|
echo "beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Update version for beta release
|
- name: Update version for beta release
|
||||||
env:
|
env:
|
||||||
DEV_VERSION: ${{ steps.version.outputs.dev_version }}
|
BETA_VERSION: ${{ steps.version.outputs.beta_version }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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:"
|
echo "Updated pyproject.toml:"
|
||||||
grep "^version" Server/pyproject.toml
|
grep "^version" Server/pyproject.toml
|
||||||
|
|
||||||
|
|
@ -62,8 +73,7 @@ jobs:
|
||||||
run: uv build
|
run: uv build
|
||||||
working-directory: ./Server
|
working-directory: ./Server
|
||||||
|
|
||||||
- name: Publish distribution to TestPyPI
|
- name: Publish distribution to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
packages-dir: Server/dist/
|
packages-dir: Server/dist/
|
||||||
repository-url: https://test.pypi.org/legacy/
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,11 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 489f99ffb7e6743e88e3203552c8b37b
|
guid: 489f99ffb7e6743e88e3203552c8b37b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace MCPForUnity.Editor.Constants
|
||||||
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
|
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
|
||||||
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
|
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
|
||||||
internal const string DevModeForceServerRefresh = "MCPForUnity.DevModeForceServerRefresh";
|
internal const string DevModeForceServerRefresh = "MCPForUnity.DevModeForceServerRefresh";
|
||||||
|
internal const string UseBetaServer = "MCPForUnity.UseBetaServer";
|
||||||
internal const string ProjectScopedToolsLocalHttp = "MCPForUnity.ProjectScopedTools.LocalHttp";
|
internal const string ProjectScopedToolsLocalHttp = "MCPForUnity.ProjectScopedTools.LocalHttp";
|
||||||
|
|
||||||
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";
|
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,83 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
return (uvxPath, fromUrl, packageName);
|
return (uvxPath, fromUrl, packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </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))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the uvx package source arguments as a list (for JSON config builders).
|
||||||
|
/// Priority: explicit fromUrl override > beta server mode > default package.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of arguments to add to uvx command</returns>
|
||||||
|
public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList()
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether uvx should use --no-cache --refresh flags.
|
/// Determines whether uvx should use --no-cache --refresh flags.
|
||||||
/// Returns true if DevModeForceServerRefresh is enabled OR if the server URL is a local path.
|
/// Returns true if DevModeForceServerRefresh is enabled OR if the server URL is a local path.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MCPForUnity.Editor.Constants;
|
|
||||||
using MCPForUnity.Editor.Clients.Configurators;
|
using MCPForUnity.Editor.Clients.Configurators;
|
||||||
|
using MCPForUnity.Editor.Constants;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Models;
|
using MCPForUnity.Editor.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -170,10 +170,11 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
args.Add("--no-cache");
|
args.Add("--no-cache");
|
||||||
args.Add("--refresh");
|
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(arg);
|
||||||
args.Add(fromUrl);
|
|
||||||
}
|
}
|
||||||
args.Add(packageName);
|
args.Add(packageName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1317,9 +1317,13 @@ namespace MCPForUnity.Editor.Services
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
string scopedFlag = projectScopedTools ? " --project-scoped-tools" : string.Empty;
|
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}{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;
|
fileName = uvxPath;
|
||||||
arguments = args;
|
arguments = args;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
private Button clearGitUrlButton;
|
private Button clearGitUrlButton;
|
||||||
private Toggle debugLogsToggle;
|
private Toggle debugLogsToggle;
|
||||||
private Toggle devModeForceRefreshToggle;
|
private Toggle devModeForceRefreshToggle;
|
||||||
|
private Toggle useBetaServerToggle;
|
||||||
private TextField deploySourcePath;
|
private TextField deploySourcePath;
|
||||||
private Button browseDeploySourceButton;
|
private Button browseDeploySourceButton;
|
||||||
private Button clearDeploySourceButton;
|
private Button clearDeploySourceButton;
|
||||||
|
|
@ -42,6 +43,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
public event Action OnGitUrlChanged;
|
public event Action OnGitUrlChanged;
|
||||||
public event Action OnHttpServerCommandUpdateRequested;
|
public event Action OnHttpServerCommandUpdateRequested;
|
||||||
public event Action OnTestConnectionRequested;
|
public event Action OnTestConnectionRequested;
|
||||||
|
public event Action<bool> OnBetaModeChanged;
|
||||||
|
|
||||||
public VisualElement Root { get; private set; }
|
public VisualElement Root { get; private set; }
|
||||||
|
|
||||||
|
|
@ -64,6 +66,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
|
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
|
||||||
debugLogsToggle = Root.Q<Toggle>("debug-logs-toggle");
|
debugLogsToggle = Root.Q<Toggle>("debug-logs-toggle");
|
||||||
devModeForceRefreshToggle = Root.Q<Toggle>("dev-mode-force-refresh-toggle");
|
devModeForceRefreshToggle = Root.Q<Toggle>("dev-mode-force-refresh-toggle");
|
||||||
|
useBetaServerToggle = Root.Q<Toggle>("use-beta-server-toggle");
|
||||||
deploySourcePath = Root.Q<TextField>("deploy-source-path");
|
deploySourcePath = Root.Q<TextField>("deploy-source-path");
|
||||||
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
|
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
|
||||||
clearDeploySourceButton = Root.Q<Button>("clear-deploy-source-button");
|
clearDeploySourceButton = Root.Q<Button>("clear-deploy-source-button");
|
||||||
|
|
@ -98,6 +101,13 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
if (forceRefreshLabel != null)
|
if (forceRefreshLabel != null)
|
||||||
forceRefreshLabel.tooltip = devModeForceRefreshToggle.tooltip;
|
forceRefreshLabel.tooltip = devModeForceRefreshToggle.tooltip;
|
||||||
}
|
}
|
||||||
|
if (useBetaServerToggle != null)
|
||||||
|
{
|
||||||
|
useBetaServerToggle.tooltip = "When enabled, uvx will fetch the latest beta server version from PyPI. Enable this on the beta branch to get the matching server version.";
|
||||||
|
var betaServerLabel = useBetaServerToggle?.parent?.Q<Label>();
|
||||||
|
if (betaServerLabel != null)
|
||||||
|
betaServerLabel.tooltip = useBetaServerToggle.tooltip;
|
||||||
|
}
|
||||||
if (testConnectionButton != null)
|
if (testConnectionButton != null)
|
||||||
testConnectionButton.tooltip = "Test the connection between Unity and the MCP server.";
|
testConnectionButton.tooltip = "Test the connection between Unity and the MCP server.";
|
||||||
if (deploySourcePath != null)
|
if (deploySourcePath != null)
|
||||||
|
|
@ -128,6 +138,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
McpLog.SetDebugLoggingEnabled(debugEnabled);
|
McpLog.SetDebugLoggingEnabled(debugEnabled);
|
||||||
|
|
||||||
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
|
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||||
UpdatePathOverrides();
|
UpdatePathOverrides();
|
||||||
UpdateDeploymentSection();
|
UpdateDeploymentSection();
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +183,13 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
OnHttpServerCommandUpdateRequested?.Invoke();
|
OnHttpServerCommandUpdateRequested?.Invoke();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useBetaServerToggle.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, evt.newValue);
|
||||||
|
OnHttpServerCommandUpdateRequested?.Invoke();
|
||||||
|
OnBetaModeChanged?.Invoke(evt.newValue);
|
||||||
|
});
|
||||||
|
|
||||||
deploySourcePath.RegisterValueChangedCallback(evt =>
|
deploySourcePath.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
string path = evt.newValue?.Trim();
|
string path = evt.newValue?.Trim();
|
||||||
|
|
@ -274,6 +292,7 @@ namespace MCPForUnity.Editor.Windows.Components.Advanced
|
||||||
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||||
debugLogsToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
|
debugLogsToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
|
||||||
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
|
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
|
||||||
UpdateDeploymentSection();
|
UpdateDeploymentSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@
|
||||||
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
|
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
|
<ui:VisualElement class="setting-row">
|
||||||
|
<ui:Label text="Use Beta Server:" class="setting-label" />
|
||||||
|
<ui:Toggle name="use-beta-server-toggle" class="setting-toggle" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
|
||||||
<ui:VisualElement class="override-row" style="margin-top: 8px;">
|
<ui:VisualElement class="override-row" style="margin-top: 8px;">
|
||||||
<ui:Label text="Package Source:" class="override-label" />
|
<ui:Label text="Package Source:" class="override-label" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ namespace MCPForUnity.Editor.Windows
|
||||||
// UI Elements
|
// UI Elements
|
||||||
private ScrollView scrollView;
|
private ScrollView scrollView;
|
||||||
private VisualElement prefsContainer;
|
private VisualElement prefsContainer;
|
||||||
|
private TextField searchField;
|
||||||
|
private string searchFilter = "";
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private List<EditorPrefItem> currentPrefs = new List<EditorPrefItem>();
|
private List<EditorPrefItem> currentPrefs = new List<EditorPrefItem>();
|
||||||
|
|
@ -40,6 +42,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
{ EditorPrefKeys.CustomToolRegistrationEnabled, EditorPrefType.Bool },
|
{ EditorPrefKeys.CustomToolRegistrationEnabled, EditorPrefType.Bool },
|
||||||
{ EditorPrefKeys.TelemetryDisabled, EditorPrefType.Bool },
|
{ EditorPrefKeys.TelemetryDisabled, EditorPrefType.Bool },
|
||||||
{ EditorPrefKeys.DevModeForceServerRefresh, EditorPrefType.Bool },
|
{ EditorPrefKeys.DevModeForceServerRefresh, EditorPrefType.Bool },
|
||||||
|
{ EditorPrefKeys.UseBetaServer, EditorPrefType.Bool },
|
||||||
{ EditorPrefKeys.ProjectScopedToolsLocalHttp, EditorPrefType.Bool },
|
{ EditorPrefKeys.ProjectScopedToolsLocalHttp, EditorPrefType.Bool },
|
||||||
|
|
||||||
// Integer prefs
|
// Integer prefs
|
||||||
|
|
@ -106,6 +109,38 @@ namespace MCPForUnity.Editor.Windows
|
||||||
|
|
||||||
visualTree.CloneTree(rootVisualElement);
|
visualTree.CloneTree(rootVisualElement);
|
||||||
|
|
||||||
|
// Add search bar container at the top
|
||||||
|
var searchContainer = new VisualElement();
|
||||||
|
searchContainer.style.flexDirection = FlexDirection.Row;
|
||||||
|
searchContainer.style.marginTop = 8;
|
||||||
|
searchContainer.style.marginBottom = 20;
|
||||||
|
searchContainer.style.marginLeft = 4;
|
||||||
|
searchContainer.style.marginRight = 4;
|
||||||
|
|
||||||
|
searchField = new TextField("Search");
|
||||||
|
searchField.style.flexGrow = 1;
|
||||||
|
searchField.style.height = 28;
|
||||||
|
searchField.style.paddingTop = 2;
|
||||||
|
searchField.style.paddingBottom = 2;
|
||||||
|
searchField.labelElement.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
searchField.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
searchFilter = evt.newValue ?? "";
|
||||||
|
RefreshPrefs();
|
||||||
|
});
|
||||||
|
|
||||||
|
var refreshButton = new Button(RefreshPrefs);
|
||||||
|
refreshButton.text = "↻";
|
||||||
|
refreshButton.tooltip = "Refresh prefs";
|
||||||
|
refreshButton.style.width = 30;
|
||||||
|
refreshButton.style.height = 28;
|
||||||
|
refreshButton.style.marginLeft = 6;
|
||||||
|
refreshButton.style.backgroundColor = new Color(0.9f, 0.5f, 0.1f);
|
||||||
|
|
||||||
|
searchContainer.Add(searchField);
|
||||||
|
searchContainer.Add(refreshButton);
|
||||||
|
rootVisualElement.Insert(0, searchContainer);
|
||||||
|
|
||||||
// Get references
|
// Get references
|
||||||
scrollView = rootVisualElement.Q<ScrollView>("scroll-view");
|
scrollView = rootVisualElement.Q<ScrollView>("scroll-view");
|
||||||
prefsContainer = rootVisualElement.Q<VisualElement>("prefs-container");
|
prefsContainer = rootVisualElement.Q<VisualElement>("prefs-container");
|
||||||
|
|
@ -155,12 +190,22 @@ namespace MCPForUnity.Editor.Windows
|
||||||
// Sort keys
|
// Sort keys
|
||||||
allKeys.Sort();
|
allKeys.Sort();
|
||||||
|
|
||||||
|
// Pre-trim filter once outside the loop
|
||||||
|
var filter = searchFilter?.Trim();
|
||||||
|
|
||||||
// Create items for existing prefs
|
// Create items for existing prefs
|
||||||
foreach (var key in allKeys)
|
foreach (var key in allKeys)
|
||||||
{
|
{
|
||||||
// Skip Customer UUID but show everything else that's defined
|
// Skip Customer UUID but show everything else that's defined
|
||||||
if (key != EditorPrefKeys.CustomerUuid)
|
if (key != EditorPrefKeys.CustomerUuid)
|
||||||
{
|
{
|
||||||
|
// Apply search filter using OrdinalIgnoreCase for fewer allocations
|
||||||
|
if (!string.IsNullOrEmpty(filter) &&
|
||||||
|
key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var item = CreateEditorPrefItem(key);
|
var item = CreateEditorPrefItem(key);
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize version label
|
// Initialize version label
|
||||||
if (versionLabel != null)
|
UpdateVersionLabel(EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true));
|
||||||
{
|
|
||||||
string version = AssetPathUtility.GetPackageVersion();
|
|
||||||
versionLabel.text = $"v{version}";
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupTabs();
|
SetupTabs();
|
||||||
|
|
||||||
|
|
@ -252,6 +248,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
if (connectionSection != null)
|
if (connectionSection != null)
|
||||||
await connectionSection.VerifyBridgeConnectionAsync();
|
await connectionSection.VerifyBridgeConnectionAsync();
|
||||||
};
|
};
|
||||||
|
advancedSection.OnBetaModeChanged += UpdateVersionLabel;
|
||||||
|
|
||||||
// Wire up health status updates from Connection to Advanced
|
// Wire up health status updates from Connection to Advanced
|
||||||
connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) =>
|
connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) =>
|
||||||
|
|
@ -288,6 +285,20 @@ namespace MCPForUnity.Editor.Windows
|
||||||
RefreshAllData();
|
RefreshAllData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateVersionLabel(bool useBetaServer)
|
||||||
|
{
|
||||||
|
if (versionLabel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string version = AssetPathUtility.GetPackageVersion();
|
||||||
|
versionLabel.text = useBetaServer ? $"v{version} β" : $"v{version}";
|
||||||
|
versionLabel.tooltip = useBetaServer
|
||||||
|
? "Beta server mode - fetching pre-release server versions from PyPI"
|
||||||
|
: $"MCP For Unity v{version}";
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureToolsLoaded()
|
private void EnsureToolsLoaded()
|
||||||
{
|
{
|
||||||
if (toolsLoaded)
|
if (toolsLoaded)
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@ except Exception:
|
||||||
_unity_connection_pool: UnityConnectionPool | None = None
|
_unity_connection_pool: UnityConnectionPool | None = None
|
||||||
_plugin_registry: PluginRegistry | None = None
|
_plugin_registry: PluginRegistry | None = None
|
||||||
|
|
||||||
|
# Cached server version (set at startup to avoid repeated I/O)
|
||||||
|
_server_version: str | None = None
|
||||||
|
|
||||||
# In-memory custom tool service initialized after MCP construction
|
# In-memory custom tool service initialized after MCP construction
|
||||||
custom_tool_service: CustomToolService | None = None
|
custom_tool_service: CustomToolService | None = None
|
||||||
|
|
||||||
|
|
@ -133,8 +136,9 @@ custom_tool_service: CustomToolService | None = None
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def server_lifespan(server: FastMCP) -> AsyncIterator[dict[str, Any]]:
|
async def server_lifespan(server: FastMCP) -> AsyncIterator[dict[str, Any]]:
|
||||||
"""Handle server startup and shutdown."""
|
"""Handle server startup and shutdown."""
|
||||||
global _unity_connection_pool
|
global _unity_connection_pool, _server_version
|
||||||
logger.info("MCP for Unity Server starting up")
|
_server_version = get_package_version()
|
||||||
|
logger.info(f"MCP for Unity Server v{_server_version} starting up")
|
||||||
|
|
||||||
# Register custom tool management endpoints with FastMCP
|
# Register custom tool management endpoints with FastMCP
|
||||||
# Routes are declared globally below after FastMCP initialization
|
# Routes are declared globally below after FastMCP initialization
|
||||||
|
|
@ -158,13 +162,12 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[dict[str, Any]]:
|
||||||
# Record server startup telemetry
|
# Record server startup telemetry
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
start_clk = time.perf_counter()
|
start_clk = time.perf_counter()
|
||||||
server_version = get_package_version()
|
|
||||||
# Defer initial telemetry by 1s to avoid stdio handshake interference
|
# Defer initial telemetry by 1s to avoid stdio handshake interference
|
||||||
|
|
||||||
def _emit_startup():
|
def _emit_startup():
|
||||||
try:
|
try:
|
||||||
record_telemetry(RecordType.STARTUP, {
|
record_telemetry(RecordType.STARTUP, {
|
||||||
"server_version": server_version,
|
"server_version": _server_version,
|
||||||
"startup_time": start_time,
|
"startup_time": start_time,
|
||||||
})
|
})
|
||||||
record_milestone(MilestoneType.FIRST_STARTUP)
|
record_milestone(MilestoneType.FIRST_STARTUP)
|
||||||
|
|
@ -325,6 +328,7 @@ def create_mcp_server(project_scoped_tools: bool) -> FastMCP:
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
|
"version": _server_version or "unknown",
|
||||||
"message": "MCP for Unity server is running"
|
"message": "MCP for Unity server is running"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue