Files
integration/refactor.md

195 lines
6.9 KiB
Markdown
Raw Normal View History

# Handler Refactor Instructions
Plugins/BlueprintMCP/Source/BlueprintMCP contains an MCP server that allows
Claude Code to control Unreal functions. MCP servers accept different
commands; each command has a "handler."
We are converting all handlers from an old style to a new style. In the old
style, each handler is a method of FMCPServer. In the new style, each
handler is a UCLASS derived from IMCPHandler. You will be assigned one
source file to convert. **Only modify that one file.**
## Step 1: Study the Existing Code
Before editing, read these files carefully:
- `MCPHandler.h` — the IMCPHandler interface and marker structs
- `MCPUtils.h` — utility functions available to handlers
- `MCPAssetFinder.h` — asset lookup helpers (FindAsset, LoadAsset, etc.)
- `MCPHandlers_Interfaces.h` — a clean example of new-style handlers
- `MCPHandlers_Validation.h` — another example (shows Optional params, bool, int32)
- `MCPHandlers_AnimMutation.h` — another example (shows FMCPJsonArray)
All of these are in `Plugins/BlueprintMCP/Source/BlueprintMCP/`.
## Step 2: Convert Your File
Each old-style handler function becomes a new-style UCLASS. The mechanical
steps are:
### 2a. Change the file header
Replace the old includes with this pattern:
```cpp
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.h"
#include "MCPUtils.h"
// ... any other includes the handlers actually need ...
#include "MCPHandlers_YourFile.generated.h"
```
Remove `#include "MCPServer.h"` — new-style handlers don't need it.
Remove any includes that are no longer used after conversion. Keep only
what the handler bodies actually reference.
### 2b. Wrap each handler in a UCLASS
Each old-style function like this:
```cpp
void FMCPServer::HandleFoo(const FJsonObject* Json, FJsonObject* Result)
{
FString Name = Json->GetStringField(TEXT("name"));
// ... body ...
}
```
Becomes:
```cpp
UCLASS(meta=(ToolName="the_mcp_tool_name"))
class UMCPHandler_Foo : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Human-readable description of this parameter"))
FString Name;
virtual FString GetDescription() const override
{
return TEXT("Human-readable description of what this tool does.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
// ... body (minus the parameter extraction) ...
}
};
```
### 2c. Convert parameters to UPROPERTY fields
- Each `Json->GetStringField(TEXT("foo"))` at the top of the handler becomes
a `UPROPERTY` field on the class. The framework populates these
automatically before calling `Handle()`.
- **Naming rule**: The UPROPERTY field name's first character gets
lowercased to produce the JSON key. So `FString Blueprint;` maps to
JSON key `"blueprint"`. `FString PackagePath;` maps to `"packagePath"`.
Choose field names so that this automatic mapping produces the correct
JSON key (i.e. the same key the old code was reading).
- **Required vs optional**: Fields are required by default. Add
`meta=(Optional, Description="...")` for optional parameters. Optional
fields should have default values (e.g. `bool Foo = false;`,
`int32 Limit = 0;`, `FString Bar;` — empty string is the default).
- **Supported types**: `FString`, `bool`, `int32`, `float`, `FName`.
For JSON arrays use `FMCPJsonArray` (has `.Array` field of type
`TArray<TSharedPtr<FJsonValue>>`). For JSON objects use `FMCPJsonObject`
(has `.Json` field of type `TSharedPtr<FJsonObject>`).
- **Remove manual extraction**: Delete the `Json->GetStringField()`,
`Json->GetBoolField()`, etc. lines that read parameters. The UPROPERTY
fields already contain the values. You can still use `Json->HasField()`
to check whether an optional field was actually provided (for cases
where you need to distinguish "not provided" from "provided as default
value").
- **Remove manual validation of required fields**: The framework already
returns an error if a required UPROPERTY field is missing from JSON.
Delete checks like `if (Name.IsEmpty()) return MakeErrorJson(...)` for
required FString fields. However, keep validation that checks field
*content* (e.g. "must start with /Game").
### 2d. Avoid UPROPERTY / local variable name conflicts
If the old code has a local variable with the same name as your new
UPROPERTY field (e.g. UPROPERTY `FString Blueprint;` and local
`UBlueprint* Blueprint`), rename the local variable. Convention:
append `Obj` — e.g. `UBlueprint* BlueprintObj`, `USkeleton* SkeletonObj`.
### 2e. Tool name
The `ToolName` in the UCLASS meta must exactly match the tool name string
that was used in the old-style `H(TEXT("tool_name"), ...)` registration
in MCPServer.cpp. If you're unsure, search MCPServer.cpp for the old
handler function name to find the tool name.
### 2f. GetDescription()
Write a concise 1-2 sentence description of what the tool does. This is
shown to the LLM that calls the tool.
### 2g. Separator between classes
Put this separator between each UCLASS:
```cpp
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
```
## Step 3: Rename the File
After conversion, rename the file from `.cpp` to `.h`. The file is now a
header — UHT will process the GENERATED_BODY() macros during build.
## What NOT to Do
- **Do NOT modify MCPServer.h or MCPServer.cpp.** We will clean up old
registrations separately.
- **Do NOT modify MCPHandlers.cpp** (the include aggregator). We will add
the new include separately.
- **Do NOT add `#include "MCPServer.h"`** — new-style handlers are
self-registering via UHT reflection and don't need it.
- **Do NOT change the handler logic.** Convert the structure, not the
behavior. This is a mechanical refactor. If you see a bug that can be
fixed within your file (e.g. a typo), fix it. But if you see a bug that
would require editing other files to fix, leave the code as-is and insert
a prominent comment block like this:
```cpp
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// BUG: <description of the bug and what needs to change>
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
```
Another agent will deal with it later.
- **Do NOT rename JSON output fields.** The tool's output format must stay
identical.
- **Do NOT add or remove handlers.** Convert exactly what's there.
## Files to Convert
Each of these `.cpp` files contains old-style handlers to convert:
1. `MCPHandlers_Read.cpp`
2. `MCPHandlers_Discovery.cpp`
3. `MCPHandlers_Graphs.cpp`
4. `MCPHandlers_Variables.cpp`
5. `MCPHandlers_Params.cpp`
6. `MCPHandlers_Dispatchers.cpp`
7. `MCPHandlers_Components.cpp`
8. `MCPHandlers_Snapshot.cpp`
9. `MCPHandlers_MaterialRead.cpp`
10. `MCPHandlers_MaterialMutation.cpp`
11. `MCPHandlers_MaterialInstance.cpp`
12. `MCPHandlers_StateMachine.cpp`