238 lines
10 KiB
C++
238 lines
10 KiB
C++
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "MCPUtils.h"
|
|
class FSocket;
|
|
class IMCPHandler;
|
|
|
|
/**
|
|
* FMCPServer — plain C++ class (not a UCLASS) that implements the
|
|
* Model Context Protocol (MCP) over a TCP socket using JSON-RPC.
|
|
*
|
|
* Clients connect via TCP and exchange newline-delimited JSON-RPC messages
|
|
* (the MCP "stdio" protocol). Each connected client gets its own thread
|
|
* for blocking I/O; tool calls are dispatched on the game thread.
|
|
*
|
|
* Both the standalone commandlet (UBlueprintMCPCommandlet) and the in-editor
|
|
* subsystem (UBlueprintMCPEditorSubsystem) delegate to an instance of this
|
|
* class. The only difference is *who ticks the engine*:
|
|
* - Commandlet: manual FTSTicker loop
|
|
* - Editor subsystem: UE editor tick via FTickableEditorObject
|
|
*/
|
|
class FMCPServer
|
|
{
|
|
public:
|
|
/** Get the active server instance via the editor subsystem. */
|
|
static FMCPServer* Get();
|
|
|
|
/** Start listening for MCP clients on the given TCP port. */
|
|
bool Start(int32 InPort, bool bEditorMode = false);
|
|
|
|
/** Stop the server: drain pending requests, close all sockets, join threads. */
|
|
void Stop();
|
|
|
|
/**
|
|
* Process pending MCP requests on the game thread.
|
|
* Call this every tick from whichever host owns this server.
|
|
* Returns true if a request was processed.
|
|
*/
|
|
bool ProcessOneRequest();
|
|
|
|
/** Whether the server is currently listening. */
|
|
bool IsRunning() const { return bRunning; }
|
|
|
|
/** Port the server is listening on. */
|
|
int32 GetPort() const { return Port; }
|
|
|
|
private:
|
|
// ----- Tool dispatch -----
|
|
using FRequestHandler = TFunction<void(const FJsonObject* Json, FJsonObject* Result)>;
|
|
TMap<FString, FRequestHandler> HandlerMap; // old-style handlers
|
|
TMap<FString, UClass*> MCPHandlerRegistry; // new-style: tool name -> UMCPHandler subclass
|
|
TSet<FString> MutationEndpoints;
|
|
void RegisterHandlers();
|
|
void BuildMCPHandlerRegistry();
|
|
|
|
// Dispatch a tool call to the appropriate handler
|
|
void DispatchToolCall(const FString& ToolName, const FJsonObject* Params, FJsonObject* Result);
|
|
|
|
// ----- MCP protocol -----
|
|
// Handle a complete JSON-RPC line and return the response line (or empty for notifications)
|
|
FString HandleJsonRpc(const FString& Line);
|
|
|
|
// MCP method handlers
|
|
FString HandleInitialize(int32 Id, const FJsonObject* Params);
|
|
FString HandleToolsList(int32 Id);
|
|
FString HandleToolsCall(int32 Id, const FJsonObject* Params);
|
|
|
|
// Build the tools/list response (cached after first call)
|
|
TSharedPtr<FJsonObject> CachedToolsList;
|
|
static TSharedRef<FJsonObject> FPropertyToPropSchema(FProperty* Prop);
|
|
static TSharedRef<FJsonObject> HandlerClassToToolSchema(UClass* HandlerClass);
|
|
void BuildCachedToolsList();
|
|
|
|
// JSON-RPC helpers
|
|
static FString MakeJsonRpcResult(int32 Id, TSharedPtr<FJsonObject> Result);
|
|
static FString MakeJsonRpcToolResult(int32 Id, TSharedPtr<FJsonObject> ToolResult, bool bIsError);
|
|
static FString MakeJsonRpcError(int32 Id, int32 Code, const FString& Message);
|
|
|
|
// ----- TCP server -----
|
|
FSocket* ListenSocket = nullptr;
|
|
int32 Port = 9847;
|
|
bool bRunning = false;
|
|
bool bIsEditor = false;
|
|
|
|
// ----- Client connections -----
|
|
struct FClientConnection
|
|
{
|
|
FSocket* Socket = nullptr;
|
|
TFuture<void> ThreadFuture;
|
|
bool bDone = false;
|
|
};
|
|
TArray<TSharedPtr<FClientConnection>> Clients;
|
|
void AcceptNewConnections();
|
|
void CleanupFinishedClients();
|
|
static void ClientThreadFunc(FMCPServer* Server, TSharedPtr<FClientConnection> Client);
|
|
|
|
// ----- Thread-safe message queue -----
|
|
struct FPendingMessage
|
|
{
|
|
FString Line;
|
|
TPromise<FString> Response;
|
|
FPendingMessage() : Response(TPromise<FString>()) {}
|
|
};
|
|
FCriticalSection Mutex;
|
|
TArray<TSharedPtr<FPendingMessage>> PendingMessages;
|
|
bool bShuttingDown = false;
|
|
|
|
// ----- Request handlers (read-only) -----
|
|
void HandleList(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSearch(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleFindReferences(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSearchByType(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Request handlers (write) -----
|
|
void HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleChangeFunctionParamType(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRemoveFunctionParameter(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Pin introspection (read-only) -----
|
|
void HandleGetPinInfo(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleCheckPinCompatibility(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Class/function discovery (read-only) -----
|
|
void HandleListClasses(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListFunctions(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListProperties(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Reparent -----
|
|
void HandleReparentBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Create -----
|
|
void HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Graph manipulation -----
|
|
void HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Variables -----
|
|
void HandleAddVariable(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Event Dispatchers -----
|
|
void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Function Parameters -----
|
|
void HandleAddFunctionParameter(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Components -----
|
|
void HandleAddComponent(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRemoveComponent(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListComponents(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Diagnostic -----
|
|
void HandleTestSave(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Snapshot / Safety tools (write) -----
|
|
void HandleSnapshotGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleDiffGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRestoreGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material read-only handlers (Phase 1) -----
|
|
void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetMaterial(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleDescribeMaterial(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSearchMaterials(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleFindMaterialReferences(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material mutation handlers (Phase 2) -----
|
|
void HandleCreateMaterial(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetMaterialProperty(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAddMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleDeleteMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleConnectMaterialPins(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleDisconnectMaterialPin(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetExpressionValue(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleMoveMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material instance handlers (Phase 3) -----
|
|
void HandleCreateMaterialInstance(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetMaterialInstanceParameter(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetMaterialInstanceParameters(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleReparentMaterialInstance(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material function handlers (Phase 4) -----
|
|
void HandleListMaterialFunctions(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleGetMaterialFunction(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleCreateMaterialFunction(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material validation -----
|
|
void HandleValidateMaterial(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Material snapshot/diff/restore (Phase 5) -----
|
|
void HandleSnapshotMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleDiffMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRestoreMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
// ----- Animation Blueprint handlers -----
|
|
void HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAddAnimState(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleRemoveAnimState(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAddAnimTransition(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetTransitionRule(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAddAnimNode(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleAddStateMachine(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetStateAnimation(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListAnimSlots(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleListSyncGroups(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleCreateBlendSpace(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetBlendSpaceSamples(const FJsonObject* Json, FJsonObject* Result);
|
|
void HandleSetStateBlendSpace(const FJsonObject* Json, FJsonObject* Result);
|
|
|
|
public:
|
|
// ----- Snapshot storage -----
|
|
TMap<FString, FGraphSnapshot> Snapshots;
|
|
TMap<FString, FGraphSnapshot> MaterialSnapshots;
|
|
static const int32 MaxSnapshots = 50;
|
|
|
|
// Snapshot helpers
|
|
FString GenerateSnapshotId(const FString& BlueprintName);
|
|
FGraphSnapshotData CaptureGraphSnapshot(UEdGraph* Graph);
|
|
void PruneOldSnapshots();
|
|
bool SaveSnapshotToDisk(const FString& SnapshotId, const FGraphSnapshot& Snapshot);
|
|
bool LoadSnapshotFromDisk(const FString& SnapshotId, FGraphSnapshot& OutSnapshot);
|
|
};
|
|
|
|
// Transitional alias — old-style handlers use this to access the server instance.
|
|
using FBlueprintMCPServer = FMCPServer;
|
|
using MCPHelper = FMCPServer;
|