Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Create.h

104 lines
2.8 KiB
C
Raw Normal View History

2026-03-08 22:17:14 -04:00
#pragma once
#include "CoreMinimal.h"
#include "MCPServer.h"
2026-03-08 22:17:14 -04:00
#include "MCPHandler.h"
2026-03-10 07:17:42 -04:00
#include "MCPFetcher.h"
2026-03-15 19:03:29 -04:00
#include "MCPJson.h"
2026-03-08 22:17:14 -04:00
#include "MCPUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
2026-03-10 20:15:59 -04:00
#include "EdGraph/EdGraphSchema.h"
2026-03-12 00:44:17 -04:00
#include "GraphNode_Create.generated.h"
2026-03-08 22:17:14 -04:00
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FSpawnNodeEntry
{
GENERATED_BODY()
UPROPERTY()
FString ActionName;
UPROPERTY()
int32 PosX = 0;
UPROPERTY()
int32 PosY = 0;
};
2026-03-12 00:44:17 -04:00
UCLASS()
class UMCP_GraphNode_Create : public UObject, public IMCPHandler
2026-03-08 22:17:14 -04:00
{
GENERATED_BODY()
public:
2026-03-10 07:17:42 -04:00
UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph"))
2026-03-08 22:17:14 -04:00
FString Graph;
2026-03-12 00:44:17 -04:00
UPROPERTY(meta=(Description="Array of {actionName, posX, posY} objects. Use GraphNodeSearchTypes to find action names."))
2026-03-08 22:17:14 -04:00
FMCPJsonArray Nodes;
virtual FString GetDescription() const override
{
2026-03-10 20:15:59 -04:00
return TEXT("Create nodes in any graph (Blueprint, Material, etc.) using the editor's action database. "
"Can create ANY node type that appears in the editor's right-click menu. "
2026-03-12 00:44:17 -04:00
"Use GraphNodeSearchTypes first to find the exact action name.");
2026-03-08 22:17:14 -04:00
}
virtual void Handle() override
2026-03-08 22:17:14 -04:00
{
2026-03-13 13:46:12 -04:00
MCPFetcher F;
2026-03-10 07:17:42 -04:00
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
2026-03-08 22:17:14 -04:00
int32 SuccessCount = 0;
2026-03-10 07:17:42 -04:00
int32 TotalCount = Nodes.Array.Num();
2026-03-08 22:17:14 -04:00
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
{
FSpawnNodeEntry Entry;
2026-03-15 19:03:29 -04:00
if (!MCPJson::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal))
2026-03-10 07:17:42 -04:00
continue;
2026-03-08 22:17:14 -04:00
2026-03-10 20:15:59 -04:00
// Find the action by exact full name
TArray<TSharedPtr<FEdGraphSchemaAction>> Matches = MCPUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 0, /*ExactMatch=*/true);
2026-03-08 22:17:14 -04:00
if (Matches.Num() == 0)
{
UMCPServer::Printf(TEXT("ERROR: No action found matching '%s'. Use GraphNodeSearchTypes to find available actions.\n"),
2026-03-10 07:17:42 -04:00
*Entry.ActionName);
2026-03-08 22:17:14 -04:00
continue;
}
if (Matches.Num() > 1)
{
UMCPServer::Printf(TEXT("ERROR: Ambiguous: %d actions match '%s'.\n"),
2026-03-10 07:17:42 -04:00
Matches.Num(), *Entry.ActionName);
2026-03-08 22:17:14 -04:00
continue;
}
2026-03-10 20:15:59 -04:00
// Perform the action
2026-03-08 22:17:14 -04:00
FVector2D Location(Entry.PosX, Entry.PosY);
2026-03-10 20:15:59 -04:00
UEdGraphNode* NewNode = Matches[0]->PerformAction(TargetGraph, nullptr, Location, /*bSelectNewNode=*/false);
2026-03-08 22:17:14 -04:00
if (!NewNode)
{
UMCPServer::Printf(TEXT("ERROR: PerformAction returned null for '%s'.\n"), *Entry.ActionName);
2026-03-08 22:17:14 -04:00
continue;
}
if (!NewNode->NodeGuid.IsValid())
NewNode->CreateNewGuid();
UMCPServer::Printf(TEXT("Spawned: %s (%s)\n"),
2026-03-10 07:17:42 -04:00
*MCPUtils::FormatName(NewNode), *MCPUtils::FormatName(NewNode->GetClass()));
2026-03-08 22:17:14 -04:00
SuccessCount++;
}
UMCPServer::Printf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount);
2026-03-08 22:17:14 -04:00
}
};