Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,11 @@ private ConfiguredTransport ResolveTransport(JObject server)
return ConfiguredTransport.HttpRemote;
}

if (UrlsEqual(configuredUrl, HttpEndpointUtility.GetLanMcpRpcUrl()))
{
return ConfiguredTransport.HttpLan;
}
Comment on lines +390 to +393
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ServerMatchesCurrentEndpoint doesn't accept the LAN URL and will treat correctly configured LAN servers as mismatched.

ResolveTransport now returns HttpLan for the LAN RPC URL, but the URL validation in ServerMatchesCurrentEndpoint (lines 354–360) only accepts GetLocalMcpRpcUrl() or GetRemoteMcpRpcUrl(). When the user has selected LAN scope and the OpenClaw config contains the LAN URL, configuredTransport == expectedTransport == HttpLan, but the else-branch URL check rejects the LAN URL and returns false, causing CheckStatus to either trigger an auto-rewrite loop or report IncorrectPath.

🛠️ Proposed fix
                 string configuredUrl = server["url"]?.ToString();
                 if (string.IsNullOrWhiteSpace(configuredUrl) ||
                     (!UrlsEqual(configuredUrl, HttpEndpointUtility.GetLocalMcpRpcUrl()) &&
-                     !UrlsEqual(configuredUrl, HttpEndpointUtility.GetRemoteMcpRpcUrl())))
+                     !UrlsEqual(configuredUrl, HttpEndpointUtility.GetRemoteMcpRpcUrl()) &&
+                     !UrlsEqual(configuredUrl, HttpEndpointUtility.GetLanMcpRpcUrl())))
                 {
                     return false;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Clients/Configurators/OpenClawConfigurator.cs` around
lines 390 - 393, ServerMatchesCurrentEndpoint incorrectly rejects the LAN RPC
URL; update ServerMatchesCurrentEndpoint so that when ResolveTransport or
configuredTransport is HttpLan it accepts HttpEndpointUtility.GetLanMcpRpcUrl()
in the same way it accepts GetLocalMcpRpcUrl() and GetRemoteMcpRpcUrl().
Specifically, modify the else-branch URL equality check inside
ServerMatchesCurrentEndpoint (the block that compares configuredUrl to
HttpEndpointUtility.GetLocalMcpRpcUrl()/GetRemoteMcpRpcUrl()) to also compare
against HttpEndpointUtility.GetLanMcpRpcUrl(), ensuring that a configured
transport of ConfiguredTransport.HttpLan will return true and prevent
CheckStatus from misreporting IncorrectPath or causing auto-rewrite loops.


if (UrlsEqual(configuredUrl, HttpEndpointUtility.GetLocalMcpRpcUrl()))
{
return ConfiguredTransport.Http;
Expand Down
19 changes: 15 additions & 4 deletions MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,15 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
// Distinguish HTTP Local from HTTP Remote by matching against both URLs
string localRpcUrl = HttpEndpointUtility.GetLocalMcpRpcUrl();
string remoteRpcUrl = HttpEndpointUtility.GetRemoteMcpRpcUrl();
string lanRpcUrl = HttpEndpointUtility.GetLanMcpRpcUrl();
if (!string.IsNullOrEmpty(remoteRpcUrl) && UrlsEqual(configuredUrl, remoteRpcUrl))
{
client.configuredTransport = Models.ConfiguredTransport.HttpRemote;
}
else if (!string.IsNullOrEmpty(lanRpcUrl) && UrlsEqual(configuredUrl, lanRpcUrl))
{
client.configuredTransport = Models.ConfiguredTransport.HttpLan;
}
else
{
client.configuredTransport = Models.ConfiguredTransport.Http;
Expand Down Expand Up @@ -377,10 +382,15 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
{
// Distinguish HTTP Local from HTTP Remote
string remoteRpcUrl = HttpEndpointUtility.GetRemoteMcpRpcUrl();
string lanRpcUrl = HttpEndpointUtility.GetLanMcpRpcUrl();
if (!string.IsNullOrEmpty(remoteRpcUrl) && UrlsEqual(url, remoteRpcUrl))
{
client.configuredTransport = Models.ConfiguredTransport.HttpRemote;
}
else if (!string.IsNullOrEmpty(lanRpcUrl) && UrlsEqual(url, lanRpcUrl))
{
client.configuredTransport = Models.ConfiguredTransport.HttpLan;
}
else
{
client.configuredTransport = Models.ConfiguredTransport.Http;
Expand Down Expand Up @@ -581,23 +591,24 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath();
RuntimePlatform platform = Application.platform;
bool isRemoteScope = HttpEndpointUtility.IsRemoteScope();
bool isLanScope = HttpEndpointUtility.IsLanScope();
// Get expected package source for the installed package version (matches what Register() would use)
string expectedPackageSource = GetExpectedPackageSourceForValidation();
return CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, expectedPackageSource, attemptAutoRewrite, HasClientProjectDirOverride);
return CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, isLanScope, expectedPackageSource, attemptAutoRewrite, HasClientProjectDirOverride);
}

/// <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, claudePath, platform, isRemoteScope, and expectedPackageSource are REQUIRED
/// projectDir, useHttpTransport, claudePath, platform, isRemoteScope, isLanScope, 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, RuntimePlatform platform,
bool isRemoteScope, string expectedPackageSource,
bool isRemoteScope, bool isLanScope, string expectedPackageSource,
bool attemptAutoRewrite = false, bool hasProjectDirOverride = false)
{
try
Expand Down Expand Up @@ -647,7 +658,7 @@ internal McpStatus CheckStatusWithProjectDir(
{
client.configuredTransport = isRemoteScope
? Models.ConfiguredTransport.HttpRemote
: Models.ConfiguredTransport.Http;
: (isLanScope ? Models.ConfiguredTransport.HttpLan : Models.ConfiguredTransport.Http);
}
else if (registeredWithStdio)
{
Expand Down
4 changes: 3 additions & 1 deletion MCPForUnity/Editor/Constants/EditorPrefKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MCPForUnity.Editor.Constants
internal static class EditorPrefKeys
{
internal const string UseHttpTransport = "MCPForUnity.UseHttpTransport";
internal const string HttpTransportScope = "MCPForUnity.HttpTransportScope"; // "local" | "remote"
internal const string HttpTransportScope = "MCPForUnity.HttpTransportScope"; // "local" | "remote" | "lan"
internal const string LastLocalHttpServerPid = "MCPForUnity.LocalHttpServer.LastPid";
internal const string LastLocalHttpServerPort = "MCPForUnity.LocalHttpServer.LastPort";
internal const string LastLocalHttpServerStartedUtc = "MCPForUnity.LocalHttpServer.LastStartedUtc";
Expand All @@ -26,6 +26,8 @@ internal static class EditorPrefKeys

internal const string HttpBaseUrl = "MCPForUnity.HttpUrl";
internal const string HttpRemoteBaseUrl = "MCPForUnity.HttpRemoteUrl";
internal const string HttpLanPublicBaseUrl = "MCPForUnity.HttpLanPublicUrl";
internal const string HttpLanBindBaseUrl = "MCPForUnity.HttpLanBindUrl";
internal const string SessionId = "MCPForUnity.SessionId";
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
Expand Down
111 changes: 110 additions & 1 deletion MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,22 @@ public static class HttpEndpointUtility
{
private const string LocalPrefKey = EditorPrefKeys.HttpBaseUrl;
private const string RemotePrefKey = EditorPrefKeys.HttpRemoteBaseUrl;
private const string LanPublicPrefKey = EditorPrefKeys.HttpLanPublicBaseUrl;
private const string LanBindPrefKey = EditorPrefKeys.HttpLanBindBaseUrl;
private const string DefaultLocalBaseUrl = "http://127.0.0.1:8080";
private const string DefaultRemoteBaseUrl = "";
private const string DefaultLanPublicBaseUrl = "http://192.168.1.10:8090";
private const string DefaultLanBindBaseUrl = "http://0.0.0.0:8090";

/// <summary>
/// Returns the normalized base URL for the currently active HTTP scope.
/// If the scope is "remote", returns the remote URL; otherwise returns the local URL.
/// </summary>
public static string GetBaseUrl()
{
return IsRemoteScope() ? GetRemoteBaseUrl() : GetLocalBaseUrl();
if (IsRemoteScope()) return GetRemoteBaseUrl();
if (IsLanScope()) return GetLanPublicBaseUrl();
return GetLocalBaseUrl();
}

/// <summary>
Expand All @@ -40,6 +46,10 @@ public static void SaveBaseUrl(string userValue)
{
SaveRemoteBaseUrl(userValue);
}
else if (IsLanScope())
{
SaveLanPublicBaseUrl(userValue);
}
else
{
SaveLocalBaseUrl(userValue);
Expand Down Expand Up @@ -78,6 +88,47 @@ public static string GetRemoteBaseUrl()
return NormalizeBaseUrl(stored, DefaultRemoteBaseUrl, remoteScope: true);
}

/// <summary>
/// Returns the normalized LAN public URL that remote clients should use.
/// </summary>
public static string GetLanPublicBaseUrl()
{
string stored = EditorPrefs.GetString(LanPublicPrefKey, DefaultLanPublicBaseUrl);
return NormalizeBaseUrl(stored, DefaultLanPublicBaseUrl, remoteScope: false);
}

/// <summary>
/// Saves the LAN public URL and keeps the bind URL on 0.0.0.0 with the same port.
/// </summary>
public static void SaveLanPublicBaseUrl(string userValue)
{
string normalized = NormalizeBaseUrl(userValue, DefaultLanPublicBaseUrl, remoteScope: false);
EditorPrefs.SetString(LanPublicPrefKey, normalized);

if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri) && uri.Port > 0)
{
EditorPrefs.SetString(LanBindPrefKey, $"http://0.0.0.0:{uri.Port}");
}
}
Comment on lines +103 to +112
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
find . -name "HttpEndpointUtility.cs" -type f

Repository: CoplayDev/unity-mcp

Length of output: 115


🏁 Script executed:

# Once we find the file, let's examine the relevant section
cat -n MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs | sed -n '95,120p'

Repository: CoplayDev/unity-mcp

Length of output: 1400


🏁 Script executed:

# Let's also search for the IsHttpLanUrlAllowedForLaunch method mentioned in the comment
rg "IsHttpLanUrlAllowedForLaunch" -A 10 -B 2

Repository: CoplayDev/unity-mcp

Length of output: 6817


🏁 Script executed:

# Search for references to DefaultLanPublicBaseUrl and LanBindPrefKey to understand the defaults
rg "DefaultLanPublicBaseUrl|LanBindPrefKey|LanPublicPrefKey" -B 2 -A 2

Repository: CoplayDev/unity-mcp

Length of output: 3145


🏁 Script executed:

# Get the full IsHttpLanUrlAllowedForLaunch method
rg "public static bool IsHttpLanUrlAllowedForLaunch" -A 30

Repository: CoplayDev/unity-mcp

Length of output: 2587


🏁 Script executed:

# Search for any other Uri.Port usages in the file to check for similar issues
rg "uri\.Port" MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs -B 3 -A 3

Repository: CoplayDev/unity-mcp

Length of output: 644


🏁 Script executed:

# Let's check if Uri.IsDefaultPort is used anywhere
rg "IsDefaultPort" MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs

Repository: CoplayDev/unity-mcp

Length of output: 45


LAN bind URL can silently collapse to port 80.

When the user saves a public URL without an explicit port (e.g., http://192.168.1.10), Uri.Port returns 80 (the HTTP default), not -1 or 0. The guard on line 108 passes, silently setting the bind URL to http://0.0.0.0:80 and overwriting the default http://0.0.0.0:8090 without warning. The policy check in IsHttpLanUrlAllowedForLaunch won't catch this either, since it only rejects uri.Port <= 0.

Gate the derivation on !uri.IsDefaultPort, or reject/ask for an explicit port upstream in SaveBaseUrl.

🛠️ Proposed fix
-            if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri) && uri.Port > 0)
+            // Only sync the bind URL when the user supplied an explicit port.
+            // Uri.Port falls back to the scheme default (80 for http), so `> 0` alone would
+            // silently rewrite the bind URL to port 80 when the user omitted the port.
+            if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri) && !uri.IsDefaultPort && uri.Port > 0)
             {
                 EditorPrefs.SetString(LanBindPrefKey, $"http://0.0.0.0:{uri.Port}");
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static void SaveLanPublicBaseUrl(string userValue)
{
string normalized = NormalizeBaseUrl(userValue, DefaultLanPublicBaseUrl, remoteScope: false);
EditorPrefs.SetString(LanPublicPrefKey, normalized);
if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri) && uri.Port > 0)
{
EditorPrefs.SetString(LanBindPrefKey, $"http://0.0.0.0:{uri.Port}");
}
}
public static void SaveLanPublicBaseUrl(string userValue)
{
string normalized = NormalizeBaseUrl(userValue, DefaultLanPublicBaseUrl, remoteScope: false);
EditorPrefs.SetString(LanPublicPrefKey, normalized);
// Only sync the bind URL when the user supplied an explicit port.
// Uri.Port falls back to the scheme default (80 for http), so `> 0` alone would
// silently rewrite the bind URL to port 80 when the user omitted the port.
if (Uri.TryCreate(normalized, UriKind.Absolute, out var uri) && !uri.IsDefaultPort && uri.Port > 0)
{
EditorPrefs.SetString(LanBindPrefKey, $"http://0.0.0.0:{uri.Port}");
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs` around lines 103 - 112, In
SaveLanPublicBaseUrl, the current check uses uri.Port > 0 which treats a missing
port as 80 and can overwrite the default bind port; change the derivation to
only set LanBindPrefKey when the parsed Uri has an explicit port by checking
!uri.IsDefaultPort (or explicitly validate/require a port from NormalizeBaseUrl
before saving), and ensure this aligns with IsHttpLanUrlAllowedForLaunch’s
policy (which should continue to reject uri.Port <= 0 if you choose to reject
missing ports upstream); update SaveLanPublicBaseUrl to use !uri.IsDefaultPort
when deciding to set LanBindPrefKey (keeping LanPublicPrefKey behavior
unchanged).


/// <summary>
/// Returns the LAN bind URL used to launch the local server.
/// </summary>
public static string GetLanBindBaseUrl()
{
string stored = EditorPrefs.GetString(LanBindPrefKey, DefaultLanBindBaseUrl);
return NormalizeBaseUrl(stored, DefaultLanBindBaseUrl, remoteScope: false);
}

/// <summary>
/// Returns the URL used to launch/probe/stop the local server process.
/// LAN mode binds all interfaces while exposing a separate public client URL.
/// </summary>
public static string GetLocalServerLaunchBaseUrl()
{
return IsLanScope() ? GetLanBindBaseUrl() : GetLocalBaseUrl();
}

/// <summary>
/// Saves a user-provided URL to the remote HTTP pref.
/// </summary>
Expand Down Expand Up @@ -108,6 +159,14 @@ public static string GetLocalMcpRpcUrl()
return AppendPathSegment(GetLocalBaseUrl(), "mcp");
}

/// <summary>
/// Builds the LAN public JSON-RPC endpoint (public base + /mcp).
/// </summary>
public static string GetLanMcpRpcUrl()
{
return AppendPathSegment(GetLanPublicBaseUrl(), "mcp");
}

/// <summary>
/// Builds the remote JSON-RPC endpoint (remote base + /mcp).
/// Returns empty string if no remote URL is configured.
Expand Down Expand Up @@ -135,6 +194,15 @@ public static bool IsRemoteScope()
return string.Equals(scope, "remote", StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Returns true if the active HTTP transport scope is "lan".
/// </summary>
public static bool IsLanScope()
{
string scope = EditorConfigurationCache.Instance.HttpTransportScope;
return string.Equals(scope, "lan", StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Returns the <see cref="ConfiguredTransport"/> that matches the current server-side
/// transport selection (Stdio, Http, or HttpRemote).
Expand All @@ -144,6 +212,7 @@ public static ConfiguredTransport GetCurrentServerTransport()
{
bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport;
if (!useHttp) return ConfiguredTransport.Stdio;
if (IsLanScope()) return ConfiguredTransport.HttpLan;
return IsRemoteScope() ? ConfiguredTransport.HttpRemote : ConfiguredTransport.Http;
}

Expand Down Expand Up @@ -249,6 +318,46 @@ public static bool IsHttpLocalUrlAllowedForLaunch(string url, out string error)
return false;
}

/// <summary>
/// Returns true when the URL is acceptable for LAN HTTP launch.
/// LAN mode intentionally binds all interfaces for trusted private networks.
/// </summary>
public static bool IsHttpLanUrlAllowedForLaunch(string url, out string error)
{
error = null;
if (string.IsNullOrWhiteSpace(url))
{
error = "LAN HTTP requires a bind URL such as http://0.0.0.0:8090.";
return false;
}

if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
error = $"Invalid LAN HTTP bind URL: {url}";
return false;
}

if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
error = "LAN HTTP bind URL must use http://.";
return false;
}

if (uri.Port <= 0)
{
error = "LAN HTTP bind URL requires an explicit port.";
return false;
}

if (IsBindAllInterfacesHost(uri.Host))
{
return true;
}

error = "LAN HTTP server bind host must be 0.0.0.0 or ::.";
return false;
}
Comment on lines +325 to +359
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What does System.Uri.Port return when a URL like "http://0.0.0.0" has no explicit port?

💡 Result:

System.Uri.Port returns 80 for the URL "http://0.0.0.0" when no explicit port is specified.

Citations:


🏁 Script executed:

cat -n MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs | sed -n '325,359p'

Repository: CoplayDev/unity-mcp

Length of output: 1391


🌐 Web query:

System.Uri.IsDefaultPort property behavior in .NET

💡 Result:

The System.Uri.IsDefaultPort property in .NET returns true if the port of the URI matches the default port defined for its scheme (e.g., 80 for http, 443 for https), and false otherwise. It is only valid for absolute URIs; accessing it on relative URIs throws InvalidOperationException. Key behaviors: - If no port is specified in the URI string, it uses the scheme's default, so IsDefaultPort is true (e.g., new Uri("http://example.com") has Port=80 and IsDefaultPort=true). - If a non-default port is specified, IsDefaultPort is false (e.g., new Uri("http://example.com:8080") has Port=8080 and IsDefaultPort=false). - If the default port is explicitly specified, Uri normalizes by removing it from AbsoluteUri, but Port still returns the default value and IsDefaultPort remains true (e.g., new Uri("http://example.com:80") normalizes to "http://example.com/", Port=80, IsDefaultPort=true). - For schemes without a defined default port, Port returns -1 and IsDefaultPort is true unless a port was explicitly parsed. - The property checks internal flags (NotAny(Flags.NotDefaultPort)) set during parsing against the UriParser's DefaultPort for the scheme. Examples: Uri uri1 = new Uri("http://www.contoso.com/"); // IsDefaultPort = true (implicit port 80) Uri uri2 = new Uri("http://www.contoso.com:8080/"); // IsDefaultPort = false Uri uri3 = new Uri("https://www.contoso.com:443/"); // IsDefaultPort = true (explicit default) Uri uri4 = new Uri("https://www.contoso.com:80/"); // IsDefaultPort = false This behavior is consistent across .NET Framework, .NET Core, and .NET 5+ (up to .NET 10.0 docs).

Citations:


uri.Port <= 0 check is dead code — Uri.Port returns the scheme default (80) when no port is specified.

Per .NET documentation, if no port is specified in a URI, Uri.Port returns the default for the scheme. For http URLs, that default is 80, not -1 or 0. So a URL like http://0.0.0.0 (without explicit port) will:

  1. Pass Uri.TryCreate
  2. Pass the scheme check ✓
  3. Pass the uri.Port <= 0 guard with uri.Port == 80 — the "requires an explicit port" error never fires
  4. Pass the bind-all host check → validation returns true

The server then attempts to bind to port 80, which requires elevated permissions and fails opaquely at runtime. Use uri.IsDefaultPort to properly detect when a port was not explicitly provided:

🛡️ Proposed fix using `IsDefaultPort`
-            if (uri.Port <= 0)
+            // Uri.Port returns the scheme default (80) when unspecified, so use IsDefaultPort
+            // to require the user to supply an explicit port for LAN bind URLs.
+            if (uri.IsDefaultPort)
             {
                 error = "LAN HTTP bind URL requires an explicit port.";
                 return false;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static bool IsHttpLanUrlAllowedForLaunch(string url, out string error)
{
error = null;
if (string.IsNullOrWhiteSpace(url))
{
error = "LAN HTTP requires a bind URL such as http://0.0.0.0:8090.";
return false;
}
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
error = $"Invalid LAN HTTP bind URL: {url}";
return false;
}
if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
error = "LAN HTTP bind URL must use http://.";
return false;
}
if (uri.Port <= 0)
{
error = "LAN HTTP bind URL requires an explicit port.";
return false;
}
if (IsBindAllInterfacesHost(uri.Host))
{
return true;
}
error = "LAN HTTP server bind host must be 0.0.0.0 or ::.";
return false;
}
public static bool IsHttpLanUrlAllowedForLaunch(string url, out string error)
{
error = null;
if (string.IsNullOrWhiteSpace(url))
{
error = "LAN HTTP requires a bind URL such as http://0.0.0.0:8090.";
return false;
}
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
error = $"Invalid LAN HTTP bind URL: {url}";
return false;
}
if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
error = "LAN HTTP bind URL must use http://.";
return false;
}
// Uri.Port returns the scheme default (80) when unspecified, so use IsDefaultPort
// to require the user to supply an explicit port for LAN bind URLs.
if (uri.IsDefaultPort)
{
error = "LAN HTTP bind URL requires an explicit port.";
return false;
}
if (IsBindAllInterfacesHost(uri.Host))
{
return true;
}
error = "LAN HTTP server bind host must be 0.0.0.0 or ::.";
return false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs` around lines 325 - 359,
The IsHttpLanUrlAllowedForLaunch method incorrectly uses uri.Port <= 0 to detect
missing explicit ports; replace that dead check with Uri.IsDefaultPort (e.g., if
(uri.IsDefaultPort) { error = "LAN HTTP bind URL requires an explicit port.";
return false; }) and then (optionally) validate uri.Port is within the valid TCP
port range if you want extra safety before proceeding to
IsBindAllInterfacesHost; update references to uri.Port only after the
IsDefaultPort check.


/// <summary>
/// Returns true when remote URL is allowed by current security policy.
/// HTTPS is required by default; HTTP needs explicit opt-in.
Expand Down
3 changes: 2 additions & 1 deletion MCPForUnity/Editor/Models/McpStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public enum ConfiguredTransport
Unknown, // Could not determine transport type
Stdio, // Client configured for stdio transport
Http, // Client configured for HTTP local transport
HttpRemote // Client configured for HTTP remote-hosted transport
HttpRemote, // Client configured for HTTP remote-hosted transport
HttpLan // Client configured for LAN HTTP transport
}
}

8 changes: 6 additions & 2 deletions MCPForUnity/Editor/Services/HttpAutoStartHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ private static async Task AutoStartAsync()
{
// For HTTP Local: launch the server process first, then connect the bridge.
// This mirrors what the UI "Start Server" button does.
if (!HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(
HttpEndpointUtility.GetLocalBaseUrl(), out string policyError))
string launchBaseUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
string policyError;
bool launchAllowed = HttpEndpointUtility.IsLanScope()
? HttpEndpointUtility.IsHttpLanUrlAllowedForLaunch(launchBaseUrl, out policyError)
: HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(launchBaseUrl, out policyError);
if (!launchAllowed)
{
McpLog.Debug($"[HTTP Auto-Start] Local URL blocked by security policy: {policyError}");
return;
Expand Down
1 change: 1 addition & 0 deletions MCPForUnity/Editor/Services/McpEditorShutdownCleanup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private static void OnEditorQuitting()
bool httpLocalSelected =
useHttp &&
(string.Equals(scope, "local", StringComparison.OrdinalIgnoreCase)
|| string.Equals(scope, "lan", StringComparison.OrdinalIgnoreCase)
|| (string.IsNullOrEmpty(scope) && MCPServiceLocator.Server.IsLocalUrl()));

if (httpLocalSelected)
Expand Down
8 changes: 6 additions & 2 deletions MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ public bool TryBuildCommand(out string fileName, out string arguments, out strin
return false;
}

string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
if (!HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(httpUrl, out string localUrlError))
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
string localUrlError;
bool launchUrlAllowed = HttpEndpointUtility.IsLanScope()
? HttpEndpointUtility.IsHttpLanUrlAllowedForLaunch(httpUrl, out localUrlError)
: HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(httpUrl, out localUrlError);
if (!launchUrlAllowed)
{
error = string.IsNullOrEmpty(localUrlError)
? $"The configured URL ({httpUrl}) is not allowed for HTTP Local launch."
Expand Down
20 changes: 11 additions & 9 deletions MCPForUnity/Editor/Services/ServerManagementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public bool StartLocalHttpServer(bool quiet = false)
// If the port is still occupied, don't start and explain why (avoid confusing "refusing to stop" warnings).
try
{
string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
if (Uri.TryCreate(httpUrl, UriKind.Absolute, out var uri) && uri.Port > 0)
{
var remaining = GetListeningProcessIdsForPort(uri.Port);
Expand All @@ -280,7 +280,7 @@ public bool StartLocalHttpServer(bool quiet = false)
// Note: Dev mode cache-busting is handled by `uvx --no-cache --refresh` in the generated command.

// Create a per-launch token + pidfile path so Stop can be deterministic without relying on port/PID heuristics.
string baseUrlForPid = HttpEndpointUtility.GetLocalBaseUrl();
string baseUrlForPid = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
Uri.TryCreate(baseUrlForPid, UriKind.Absolute, out var uriForPid);
int portForPid = uriForPid?.Port ?? 0;
string instanceToken = Guid.NewGuid().ToString("N");
Expand Down Expand Up @@ -359,7 +359,7 @@ public bool StopManagedLocalHttpServer()
int port = 0;
if (!TryGetPortFromPidFilePath(pidFilePath, out port) || port <= 0)
{
string baseUrl = HttpEndpointUtility.GetLocalBaseUrl();
string baseUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
if (IsLocalUrl(baseUrl)
&& Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri)
&& uri.Port > 0)
Expand All @@ -380,7 +380,7 @@ public bool IsLocalHttpServerRunning()
{
try
{
string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
if (!IsLocalUrl(httpUrl))
{
return false;
Expand Down Expand Up @@ -442,7 +442,7 @@ public bool IsLocalHttpServerReachable()
{
try
{
string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
if (!IsLocalUrl(httpUrl))
{
return false;
Expand Down Expand Up @@ -543,7 +543,7 @@ private static void AddHostCandidate(List<string> hosts, string candidate)

private bool StopLocalHttpServerInternal(bool quiet, int? portOverride = null, bool allowNonLocalUrl = false)
{
string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
if (!allowNonLocalUrl && !IsLocalUrl(httpUrl))
{
if (!quiet)
Expand Down Expand Up @@ -898,7 +898,7 @@ private bool TryGetLocalHttpServerCommandParts(out string fileName, out string a
/// </summary>
public bool IsLocalUrl()
{
string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
return IsLocalUrl(httpUrl);
}

Expand Down Expand Up @@ -933,8 +933,10 @@ public bool CanStartLocalServer()
return false;
}

string httpUrl = HttpEndpointUtility.GetLocalBaseUrl();
return HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(httpUrl, out _);
string httpUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
return HttpEndpointUtility.IsLanScope()
? HttpEndpointUtility.IsHttpLanUrlAllowedForLaunch(httpUrl, out _)
: HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(httpUrl, out _);
}

private System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(string command)
Expand Down
Loading