How-To: Add MCP Tools to a Plugin
Goal: expose your plugin's editor operations to AI agents over Model Context Protocol (MCP), the way CrimsonEditorUtilities does. CrimsonCommonEditor gives you two things: the master switch every Crimson plugin honors, and a low-level tool base for cases the engine path can't express. This is an editor-only, C++ task.
CrimsonCommonEditor, and its .uplugin declares CrimsonCommon, ModelContextProtocol, and ToolsetRegistry in the Plugins array. CrimsonEditorUtilities is a complete working example.UCrimsonMCPSettings::IsEnabled() (from MCP/CrimsonMCPSettings.h). That single Project Settings toggle - Crimson -> Crimson MCP - is what lets users turn the whole suite's MCP surface off. Skipping the gate breaks that guarantee.1. Recommended: a UToolsetDefinition
UE 5.8's Toolset Registry is the modern, discoverable path: tools authored here show up in an agent's list_toolsets. Declare a UToolsetDefinition subclass with static UFUNCTION(meta = (AICallable)) functions - the class name becomes the toolset name and each function is one tool. Report failures with UKismetSystemLibrary::RaiseScriptError. Add ToolsetRegistry and Kismet to the editor module's .Build.cs.
// MyPluginToolset.h#include "ToolsetRegistry/ToolsetDefinition.h"#include "MyPluginToolset.generated.h"/** Tools for my plugin. */UCLASS(BlueprintType, Hidden)class UMyPluginToolset : public UToolsetDefinition{GENERATED_BODY()public:/*** One-line description the agent sees.* @param Name What this parameter means.*/UFUNCTION(meta = (AICallable), Category = "MyPlugin")static FString DoThing(const FString& Name);};
Register the toolset in the editor module's StartupModule (gated), and unregister on shutdown:
#include "ToolsetRegistry/UToolsetRegistry.h"#include "MCP/CrimsonMCPSettings.h"void FMyPluginEditorModule::StartupModule(){if (UCrimsonMCPSettings::IsEnabled()){UToolsetRegistry::RegisterToolsetClass(UMyPluginToolset::StaticClass());}}void FMyPluginEditorModule::ShutdownModule(){if (UToolsetRegistry::IsAvailable()){UToolsetRegistry::UnregisterToolsetClass(UMyPluginToolset::StaticClass());}}
list_toolsets. Run ModelContextProtocol.RefreshTools after live-coding a tool method.2. Advanced: the low-level tool base
For tools that need richer results than the toolset path expresses - explicit error flags, structured JSON, images, audio, or async operations - CrimsonCommonEditor ships a typed base. Define a USTRUCT of params, subclass TCrimsonMCPTool<T>, and return with the Crimson::MCP:: helpers. Add ModelContextProtocol, Json, and JsonUtilities to the .Build.cs.
#include "MCP/CrimsonMCPTool.h"#include "MCP/CrimsonMCPResults.h"USTRUCT()struct FMyParams { GENERATED_BODY()UPROPERTY(meta = (ToolTip = "What to do.")) FString Name;};struct FMyTool : TCrimsonMCPTool<FMyParams>{virtual FString GetName() const override { return TEXT("myplugin.do_thing"); }virtual FString GetDescription() const override { return TEXT("Does the thing."); }virtual FModelContextProtocolToolResult RunWithParams(const FMyParams& P) override{if (P.Name.IsEmpty()) return Crimson::MCP::Error(TEXT("Name is required."));return Crimson::MCP::Text(TEXT("Done."));}};
Register these through an FCrimsonMCPToolRegistrar member on your editor module - it re-adds tools when ModelContextProtocol.RefreshTools runs, and consults the master switch for you inside Register():
// Module member: FCrimsonMCPToolRegistrar MCPTools;#include "MCP/CrimsonMCPRegistrar.h"void FMyPluginEditorModule::StartupModule(){MCPTools.Emplace<FMyTool>();MCPTools.Register(); // no-op unless UCrimsonMCPSettings::IsEnabled()}void FMyPluginEditorModule::ShutdownModule(){MCPTools.Unregister();}
list_toolsets. Prefer the UToolsetDefinition path for anything an agent should discover; reserve the low-level base for image / audio / struct / async results.See also
- CrimsonEditorUtilities -> Quick Start - enable Crimson MCP and connect an agent
- CrimsonEditorUtilities -> Concept: How Crimson MCP Works - toolsets, tool search, and the master-switch cascade