MCP can now control function args
This commit is contained in:
BIN
Content/Testing/BP_Test.uasset
LFS
Normal file
BIN
Content/Testing/BP_Test.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPServer.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "FunctionArgs.h"
|
||||
#include "GraphNode_SetArgs.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UMCP_GraphNode_SetArgs : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Path to a graph node (function entry, function result, custom event, or tunnel)"))
|
||||
FString Node;
|
||||
|
||||
UPROPERTY(meta=(Description="Comma-separated args, e.g. 'int x, float y'"))
|
||||
FString Args;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Set the user-defined pins (arguments or return values) on a function entry, result, custom event, or tunnel node.");
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
MCPFetcher F;
|
||||
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
|
||||
if (!NodeObj) return;
|
||||
|
||||
if (!MCPFunctionArgs::HasArgs(NodeObj))
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Node does not support editable args\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MCPFunctionArgs::SetArgs(NodeObj, Args)) return;
|
||||
|
||||
UMCPServer::Printf(TEXT("Args set to: %s\n"), *MCPFunctionArgs::GetArgs(NodeObj));
|
||||
}
|
||||
};
|
||||
@@ -81,7 +81,5 @@ public:
|
||||
UMCPServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n"));
|
||||
EmitCommandList(true);
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
MCPFetcher::PrintDocs();
|
||||
UMCPTypes::PrintDocs();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPServer.h"
|
||||
#include "UserManual.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UMCP_UserManual : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Print the user manual.");
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
UMCPServer::Print(TEXT(
|
||||
"\n PATHS:"
|
||||
"\n"
|
||||
"\n Most commands require you to specify a path. A path starts"
|
||||
"\n with an asset name, followed by comma-separated steps that"
|
||||
"\n navigate into the asset. Example:"
|
||||
"\n"
|
||||
"\n /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result"
|
||||
"\n"
|
||||
"\n The navigation steps supported are:"
|
||||
"\n"
|
||||
"\n graph — move from a blueprint or material to a graph."
|
||||
"\n node — move from a graph to a graph node"
|
||||
"\n pin — move from a graph node to a pin"
|
||||
"\n component — move from a blueprint to a component"
|
||||
"\n levelblueprint — move from a world to a blueprint"
|
||||
"\n"
|
||||
"\n DUMP COMMANDS:"
|
||||
"\n"
|
||||
"\n There are several commands whose names end in 'Dump'. These"
|
||||
"\n are essential tools for viewing the state of the world."
|
||||
"\n They are particularly important because they show you what"
|
||||
"\n unique IDs exist that you can refer to."
|
||||
"\n"
|
||||
"\n TYPES:"
|
||||
"\n"
|
||||
"\n To change variable types, or function prototypes, you will"
|
||||
"\n use our syntax for types. Here are some simple examples:"
|
||||
"\n"
|
||||
"\n boolean, int64, double, string, etc"
|
||||
"\n vector, rotator, hitresult, etc"
|
||||
"\n actor, character, playercontroller, etc"
|
||||
"\n eblendmode, emovementmode, etc"
|
||||
"\n"
|
||||
"\n Notice that it's 'actor', not 'AActor'."
|
||||
"\n You can use the following notations for complex types:"
|
||||
"\n"
|
||||
"\n soft<abp_manny>, class<pawn>, softclass<pawn>"
|
||||
"\n array<int>, set<string>, map<int, string>"
|
||||
"\n"
|
||||
"\n FUNCTION ARGUMENTS AND RETURN VALUES:"
|
||||
"\n"
|
||||
"\n Function argument lists are expressed as comma-separated"
|
||||
"\n lists containing type and variable name:"
|
||||
"\n"
|
||||
"\n double D, PlayerController P, array<int> A"
|
||||
"\n"
|
||||
"\n To change the arguments or return values of a function, edit the"
|
||||
"\n entry or exit node of the graph using GraphNode_SetArgs."
|
||||
"\n You can view the arguments using GraphNode_Dump. If a return "
|
||||
"\n node doesn't exist, you may have to create it using GraphNode_Create"
|
||||
"\n before you can set return values. Custom event nodes also have"
|
||||
"\n editable arguments."
|
||||
"\n"
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n"
|
||||
"\n We do not expose material expressions directly. Instead, you"
|
||||
"\n will be editing the material graph. However, if you Graph_Dump"
|
||||
"\n a material graph, you will see that the nodes contain mxprop"
|
||||
"\n properties, which actually come from the material expressions."
|
||||
"\n You can edit these using Property_Set on the node."
|
||||
"\n"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "FunctionArgs.h"
|
||||
#include "K2Node_EditablePinBase.h"
|
||||
#include "K2Node_FunctionResult.h"
|
||||
#include "K2Node_Tunnel.h"
|
||||
#include "MCPTypes.h"
|
||||
#include "MCPServer.h"
|
||||
|
||||
bool MCPFunctionArgs::HasArgs(UEdGraphNode* Node)
|
||||
{
|
||||
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
|
||||
if (!Editable) return false;
|
||||
return Editable->IsEditable();
|
||||
}
|
||||
|
||||
FString MCPFunctionArgs::GetArgs(UEdGraphNode* Node)
|
||||
{
|
||||
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
|
||||
if (!Editable) return FString();
|
||||
|
||||
TStringBuilder<256> SB;
|
||||
for (const TSharedPtr<FUserPinInfo>& Pin : Editable->UserDefinedPins)
|
||||
{
|
||||
if (SB.Len() > 0) SB << TEXT(", ");
|
||||
SB << UMCPTypes::TypeToText(Pin->PinType) << TEXT(" ") << Pin->PinName.ToString();
|
||||
}
|
||||
return FString(SB);
|
||||
}
|
||||
|
||||
EEdGraphPinDirection MCPFunctionArgs::GetPinDirection(UK2Node_EditablePinBase* Node)
|
||||
{
|
||||
// FunctionResult takes inputs; Tunnel depends on its flags; everything else outputs.
|
||||
if (Node->IsA<UK2Node_FunctionResult>())
|
||||
return EGPD_Input;
|
||||
if (UK2Node_Tunnel* Tunnel = Cast<UK2Node_Tunnel>(Node))
|
||||
return Tunnel->bCanHaveInputs ? EGPD_Input : EGPD_Output;
|
||||
return EGPD_Output;
|
||||
}
|
||||
|
||||
bool MCPFunctionArgs::ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs)
|
||||
{
|
||||
FString Trimmed = Args.TrimStartAndEnd();
|
||||
if (Trimmed.IsEmpty()) return true;
|
||||
|
||||
TArray<FString> Parts;
|
||||
Trimmed.ParseIntoArray(Parts, TEXT(","));
|
||||
|
||||
for (const FString& Part : Parts)
|
||||
{
|
||||
FString Token = Part.TrimStartAndEnd();
|
||||
if (Token.IsEmpty()) continue;
|
||||
|
||||
// Split "type name" on the last space.
|
||||
int32 LastSpace;
|
||||
if (!Token.FindLastChar(TEXT(' '), LastSpace))
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token);
|
||||
return false;
|
||||
}
|
||||
|
||||
FString TypeStr = Token.Left(LastSpace).TrimStartAndEnd();
|
||||
FString NameStr = Token.Mid(LastSpace + 1).TrimStartAndEnd();
|
||||
|
||||
if (TypeStr.IsEmpty() || NameStr.IsEmpty())
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token);
|
||||
return false;
|
||||
}
|
||||
|
||||
FParsedArg Arg;
|
||||
if (!UMCPTypes::TextToType(TypeStr, Arg.PinType)) return false;
|
||||
Arg.PinName = FName(*NameStr);
|
||||
OutArgs.Add(MoveTemp(Arg));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MCPFunctionArgs::SetArgs(UEdGraphNode* Node, const FString& Args)
|
||||
{
|
||||
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
|
||||
if (!Editable || !Editable->IsEditable())
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Node does not support editable pins\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the args string.
|
||||
TArray<FParsedArg> NewArgs;
|
||||
if (!ParseArgs(Args, NewArgs)) return false;
|
||||
|
||||
EEdGraphPinDirection Direction = GetPinDirection(Editable);
|
||||
|
||||
// Replace the UserDefinedPins array directly.
|
||||
Editable->UserDefinedPins.Empty();
|
||||
for (const FParsedArg& Arg : NewArgs)
|
||||
{
|
||||
TSharedPtr<FUserPinInfo> PinInfo = MakeShareable(new FUserPinInfo());
|
||||
PinInfo->PinName = Arg.PinName;
|
||||
PinInfo->PinType = Arg.PinType;
|
||||
PinInfo->DesiredPinDirection = Direction;
|
||||
Editable->UserDefinedPins.Add(PinInfo);
|
||||
}
|
||||
|
||||
// ReconstructNode rebuilds real pins from UserDefinedPins
|
||||
// and rewires old connections by matching pin names.
|
||||
Editable->ReconstructNode();
|
||||
return true;
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_FunctionEntry.h"
|
||||
#include "FunctionArgs.h"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
|
||||
MCPGraphExport::MCPGraphExport(UEdGraph* InGraph)
|
||||
@@ -213,6 +214,10 @@ void MCPGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
|
||||
Output.Appendf(TEXT("\nnode %s: %s\n"), *MCPUtils::FormatName(Node), *MCPUtils::FormatNodeTitle(Node));
|
||||
|
||||
// Emit function args (if applicable).
|
||||
if (MCPFunctionArgs::HasArgs(Node))
|
||||
Output.Appendf(TEXT(" args %s\n"), *MCPFunctionArgs::GetArgs(Node));
|
||||
|
||||
// Emit material expression properties (if applicable).
|
||||
EmitMaterialProperties(Node, Output, true);
|
||||
|
||||
@@ -239,7 +244,7 @@ void MCPGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
}
|
||||
if (!ReturnPins.IsEmpty())
|
||||
{
|
||||
Output.Appendf(TEXT(" return %s\n"), *ReturnPins);
|
||||
Output.Appendf(TEXT(" output-pins %s\n"), *ReturnPins);
|
||||
}
|
||||
|
||||
// Emit output exec pins as goto statements.
|
||||
|
||||
@@ -25,27 +25,6 @@ MCPFetcher::WalkFunc MCPFetcher::GetWalker(const FString& Step)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MCPFetcher::PrintDocs()
|
||||
{
|
||||
UMCPServer::Print(TEXT("Most commands require you to specify a path. A path starts\n"));
|
||||
UMCPServer::Print(TEXT("with an asset name, followed by comma-separated steps that\n"));
|
||||
UMCPServer::Print(TEXT("navigate into the asset. Example:\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT(" /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT("The navigation steps supported are:\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT(" graph — move from a blueprint or material to a graph.\n"));
|
||||
UMCPServer::Print(TEXT(" node — move from a graph to a graph node\n"));
|
||||
UMCPServer::Print(TEXT(" pin — move from a graph node to a pin\n"));
|
||||
UMCPServer::Print(TEXT(" component — move from a blueprint to a component\n"));
|
||||
UMCPServer::Print(TEXT(" levelblueprint — move from a world to a blueprint\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT("It is often useful to use 'dump' commands to see the contents\n"));
|
||||
UMCPServer::Print(TEXT("of an asset, so you know what objects exist to navigate into.\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
|
||||
void MCPFetcher::SetObj(UObject* InObj)
|
||||
{
|
||||
|
||||
@@ -522,19 +522,3 @@ UClass* UMCPTypes::TextToOneInterfaceType(const FString& Text)
|
||||
return Class;
|
||||
}
|
||||
|
||||
void UMCPTypes::PrintDocs()
|
||||
{
|
||||
UMCPServer::Printf(TEXT("To express types, use case-insensitive short names:\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT(" boolean, int64, double, string, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" vector, rotator, hitresult, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" actor, character, playercontroller, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" eblendmode, emovementmode, etc\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT("Notice that it's 'actor', not 'AActor'.\n"));
|
||||
UMCPServer::Printf(TEXT("You can use the following notations for complex types:\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT(" soft<abp_manny>, class<pawn>, softclass<pawn>\n"));
|
||||
UMCPServer::Printf(TEXT(" array<int>, set<string>, map<int, string>\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class UEdGraphNode;
|
||||
class UK2Node_EditablePinBase;
|
||||
struct FEdGraphPinType;
|
||||
|
||||
struct MCPFunctionArgs
|
||||
{
|
||||
// Returns true if the node is an EditablePinBase subclass that
|
||||
// actually supports user-defined pins (FunctionEntry, FunctionResult,
|
||||
// CustomEvent, or Tunnel).
|
||||
static bool HasArgs(UEdGraphNode* Node);
|
||||
|
||||
// Returns the user-defined pins as a string like "int x, float y".
|
||||
static FString GetArgs(UEdGraphNode* Node);
|
||||
|
||||
// Sets the user-defined pins from a string like "int x, float y".
|
||||
// Returns true on success.
|
||||
static bool SetArgs(UEdGraphNode* Node, const FString& Args);
|
||||
|
||||
private:
|
||||
// A parsed argument: type + name.
|
||||
struct FParsedArg
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
FName PinName;
|
||||
};
|
||||
|
||||
// Parse "int x, float y" into an array of FParsedArg.
|
||||
// Returns false and prints an error on failure.
|
||||
static bool ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs);
|
||||
|
||||
// Determine the pin direction for user-defined pins on this node.
|
||||
static EEdGraphPinDirection GetPinDirection(UK2Node_EditablePinBase* Node);
|
||||
};
|
||||
@@ -125,9 +125,6 @@ public:
|
||||
//
|
||||
MCPFetcher(UObject* O) : Obj(O) {}
|
||||
|
||||
// Print out the documentation for paths, for the LLM.
|
||||
//
|
||||
static void PrintDocs();
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ public:
|
||||
// no container, no wrapper). Returns nullptr and prints error on failure.
|
||||
static UClass* TextToOneInterfaceType(const FString& Text);
|
||||
|
||||
// Print the documentation for how types are expressed, for the LLM.
|
||||
static void PrintDocs();
|
||||
|
||||
private:
|
||||
FString TypeToTextInner(FName Category, FName SubCategory, UObject* SubCategoryObject);
|
||||
|
||||
@@ -21,6 +21,7 @@ TOOL_DESCRIPTION = (
|
||||
"The 'command' field specifies which operation to perform; "
|
||||
"additional fields are command-specific parameters. "
|
||||
'Use {"command": "ShowCommands"} to list available commands. '
|
||||
'Use {"command": "UserManual"} to get an overview. '
|
||||
"If the editor is not running, the call will return an error; "
|
||||
"just ask the user to start the editor and try again."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user