Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
00d7706
Move the current test to a Tools folder
msanatan Aug 20, 2025
6ee2890
feat: add env object and disabled flag handling for MCP client config…
msanatan Aug 20, 2025
6220750
Format manual config specially for Windsurf and Kiro
msanatan Aug 21, 2025
05011c4
refactor: extract config JSON building logic into dedicated ConfigJso…
msanatan Aug 21, 2025
d28a468
refactor: extract unity node population logic into centralized helper…
msanatan Aug 21, 2025
2c1630e
refactor: only add env property to config for Windsurf and Kiro clients
msanatan Aug 21, 2025
5cec801
Merge branch 'main' into improve-windsurf-mcp-config
msanatan Aug 21, 2025
317b099
fix: write UTF-8 without BOM encoding for config files to avoid Windo…
msanatan Aug 22, 2025
c53f5f1
fix: enforce UTF-8 encoding without BOM when writing files to disk
msanatan Aug 22, 2025
b457585
refactor: replace execute_menu_item with enhanced manage_menu_item to…
msanatan Aug 22, 2025
c399938
Update meta files for older Unity versions
msanatan Aug 22, 2025
97d48d5
test: add unit tests for menu item management and execution
msanatan Aug 23, 2025
d5bfcde
feat: add tips for paths, script compilation, and menu item usage in …
msanatan Aug 23, 2025
088dc23
Merge branch 'main' into add-read-menu-tool
msanatan Sep 4, 2025
df990dc
Use McpLog functionality instead of Unity's Debug
msanatan Sep 5, 2025
371c7ef
Merge branch 'main' into add-read-menu-tool
msanatan Sep 12, 2025
7dc4a1d
Add telemetry
msanatan Sep 12, 2025
1a7d19e
Annotate parameters
msanatan Sep 12, 2025
d4224e9
Remove the refresh command
msanatan Sep 12, 2025
e021eca
Updated meta files since running in Unity 2021
msanatan Sep 12, 2025
4acbcfc
Slightly better README
msanatan Sep 12, 2025
b01f605
fix: rename server-version.txt to server_version.txt and update menu …
msanatan Sep 12, 2025
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ MCP for Unity acts as a bridge, allowing AI assistants (like Claude, Cursor) to
* `manage_asset`: Performs asset operations (import, create, modify, delete, etc.).
* `manage_shader`: Performs shader CRUD operations (create, read, modify, delete).
* `manage_gameobject`: Manages GameObjects: create, modify, delete, find, and component operations.
* `execute_menu_item`: Executes a menu item via its path (e.g., "File/Save Project").
* `manage_menu_item`: List Unity Editor menu items; and check for their existence or execute them (e.g., execute "File/Save Project").
* `apply_text_edits`: Precise text edits with precondition hashes and atomic multi-edit batches.
* `script_apply_edits`: Structured C# method/class edits (insert/replace/delete) with safer boundaries.
* `validate_script`: Fast validation (basic/standard) to catch syntax/structure issues before/after writes.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using NUnit.Framework;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools.MenuItems;

namespace MCPForUnityTests.Editor.Tools.MenuItems
{
public class ManageMenuItemTests
{
private static JObject ToJO(object o) => JObject.FromObject(o);

[Test]
public void HandleCommand_UnknownAction_ReturnsError()
{
var res = ManageMenuItem.HandleCommand(new JObject { ["action"] = "unknown_action" });
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false for unknown action");
StringAssert.Contains("Unknown action", (string)jo["error"]);
}

[Test]
public void HandleCommand_List_RoutesAndReturnsArray()
{
var res = ManageMenuItem.HandleCommand(new JObject { ["action"] = "list" });
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected success true");
Assert.AreEqual(JTokenType.Array, jo["data"].Type, "Expected data to be an array");
}

[Test]
public void HandleCommand_Execute_Blacklisted_RoutesAndErrors()
{
var res = ManageMenuItem.HandleCommand(new JObject { ["action"] = "execute", ["menuPath"] = "File/Quit" });
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false");
StringAssert.Contains("blocked for safety", (string)jo["error"], "Expected blacklist message");
}

[Test]
public void HandleCommand_Exists_MissingParam_ReturnsError()
{
var res = ManageMenuItem.HandleCommand(new JObject { ["action"] = "exists" });
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false when missing menuPath");
StringAssert.Contains("Required parameter", (string)jo["error"]);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using NUnit.Framework;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools.MenuItems;

namespace MCPForUnityTests.Editor.Tools.MenuItems
{
public class MenuItemExecutorTests
{
private static JObject ToJO(object o) => JObject.FromObject(o);

[Test]
public void Execute_MissingParam_ReturnsError()
{
var res = MenuItemExecutor.Execute(new JObject());
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false");
StringAssert.Contains("Required parameter", (string)jo["error"]);
}

[Test]
public void Execute_Blacklisted_ReturnsError()
{
var res = MenuItemExecutor.Execute(new JObject { ["menuPath"] = "File/Quit" });
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false for blacklisted menu");
StringAssert.Contains("blocked for safety", (string)jo["error"], "Expected blacklist message");
}

[Test]
public void Execute_NonBlacklisted_ReturnsImmediateSuccess()
{
// We don't rely on the menu actually existing; execution is delayed and we only check the immediate response shape
var res = MenuItemExecutor.Execute(new JObject { ["menuPath"] = "File/Save Project" });
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected immediate success response");
StringAssert.Contains("Attempted to execute menu item", (string)jo["message"], "Expected attempt message");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using NUnit.Framework;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools.MenuItems;
using System;
using System.Linq;

namespace MCPForUnityTests.Editor.Tools.MenuItems
{
public class MenuItemsReaderTests
{
private static JObject ToJO(object o) => JObject.FromObject(o);

[Test]
public void List_NoSearch_ReturnsSuccessAndArray()
{
var res = MenuItemsReader.List(new JObject());
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected success true");
Assert.IsNotNull(jo["data"], "Expected data field present");
Assert.AreEqual(JTokenType.Array, jo["data"].Type, "Expected data to be an array");

// Validate list is sorted ascending when there are multiple items
var arr = (JArray)jo["data"];
if (arr.Count >= 2)
{
var original = arr.Select(t => (string)t).ToList();
var sorted = original.OrderBy(s => s, StringComparer.Ordinal).ToList();
CollectionAssert.AreEqual(sorted, original, "Expected menu items to be sorted ascending");
}
}

[Test]
public void List_SearchNoMatch_ReturnsEmpty()
{
var res = MenuItemsReader.List(new JObject { ["search"] = "___unlikely___term___" });
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected success true");
Assert.AreEqual(JTokenType.Array, jo["data"].Type, "Expected data to be an array");
Assert.AreEqual(0, jo["data"].Count(), "Expected no results for unlikely search term");
}

[Test]
public void List_SearchMatchesExistingItem_ReturnsContainingItem()
{
// Get the full list first
var listRes = MenuItemsReader.List(new JObject());
var listJo = ToJO(listRes);
if (listJo["data"] is JArray arr && arr.Count > 0)
{
var first = (string)arr[0];
// Use a mid-substring (case-insensitive) to avoid edge cases
var term = first.Length > 4 ? first.Substring(1, Math.Min(3, first.Length - 2)) : first;
term = term.ToLowerInvariant();

var res = MenuItemsReader.List(new JObject { ["search"] = term });
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected success true");
Assert.AreEqual(JTokenType.Array, jo["data"].Type, "Expected data to be an array");
// Expect at least the original item to be present
var names = ((JArray)jo["data"]).Select(t => (string)t).ToList();
CollectionAssert.Contains(names, first, "Expected search results to include the sampled item");
}
else
{
Assert.Pass("No menu items available to perform a content-based search assertion.");
}
}

[Test]
public void Exists_MissingParam_ReturnsError()
{
var res = MenuItemsReader.Exists(new JObject());
var jo = ToJO(res);
Assert.IsFalse((bool)jo["success"], "Expected success false");
StringAssert.Contains("Required parameter", (string)jo["error"]);
}

[Test]
public void Exists_Bogus_ReturnsFalse()
{
var res = MenuItemsReader.Exists(new JObject { ["menuPath"] = "Nonexistent/Menu/___unlikely___" });
var jo = ToJO(res);
Assert.IsTrue((bool)jo["success"], "Expected success true");
Assert.IsNotNull(jo["data"], "Expected data field present");
Assert.IsFalse((bool)jo["data"]["exists"], "Expected exists false for bogus menu path");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"m_Name": "Settings",
"m_Path": "ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json",
"m_Dictionary": {
"m_DictionaryValues": []
}
Expand Down
11 changes: 10 additions & 1 deletion UnityMcpBridge/Editor/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion UnityMcpBridge/Editor/Helpers/PackageDetector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion UnityMcpBridge/Editor/MCPForUnityBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;
using MCPForUnity.Editor.Tools;
using MCPForUnity.Editor.Tools.MenuItems;

namespace MCPForUnity.Editor
{
Expand Down Expand Up @@ -1053,7 +1054,7 @@ private static string ExecuteCommand(Command command)
"manage_asset" => ManageAsset.HandleCommand(paramsObject),
"manage_shader" => ManageShader.HandleCommand(paramsObject),
"read_console" => ReadConsole.HandleCommand(paramsObject),
"execute_menu_item" => ExecuteMenuItem.HandleCommand(paramsObject),
"manage_menu_item" => ManageMenuItem.HandleCommand(paramsObject),
_ => throw new ArgumentException(
$"Unknown or unsupported command type: {command.type}"
),
Expand Down
3 changes: 2 additions & 1 deletion UnityMcpBridge/Editor/Tools/CommandRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools.MenuItems;

namespace MCPForUnity.Editor.Tools
{
Expand All @@ -19,7 +20,7 @@ public static class CommandRegistry
{ "HandleManageGameObject", ManageGameObject.HandleCommand },
{ "HandleManageAsset", ManageAsset.HandleCommand },
{ "HandleReadConsole", ReadConsole.HandleCommand },
{ "HandleExecuteMenuItem", ExecuteMenuItem.HandleCommand },
{ "HandleManageMenuItem", ManageMenuItem.HandleCommand },
{ "HandleManageShader", ManageShader.HandleCommand},
};

Expand Down
Loading