unity-mcp/MCPForUnity/Editor/Windows/Components/Tools/McpToolsSection.cs

271 lines
8.7 KiB
C#
Raw Normal View History

[FEATURE] Custom Tool Fix and Add inspection window for all the tools (#414) * Update .Bat file and Bug fix on ManageScript * Update the .Bat file to include runtime folder * Fix the inconsistent EditorPrefs variable so the GUI change on Script Validation could cause real change. * Further changes String to Int for consistency * [Custom Tool] Roslyn Runtime Compilation Allows users to generate/compile codes during Playmode * Fix based on CR * Create claude_skill_unity.zip Upload the unity_claude_skill that can be uploaded to Claude for a combo of unity-mcp-skill. * Update for Custom_Tool Fix and Detection 1. Fix Original Roslyn Compilation Custom Tool to fit the V8 standard 2. Add a new panel in the GUI to see and toggle/untoggle the tools. The toggle feature will be implemented in the future, right now its implemented here to discuss with the team if this is a good feature to add; 3. Add few missing summary in certain tools * Revert "Update for Custom_Tool Fix and Detection" This reverts commit ae8cfe5e256c70ac4a16c79d50341a39cbac18ba. * Update README.md * Reapply "Update for Custom_Tool Fix and Detection" This reverts commit f423c2f25e9ccff4f3b89d1d360ee9cf13143733. * Update ManageScript.cs Fix the layout problem of manage_script in the panel * Update To comply with the current server setting * Update on Batch Tested object generation/modification with batch and it works perfectly! We should push and let users test for a while and see PS: I tried both VS Copilot and Claude Desktop. Claude Desktop works but VS Copilot does not due to the nested structure of batch. Will look into it more. * Revert "Merge pull request #1 from Scriptwonder/batching" This reverts commit 55ee76810be161d414e1f5f5abaa5ee30ddd0052, reversing changes made to ae2eedd7fb2c6a66ff008bacac481aefb1b0d176.
2025-12-08 08:38:32 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
using UnityEditor;
using UnityEngine.UIElements;
namespace MCPForUnity.Editor.Windows.Components.Tools
{
/// <summary>
/// Controller for the Tools section inside the MCP For Unity editor window.
/// Provides discovery, filtering, and per-tool enablement toggles.
/// </summary>
public class McpToolsSection
{
private readonly Dictionary<string, Toggle> toolToggleMap = new();
private Label summaryLabel;
private Label noteLabel;
private Button enableAllButton;
private Button disableAllButton;
private Button rescanButton;
private VisualElement categoryContainer;
private List<ToolMetadata> allTools = new();
public VisualElement Root { get; }
public McpToolsSection(VisualElement root)
{
Root = root;
CacheUIElements();
RegisterCallbacks();
}
private void CacheUIElements()
{
summaryLabel = Root.Q<Label>("tools-summary");
noteLabel = Root.Q<Label>("tools-note");
enableAllButton = Root.Q<Button>("enable-all-button");
disableAllButton = Root.Q<Button>("disable-all-button");
rescanButton = Root.Q<Button>("rescan-button");
categoryContainer = Root.Q<VisualElement>("tool-category-container");
}
private void RegisterCallbacks()
{
if (enableAllButton != null)
{
enableAllButton.AddToClassList("tool-action-button");
enableAllButton.style.marginRight = 4;
enableAllButton.clicked += () => SetAllToolsState(true);
}
if (disableAllButton != null)
{
disableAllButton.AddToClassList("tool-action-button");
disableAllButton.style.marginRight = 4;
disableAllButton.clicked += () => SetAllToolsState(false);
}
if (rescanButton != null)
{
rescanButton.AddToClassList("tool-action-button");
rescanButton.clicked += () =>
{
McpLog.Info("Rescanning MCP tools from the editor window.");
MCPServiceLocator.ToolDiscovery.InvalidateCache();
Refresh();
};
}
}
/// <summary>
/// Rebuilds the tool list and synchronises toggle states.
/// </summary>
public void Refresh()
{
toolToggleMap.Clear();
categoryContainer?.Clear();
var service = MCPServiceLocator.ToolDiscovery;
allTools = service.DiscoverAllTools()
.OrderBy(tool => IsBuiltIn(tool) ? 0 : 1)
.ThenBy(tool => tool.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
bool hasTools = allTools.Count > 0;
enableAllButton?.SetEnabled(hasTools);
disableAllButton?.SetEnabled(hasTools);
if (noteLabel != null)
{
noteLabel.style.display = hasTools ? DisplayStyle.Flex : DisplayStyle.None;
}
if (!hasTools)
{
AddInfoLabel("No MCP tools found. Add classes decorated with [McpForUnityTool] to expose tools.");
UpdateSummary();
return;
}
BuildCategory("Built-in Tools", "built-in", allTools.Where(IsBuiltIn));
var customTools = allTools.Where(tool => !IsBuiltIn(tool)).ToList();
if (customTools.Count > 0)
{
BuildCategory("Custom Tools", "custom", customTools);
}
else
{
AddInfoLabel("No custom tools detected in loaded assemblies.");
}
UpdateSummary();
}
private void BuildCategory(string title, string prefsSuffix, IEnumerable<ToolMetadata> tools)
{
var toolList = tools.ToList();
if (toolList.Count == 0)
{
return;
}
var foldout = new Foldout
{
text = $"{title} ({toolList.Count})",
value = EditorPrefs.GetBool(EditorPrefKeys.ToolFoldoutStatePrefix + prefsSuffix, true)
};
foldout.RegisterValueChangedCallback(evt =>
{
EditorPrefs.SetBool(EditorPrefKeys.ToolFoldoutStatePrefix + prefsSuffix, evt.newValue);
});
foreach (var tool in toolList)
{
foldout.Add(CreateToolRow(tool));
}
categoryContainer?.Add(foldout);
}
private VisualElement CreateToolRow(ToolMetadata tool)
{
var row = new VisualElement();
row.AddToClassList("tool-item");
var header = new VisualElement();
header.AddToClassList("tool-item-header");
var toggle = new Toggle(tool.Name)
{
value = MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name)
};
toggle.AddToClassList("tool-item-toggle");
toggle.tooltip = string.IsNullOrWhiteSpace(tool.Description) ? tool.Name : tool.Description;
toggle.RegisterValueChangedCallback(evt =>
{
HandleToggleChange(tool, evt.newValue);
});
toolToggleMap[tool.Name] = toggle;
header.Add(toggle);
var tagsContainer = new VisualElement();
tagsContainer.AddToClassList("tool-tags");
bool defaultEnabled = tool.AutoRegister || tool.IsBuiltIn;
tagsContainer.Add(CreateTag(defaultEnabled ? "On by default" : "Off by default"));
tagsContainer.Add(CreateTag(tool.StructuredOutput ? "Structured output" : "Free-form"));
if (tool.RequiresPolling)
{
tagsContainer.Add(CreateTag($"Polling: {tool.PollAction}"));
}
header.Add(tagsContainer);
row.Add(header);
if (!string.IsNullOrWhiteSpace(tool.Description))
{
var description = new Label(tool.Description);
description.AddToClassList("tool-item-description");
row.Add(description);
}
if (tool.Parameters != null && tool.Parameters.Count > 0)
{
var paramSummary = string.Join(", ", tool.Parameters.Select(p =>
$"{p.Name}{(p.Required ? string.Empty : " (optional)")}: {p.Type}"));
var parametersLabel = new Label(paramSummary);
parametersLabel.AddToClassList("tool-parameters");
row.Add(parametersLabel);
}
return row;
}
private void HandleToggleChange(ToolMetadata tool, bool enabled, bool updateSummary = true)
{
MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled);
if (updateSummary)
{
UpdateSummary();
}
}
private void SetAllToolsState(bool enabled)
{
foreach (var tool in allTools)
{
if (!toolToggleMap.TryGetValue(tool.Name, out var toggle))
{
MCPServiceLocator.ToolDiscovery.SetToolEnabled(tool.Name, enabled);
continue;
}
if (toggle.value == enabled)
{
continue;
}
toggle.SetValueWithoutNotify(enabled);
HandleToggleChange(tool, enabled, updateSummary: false);
}
UpdateSummary();
}
private void UpdateSummary()
{
if (summaryLabel == null)
{
return;
}
if (allTools.Count == 0)
{
summaryLabel.text = "No MCP tools discovered.";
return;
}
int enabledCount = allTools.Count(tool => MCPServiceLocator.ToolDiscovery.IsToolEnabled(tool.Name));
summaryLabel.text = $"{enabledCount} of {allTools.Count} tools will register with connected clients.";
}
private void AddInfoLabel(string message)
{
var label = new Label(message);
label.AddToClassList("help-text");
categoryContainer?.Add(label);
}
private static Label CreateTag(string text)
{
var tag = new Label(text);
tag.AddToClassList("tool-tag");
return tag;
}
private static bool IsBuiltIn(ToolMetadata tool) => tool?.IsBuiltIn ?? false;
}
}