157 lines
7.4 KiB
C#
157 lines
7.4 KiB
C#
using Newtonsoft.Json.Linq;
|
|
using NUnit.Framework;
|
|
using UnityEditor;
|
|
using MCPForUnity.Editor.Clients.Configurators;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using MCPForUnity.Editor.Models;
|
|
using MCPForUnity.Editor.Constants;
|
|
using EditorConfigCache = MCPForUnity.Editor.Services.EditorConfigurationCache;
|
|
|
|
namespace MCPForUnityTests.Editor.Helpers
|
|
{
|
|
// Per-client MCP config-format coverage.
|
|
//
|
|
// Issue #1120: Kilo Code v7.0.33+ moved its MCP config from the VS Code extension's
|
|
// globalStorage/mcp_settings.json (mcpServers / type:"http" or "streamableHttp" / disabled)
|
|
// to a CLI-style kilo.jsonc under ~/.config/kilo whose schema (https://app.kilo.ai/config.json)
|
|
// uses an "mcp" container, type:"remote" for HTTP servers, and an "enabled" flag. Writing the
|
|
// old format left the server showing as "stdio" + disabled. These tests pin the new Kilo format
|
|
// while guarding that Cline keeps "streamableHttp" and generic clients keep plain "http".
|
|
public class ClientConfigFormatTests
|
|
{
|
|
private const string UseHttpTransportPrefKey = EditorPrefKeys.UseHttpTransport;
|
|
|
|
private bool _hadHttpTransport;
|
|
private bool _originalHttpTransport;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_hadHttpTransport = EditorPrefs.HasKey(UseHttpTransportPrefKey);
|
|
_originalHttpTransport = EditorPrefs.GetBool(UseHttpTransportPrefKey, true);
|
|
|
|
// Force HTTP transport so the remote/streamableHttp branch is exercised.
|
|
EditorPrefs.SetBool(UseHttpTransportPrefKey, true);
|
|
EditorConfigCache.Instance.Refresh();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
if (_hadHttpTransport)
|
|
EditorPrefs.SetBool(UseHttpTransportPrefKey, _originalHttpTransport);
|
|
else
|
|
EditorPrefs.DeleteKey(UseHttpTransportPrefKey);
|
|
EditorConfigCache.Instance.Refresh();
|
|
}
|
|
|
|
[Test]
|
|
public void KiloCodeConfigurator_DeclaresNewKiloFormat()
|
|
{
|
|
var client = new KiloCodeConfigurator().Client;
|
|
|
|
Assert.AreEqual("mcp", client.ServerContainerKey,
|
|
"Kilo Code's new schema nests servers under an \"mcp\" container, not \"mcpServers\"");
|
|
Assert.AreEqual("remote", client.HttpTypeValue,
|
|
"Kilo Code's new schema uses type:\"remote\" for HTTP servers");
|
|
Assert.AreEqual("local", client.StdioTypeValue,
|
|
"Kilo Code's new schema uses type:\"local\" for stdio servers");
|
|
Assert.AreEqual("https://app.kilo.ai/config.json", client.SchemaUrl,
|
|
"Kilo Code config should declare the kilo.jsonc $schema");
|
|
Assert.IsTrue(client.DefaultUnityFields.ContainsKey("enabled"),
|
|
"Kilo Code uses an \"enabled\" flag instead of \"disabled\"");
|
|
Assert.AreEqual(true, client.DefaultUnityFields["enabled"],
|
|
"Kilo Code must default enabled:true so the server is active");
|
|
Assert.IsFalse(client.DefaultUnityFields.ContainsKey("disabled"),
|
|
"Kilo Code's new schema must not write the legacy \"disabled\" field");
|
|
|
|
foreach (var path in new[] { client.windowsConfigPath, client.macConfigPath, client.linuxConfigPath })
|
|
{
|
|
Assert.AreEqual("kilo.jsonc", System.IO.Path.GetFileName(path),
|
|
"Kilo Code config must target kilo.jsonc, not mcp_settings.json");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void BuildManualConfigJson_ForKiloCode_UsesMcpContainerRemoteTypeAndEnabled()
|
|
{
|
|
var client = new KiloCodeConfigurator().Client;
|
|
|
|
var root = JObject.Parse(ConfigJsonBuilder.BuildManualConfigJson(uvPath: null, client));
|
|
|
|
Assert.AreEqual("https://app.kilo.ai/config.json", (string)root["$schema"],
|
|
"Kilo Code config should include the kilo.jsonc $schema at the root");
|
|
Assert.IsNull(root["mcpServers"], "Kilo Code must not use the legacy \"mcpServers\" container");
|
|
|
|
var unity = (JObject)root.SelectToken("mcp.unityMCP");
|
|
Assert.NotNull(unity, "Expected mcp.unityMCP node");
|
|
Assert.AreEqual("remote", (string)unity["type"],
|
|
"Kilo Code HTTP config must use type:remote, not type:http/streamableHttp");
|
|
Assert.AreEqual(true, (bool)unity["enabled"],
|
|
"Kilo Code config must set enabled:true");
|
|
Assert.IsNull(unity["disabled"], "Kilo Code must not write the legacy disabled field");
|
|
Assert.IsNotNull(unity["url"], "HTTP transport should set a url");
|
|
Assert.IsNull(unity["command"], "HTTP transport should not include a command");
|
|
}
|
|
|
|
[Test]
|
|
public void ApplyUnityServerToExistingConfig_ForKiloCode_RewritesStaleStdioToRemote()
|
|
{
|
|
var client = new KiloCodeConfigurator().Client;
|
|
|
|
// Simulate a stale local (stdio) entry that Kilo would have shown as the wrong transport.
|
|
var root = new JObject
|
|
{
|
|
["mcp"] = new JObject
|
|
{
|
|
["unityMCP"] = new JObject
|
|
{
|
|
["command"] = "uvx",
|
|
["args"] = new JArray("unity-mcp-server"),
|
|
["type"] = "local"
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = ConfigJsonBuilder.ApplyUnityServerToExistingConfig(root, uvPath: null, client);
|
|
|
|
Assert.AreEqual("https://app.kilo.ai/config.json", (string)result["$schema"],
|
|
"Rewrite should add the kilo.jsonc $schema when missing");
|
|
var unity = (JObject)result.SelectToken("mcp.unityMCP");
|
|
Assert.NotNull(unity, "Expected mcp.unityMCP node");
|
|
Assert.AreEqual("remote", (string)unity["type"],
|
|
"Existing config should be rewritten to type:remote for Kilo Code");
|
|
Assert.AreEqual(true, (bool)unity["enabled"], "Existing config should gain enabled:true");
|
|
Assert.IsNull(unity["command"], "HTTP transport should remove the stale stdio command");
|
|
}
|
|
|
|
[Test]
|
|
public void BuildManualConfigJson_ForCline_StillUsesStreamableHttpInMcpServers()
|
|
{
|
|
var client = new ClineConfigurator().Client;
|
|
|
|
var root = JObject.Parse(ConfigJsonBuilder.BuildManualConfigJson(uvPath: null, client));
|
|
|
|
Assert.IsNull(root["$schema"], "Cline must not write a $schema");
|
|
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
|
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
|
Assert.AreEqual("streamableHttp", (string)unity["type"],
|
|
"Cline must keep type:streamableHttp after the name-check was replaced with a flag");
|
|
}
|
|
|
|
[Test]
|
|
public void BuildManualConfigJson_ForGenericHttpClient_UsesPlainHttpType()
|
|
{
|
|
// A client without a type override must continue to receive the generic type:http.
|
|
var client = new McpClient { name = "Cursor" };
|
|
|
|
var root = JObject.Parse(ConfigJsonBuilder.BuildManualConfigJson(uvPath: null, client));
|
|
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
|
|
|
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
|
Assert.AreEqual("http", (string)unity["type"],
|
|
"Clients without HttpTypeValue should keep the generic type:http");
|
|
}
|
|
}
|
|
}
|