Skip to content

Commit b488196

Browse files
authored
Merge branch 'CoplayDev:beta' into fix/asyncio-socket-accept-error
2 parents 6df7cdf + d6b497f commit b488196

19 files changed

Lines changed: 374 additions & 79 deletions

File tree

.github/workflows/beta-release.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Beta Release (PyPI Pre-release)
2+
3+
concurrency:
4+
group: beta-release
5+
cancel-in-progress: true
6+
7+
on:
8+
push:
9+
branches:
10+
- beta
11+
paths:
12+
- "Server/**"
13+
14+
jobs:
15+
publish_pypi_prerelease:
16+
name: Publish beta to PyPI (pre-release)
17+
runs-on: ubuntu-latest
18+
environment:
19+
name: pypi
20+
url: https://pypi.org/p/mcpforunityserver
21+
permissions:
22+
contents: read
23+
id-token: write
24+
steps:
25+
- uses: actions/checkout@v6
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Install uv
30+
uses: astral-sh/setup-uv@v7
31+
with:
32+
version: "latest"
33+
enable-cache: true
34+
cache-dependency-glob: "Server/uv.lock"
35+
36+
- name: Generate beta version
37+
id: version
38+
shell: bash
39+
run: |
40+
set -euo pipefail
41+
RAW_VERSION=$(grep -oP '(?<=version = ")[^"]+' Server/pyproject.toml)
42+
# Strip any existing pre-release suffix (a, b, rc, dev, post) for safe parsing
43+
# e.g., "9.2.0b1" -> "9.2.0", "9.2.0.dev1" -> "9.2.0"
44+
BASE_VERSION=$(echo "$RAW_VERSION" | sed -E 's/(a|b|rc|\.dev|\.post)[0-9]+$//')
45+
# Validate we have a proper X.Y.Z format
46+
if ! [[ "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
47+
echo "Error: Could not parse version '$RAW_VERSION' -> '$BASE_VERSION'" >&2
48+
exit 1
49+
fi
50+
# Bump minor version and use beta suffix (PEP 440 compliant: X.Y+1.0bN)
51+
# This ensures beta is "newer" than the stable release
52+
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
53+
NEXT_MINOR=$((MINOR + 1))
54+
BETA_NUMBER="$(date +%Y%m%d%H%M%S)"
55+
BETA_VERSION="${MAJOR}.${NEXT_MINOR}.0b${BETA_NUMBER}"
56+
echo "Raw version: $RAW_VERSION"
57+
echo "Base version: $BASE_VERSION"
58+
echo "Beta version: $BETA_VERSION"
59+
echo "beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT"
60+
61+
- name: Update version for beta release
62+
env:
63+
BETA_VERSION: ${{ steps.version.outputs.beta_version }}
64+
shell: bash
65+
run: |
66+
set -euo pipefail
67+
sed -i "s/^version = .*/version = \"${BETA_VERSION}\"/" Server/pyproject.toml
68+
echo "Updated pyproject.toml:"
69+
grep "^version" Server/pyproject.toml
70+
71+
- name: Build a binary wheel and a source tarball
72+
shell: bash
73+
run: uv build
74+
working-directory: ./Server
75+
76+
- name: Publish distribution to PyPI
77+
uses: pypa/gh-action-pypi-publish@release/v1
78+
with:
79+
packages-dir: Server/dist/

MCPForUnity/Editor/Clients/Configurators/OpenCodeConfigurator.cs.meta

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MCPForUnity/Editor/Constants/EditorPrefKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal static class EditorPrefKeys
2828
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
2929
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
3030
internal const string DevModeForceServerRefresh = "MCPForUnity.DevModeForceServerRefresh";
31+
internal const string UseBetaServer = "MCPForUnity.UseBetaServer";
3132
internal const string ProjectScopedToolsLocalHttp = "MCPForUnity.ProjectScopedTools.LocalHttp";
3233

3334
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";

MCPForUnity/Editor/Helpers/AssetPathUtility.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,83 @@ public static (string uvxPath, string fromUrl, string packageName) GetUvxCommand
243243
return (uvxPath, fromUrl, packageName);
244244
}
245245

246+
/// <summary>
247+
/// Builds the uvx package source arguments for the MCP server.
248+
/// Handles beta server mode (prerelease from PyPI) vs standard mode (pinned version or override).
249+
/// Centralizes the prerelease logic to avoid duplication between HTTP and stdio transports.
250+
/// Priority: explicit fromUrl override > beta server mode > default package.
251+
/// </summary>
252+
/// <param name="quoteFromPath">Whether to quote the --from path (needed for command-line strings, not for arg lists)</param>
253+
/// <returns>The package source arguments (e.g., "--prerelease explicit --from mcpforunityserver>=0.0.0a0")</returns>
254+
public static string GetBetaServerFromArgs(bool quoteFromPath = false)
255+
{
256+
// Explicit override (local path, git URL, etc.) always wins
257+
string fromUrl = GetMcpServerPackageSource();
258+
string overrideUrl = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
259+
if (!string.IsNullOrEmpty(overrideUrl))
260+
{
261+
return $"--from {fromUrl}";
262+
}
263+
264+
// Beta server mode: use prerelease from PyPI
265+
bool useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
266+
if (useBetaServer)
267+
{
268+
// Use --prerelease explicit with version specifier to only get prereleases of our package,
269+
// not of dependencies (which can be broken on PyPI).
270+
string fromValue = quoteFromPath ? "\"mcpforunityserver>=0.0.0a0\"" : "mcpforunityserver>=0.0.0a0";
271+
return $"--prerelease explicit --from {fromValue}";
272+
}
273+
274+
// Standard mode: use pinned version from package.json
275+
if (!string.IsNullOrEmpty(fromUrl))
276+
{
277+
return $"--from {fromUrl}";
278+
}
279+
280+
return string.Empty;
281+
}
282+
283+
/// <summary>
284+
/// Builds the uvx package source arguments as a list (for JSON config builders).
285+
/// Priority: explicit fromUrl override > beta server mode > default package.
286+
/// </summary>
287+
/// <returns>List of arguments to add to uvx command</returns>
288+
public static System.Collections.Generic.IList<string> GetBetaServerFromArgsList()
289+
{
290+
var args = new System.Collections.Generic.List<string>();
291+
292+
// Explicit override (local path, git URL, etc.) always wins
293+
string fromUrl = GetMcpServerPackageSource();
294+
string overrideUrl = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
295+
if (!string.IsNullOrEmpty(overrideUrl))
296+
{
297+
args.Add("--from");
298+
args.Add(fromUrl);
299+
return args;
300+
}
301+
302+
// Beta server mode: use prerelease from PyPI
303+
bool useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
304+
if (useBetaServer)
305+
{
306+
args.Add("--prerelease");
307+
args.Add("explicit");
308+
args.Add("--from");
309+
args.Add("mcpforunityserver>=0.0.0a0");
310+
return args;
311+
}
312+
313+
// Standard mode: use pinned version from package.json
314+
if (!string.IsNullOrEmpty(fromUrl))
315+
{
316+
args.Add("--from");
317+
args.Add(fromUrl);
318+
}
319+
320+
return args;
321+
}
322+
246323
/// <summary>
247324
/// Determines whether uvx should use --no-cache --refresh flags.
248325
/// Returns true if DevModeForceServerRefresh is enabled OR if the server URL is a local path.

MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5-
using MCPForUnity.Editor.Constants;
65
using MCPForUnity.Editor.Clients.Configurators;
6+
using MCPForUnity.Editor.Constants;
77
using MCPForUnity.Editor.Helpers;
88
using MCPForUnity.Editor.Models;
99
using Newtonsoft.Json;
@@ -170,10 +170,11 @@ private static IList<string> BuildUvxArgs(string fromUrl, string packageName)
170170
args.Add("--no-cache");
171171
args.Add("--refresh");
172172
}
173-
if (!string.IsNullOrEmpty(fromUrl))
173+
174+
// Use centralized helper for beta server / prerelease args
175+
foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList())
174176
{
175-
args.Add("--from");
176-
args.Add(fromUrl);
177+
args.Add(arg);
177178
}
178179
args.Add(packageName);
179180

MCPForUnity/Editor/Services/ServerManagementService.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,9 +1317,13 @@ private bool TryGetLocalHttpServerCommandParts(out string fileName, out string a
13171317
true
13181318
);
13191319
string scopedFlag = projectScopedTools ? " --project-scoped-tools" : string.Empty;
1320-
string args = string.IsNullOrEmpty(fromUrl)
1320+
1321+
// Use centralized helper for beta server / prerelease args
1322+
string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);
1323+
1324+
string args = string.IsNullOrEmpty(fromArgs)
13211325
? $"{devFlags}{packageName} --transport http --http-url {httpUrl}{scopedFlag}"
1322-
: $"{devFlags}--from {fromUrl} {packageName} --transport http --http-url {httpUrl}{scopedFlag}";
1326+
: $"{devFlags}{fromArgs} {packageName} --transport http --http-url {httpUrl}{scopedFlag}";
13231327

13241328
fileName = uvxPath;
13251329
arguments = args;

MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class McpAdvancedSection
2626
private Button clearGitUrlButton;
2727
private Toggle debugLogsToggle;
2828
private Toggle devModeForceRefreshToggle;
29+
private Toggle useBetaServerToggle;
2930
private TextField deploySourcePath;
3031
private Button browseDeploySourceButton;
3132
private Button clearDeploySourceButton;
@@ -42,6 +43,7 @@ public class McpAdvancedSection
4243
public event Action OnGitUrlChanged;
4344
public event Action OnHttpServerCommandUpdateRequested;
4445
public event Action OnTestConnectionRequested;
46+
public event Action<bool> OnBetaModeChanged;
4547

4648
public VisualElement Root { get; private set; }
4749

@@ -64,6 +66,7 @@ private void CacheUIElements()
6466
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
6567
debugLogsToggle = Root.Q<Toggle>("debug-logs-toggle");
6668
devModeForceRefreshToggle = Root.Q<Toggle>("dev-mode-force-refresh-toggle");
69+
useBetaServerToggle = Root.Q<Toggle>("use-beta-server-toggle");
6770
deploySourcePath = Root.Q<TextField>("deploy-source-path");
6871
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
6972
clearDeploySourceButton = Root.Q<Button>("clear-deploy-source-button");
@@ -98,6 +101,13 @@ private void InitializeUI()
98101
if (forceRefreshLabel != null)
99102
forceRefreshLabel.tooltip = devModeForceRefreshToggle.tooltip;
100103
}
104+
if (useBetaServerToggle != null)
105+
{
106+
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.";
107+
var betaServerLabel = useBetaServerToggle?.parent?.Q<Label>();
108+
if (betaServerLabel != null)
109+
betaServerLabel.tooltip = useBetaServerToggle.tooltip;
110+
}
101111
if (testConnectionButton != null)
102112
testConnectionButton.tooltip = "Test the connection between Unity and the MCP server.";
103113
if (deploySourcePath != null)
@@ -128,6 +138,7 @@ private void InitializeUI()
128138
McpLog.SetDebugLoggingEnabled(debugEnabled);
129139

130140
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
141+
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
131142
UpdatePathOverrides();
132143
UpdateDeploymentSection();
133144
}
@@ -172,6 +183,13 @@ private void RegisterCallbacks()
172183
OnHttpServerCommandUpdateRequested?.Invoke();
173184
});
174185

186+
useBetaServerToggle.RegisterValueChangedCallback(evt =>
187+
{
188+
EditorPrefs.SetBool(EditorPrefKeys.UseBetaServer, evt.newValue);
189+
OnHttpServerCommandUpdateRequested?.Invoke();
190+
OnBetaModeChanged?.Invoke(evt.newValue);
191+
});
192+
175193
deploySourcePath.RegisterValueChangedCallback(evt =>
176194
{
177195
string path = evt.newValue?.Trim();
@@ -274,6 +292,7 @@ public void UpdatePathOverrides()
274292
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
275293
debugLogsToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
276294
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
295+
useBetaServerToggle.value = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, true);
277296
UpdateDeploymentSection();
278297
}
279298

MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
4242
</ui:VisualElement>
4343

44+
<ui:VisualElement class="setting-row">
45+
<ui:Label text="Use Beta Server:" class="setting-label" />
46+
<ui:Toggle name="use-beta-server-toggle" class="setting-toggle" />
47+
</ui:VisualElement>
48+
4449
<ui:VisualElement class="override-row" style="margin-top: 8px;">
4550
<ui:Label text="Package Source:" class="override-label" />
4651
</ui:VisualElement>

MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class EditorPrefsWindow : EditorWindow
1919
// UI Elements
2020
private ScrollView scrollView;
2121
private VisualElement prefsContainer;
22+
private TextField searchField;
23+
private string searchFilter = "";
2224

2325
// Data
2426
private List<EditorPrefItem> currentPrefs = new List<EditorPrefItem>();
@@ -40,6 +42,7 @@ public class EditorPrefsWindow : EditorWindow
4042
{ EditorPrefKeys.CustomToolRegistrationEnabled, EditorPrefType.Bool },
4143
{ EditorPrefKeys.TelemetryDisabled, EditorPrefType.Bool },
4244
{ EditorPrefKeys.DevModeForceServerRefresh, EditorPrefType.Bool },
45+
{ EditorPrefKeys.UseBetaServer, EditorPrefType.Bool },
4346
{ EditorPrefKeys.ProjectScopedToolsLocalHttp, EditorPrefType.Bool },
4447

4548
// Integer prefs
@@ -105,7 +108,39 @@ public void CreateGUI()
105108
}
106109

107110
visualTree.CloneTree(rootVisualElement);
108-
111+
112+
// Add search bar container at the top
113+
var searchContainer = new VisualElement();
114+
searchContainer.style.flexDirection = FlexDirection.Row;
115+
searchContainer.style.marginTop = 8;
116+
searchContainer.style.marginBottom = 20;
117+
searchContainer.style.marginLeft = 4;
118+
searchContainer.style.marginRight = 4;
119+
120+
searchField = new TextField("Search");
121+
searchField.style.flexGrow = 1;
122+
searchField.style.height = 28;
123+
searchField.style.paddingTop = 2;
124+
searchField.style.paddingBottom = 2;
125+
searchField.labelElement.style.unityFontStyleAndWeight = FontStyle.Bold;
126+
searchField.RegisterValueChangedCallback(evt =>
127+
{
128+
searchFilter = evt.newValue ?? "";
129+
RefreshPrefs();
130+
});
131+
132+
var refreshButton = new Button(RefreshPrefs);
133+
refreshButton.text = "↻";
134+
refreshButton.tooltip = "Refresh prefs";
135+
refreshButton.style.width = 30;
136+
refreshButton.style.height = 28;
137+
refreshButton.style.marginLeft = 6;
138+
refreshButton.style.backgroundColor = new Color(0.9f, 0.5f, 0.1f);
139+
140+
searchContainer.Add(searchField);
141+
searchContainer.Add(refreshButton);
142+
rootVisualElement.Insert(0, searchContainer);
143+
109144
// Get references
110145
scrollView = rootVisualElement.Q<ScrollView>("scroll-view");
111146
prefsContainer = rootVisualElement.Q<VisualElement>("prefs-container");
@@ -154,13 +189,23 @@ private void RefreshPrefs()
154189

155190
// Sort keys
156191
allKeys.Sort();
157-
192+
193+
// Pre-trim filter once outside the loop
194+
var filter = searchFilter?.Trim();
195+
158196
// Create items for existing prefs
159197
foreach (var key in allKeys)
160198
{
161199
// Skip Customer UUID but show everything else that's defined
162200
if (key != EditorPrefKeys.CustomerUuid)
163201
{
202+
// Apply search filter using OrdinalIgnoreCase for fewer allocations
203+
if (!string.IsNullOrEmpty(filter) &&
204+
key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0)
205+
{
206+
continue;
207+
}
208+
164209
var item = CreateEditorPrefItem(key);
165210
if (item != null)
166211
{

0 commit comments

Comments
 (0)