Files

10 KiB

Unreal MCP — Claude Code Project Context

Ce fichier est lu automatiquement par Claude Code au démarrage de chaque session. Il documente l'architecture, les décisions techniques et le travail en cours.


Architecture globale

Le système est composé de 3 couches :

Claude Code (stdio)
    ↕  JSON via stdio
Python FastMCP server  (unreal_mcp_server.py)
    ↕  JSON via TCP socket (connexion par commande)
C++ UEditorSubsystem   (UnrealMCPBridge)
    ↕  GameThread via AsyncTask + Promise/Future
Unreal Engine Editor   (port TCP 55557, localhost)

Détails par couche

Claude Code — lit .claude/settings.json pour démarrer le serveur MCP Python via uv run.

Python FastMCP server (unreal_mcp_server.py) :

  • Utilise FastMCP avec transport stdio
  • Classe UnrealConnection : crée une nouvelle socket TCP par commande (connexion-par-commande, pas persistante)
  • Timeout de 5 secondes sur réception
  • 5 modules d'outils : editor_tools, blueprint_tools, node_tools, project_tools, umg_tools

C++ plugin (UnrealMCPBridge — UEditorSubsystem) :

  • S'initialise automatiquement au démarrage de l'éditeur via Initialize()
  • StartServer() → crée un listener TCP sur 127.0.0.1:55557 → spawn d'un thread FMCPServerRunnable
  • ExecuteCommand() : utilise AsyncTask(ENamedThreads::GameThread, ...) avec TPromise<FString> / TFuture<FString> — le thread réseau bloque jusqu'à la réponse du Game Thread
  • 5 catégories de handlers : Editor, Blueprint, BlueprintNode, Project, UMG

Structure des fichiers (plugin)

Plugins/UnrealMCP/
├── Source/UnrealMCP/
│   ├── Public/
│   │   ├── UnrealMCPBridge.h          — UEditorSubsystem, déclaration
│   │   ├── UnrealMCPModule.h          — IModuleInterface
│   │   ├── MCPServerRunnable.h        — FRunnableThread TCP listener
│   │   ├── Setup/
│   │   │   └── UnrealMCPSetupCommands.h   — [À CRÉER] menu "Claude Code"
│   │   └── Commands/
│   │       ├── UnrealMCPEditorCommands.h
│   │       ├── UnrealMCPBlueprintCommands.h
│   │       ├── UnrealMCPBlueprintNodeCommands.h
│   │       ├── UnrealMCPProjectCommands.h
│   │       ├── UnrealMCPUMGCommands.h
│   │       └── UnrealMCPCommonUtils.h
│   └── Private/
│       ├── UnrealMCPBridge.cpp        — logique principale, ExecuteCommand()
│       ├── UnrealMCPModule.cpp        — StartupModule / ShutdownModule
│       ├── MCPServerRunnable.cpp      — thread TCP, ProcessMessage()
│       ├── Setup/
│       │   └── UnrealMCPSetupCommands.cpp  — [À CRÉER] OnInitClaudeCode()
│       └── Commands/
│           ├── UnrealMCPEditorCommands.cpp
│           ├── UnrealMCPBlueprintCommands.cpp
│           ├── UnrealMCPBlueprintNodeCommands.cpp
│           ├── UnrealMCPProjectCommands.cpp
│           ├── UnrealMCPUMGCommands.cpp
│           └── UnrealMCPCommonUtils.cpp
├── PythonServer/                      — [À CRÉER] copie du serveur Python bundlé dans le plugin
│   ├── unreal_mcp_server.py
│   ├── pyproject.toml
│   ├── uv.lock
│   └── tools/
│       ├── editor_tools.py
│       ├── blueprint_tools.py
│       ├── node_tools.py
│       ├── project_tools.py
│       └── umg_tools.py
└── UnrealMCP.uplugin

Feature en cours : "Init Claude Code" menu

Objectif

Ajouter un menu "Claude Code" dans la barre de menu de l'éditeur UE. Ce menu contient un bouton "Init Claude Code" (tooltip: "Initialize Claude Code for this project (requires Claude Code restart)").

En cliquant, le plugin :

  1. Vérifie que uv est disponible dans le PATH
  2. Vérifie si .claude/ existe déjà (demande confirmation si oui)
  3. Copie <PluginDir>/PythonServer/ → <ProjectDir>/.claude/mcp-server/
  4. Écrit <ProjectDir>/.claude/settings.json (seulement si inexistant)
  5. Écrit <ProjectDir>/.claude/settings.local.json (toujours — permissions safe)
  6. Affiche un dialog de succès

Contenu de settings.json généré (chemin relatif — portable)

{
  "mcpServers": {
    "unrealMCP": {
      "type": "stdio",
      "command": "uv",
      "args": ["--directory", ".claude/mcp-server", "run", "unreal_mcp_server.py"]
    }
  }
}

Note : Le chemin .claude/mcp-server est relatif au répertoire projet (là où Claude Code est lancé). C'est ce qui rend la config portable entre machines — pas de chemin absolu.

Fichiers C++ à créer

Public/Setup/UnrealMCPSetupCommands.h

#pragma once
#include "CoreMinimal.h"

class FUnrealMCPSetupCommands
{
public:
    static void RegisterMenus();

private:
    static void OnInitClaudeCode();
    static void BuildMenu(UToolMenu* Menu);
    static bool IsUvAvailable();
    static bool CopyPythonServer(const FString& Src, const FString& Dest);
    static bool WriteClaudeSettings(const FString& ClaudeDir);
    static bool WriteClaudeLocalSettings(const FString& ClaudeDir);
};

Private/Setup/UnrealMCPSetupCommands.cpp — points clés :

// Menu bar extension
UToolMenu* MenuBar = UToolMenus::Get()->ExtendMenu("MainFrame.MainTabMenu");
FToolMenuSection& Section = MenuBar->FindOrAddSection("ClaudeCode");
FToolMenuEntry SubMenu = FToolMenuEntry::InitSubMenu(
    "ClaudeCodeMenu",
    LOCTEXT("ClaudeCodeMenuLabel", "Claude Code"),
    LOCTEXT("ClaudeCodeMenuTooltip", "Claude Code tools"),
    FNewToolMenuDelegate::CreateStatic(&FUnrealMCPSetupCommands::BuildMenu)
);
Section.AddEntry(SubMenu);

// Chemin source = plugin bundled PythonServer
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin("UnrealMCP");
const FString PythonServerSrc = Plugin->GetBaseDir() / TEXT("PythonServer");
const FString McpServerDest = FPaths::ProjectDir() / TEXT(".claude/mcp-server");

// Copie via IPlatformFile
IPlatformFile& PF = FPlatformFileManager::Get().GetPlatformFile();
PF.CopyDirectoryTree(*McpServerDest, *PythonServerSrc, true);

// Check uv via FPlatformProcess::ExecProcess
int32 ReturnCode = 0;
FString StdOut, StdErr;
FPlatformProcess::ExecProcess(TEXT("uv"), TEXT("--version"), &ReturnCode, &StdOut, &StdErr);
// ReturnCode == 0 → uv présent

UnrealMCPModule.cpp — enregistrement du callback :

void FUnrealMCPModule::StartupModule()
{
    if (GEditor)
    {
        UToolMenus::RegisterStartupCallback(
            FSimpleMulticastDelegate::FDelegate::CreateRaw(
                this, &FUnrealMCPModule::RegisterMenus));
    }
}

void FUnrealMCPModule::ShutdownModule()
{
    UToolMenus::UnRegisterStartupCallback(this);
}

void FUnrealMCPModule::RegisterMenus()
{
    FUnrealMCPSetupCommands::RegisterMenus();
}

UnrealMCPModule.h — ajouter :

private:
    void RegisterMenus();

Dépendances Build.cs déjà présentes

  • ToolMenus ✅ (dans le bloc bBuildEditor)
  • Projects ✅ (pour IPluginManager)
  • Slate, SlateCore ✅

Problèmes connus et workarounds

spawn_blueprint_actor → Timeout systématique

Cause : create_blueprint + compile_blueprint crée les blueprints en mémoire UE mais ne les sauvegarde pas sur disque (pas de .uasset). spawn_blueprint_actor tente de charger depuis le disque → attend indéfiniment → timeout. Workaround : Utiliser spawn_actor avec "StaticMeshActor" puis assigner le mesh manuellement dans l'éditeur.

set_actor_property("StaticMesh", ...) → "Property not found"

Cause : La propriété StaticMesh est sur le composant StaticMeshComponent, pas directement sur l'acteur AStaticMeshActor. Les chemins de type "StaticMeshComponent.StaticMesh" ne fonctionnent pas non plus via MCP. Workaround : set_static_mesh_properties fonctionne seulement sur les composants définis dans un Blueprint, pas sur des instances level.

spawn_actor avec nom de Blueprint → "Unknown actor type"

Cause : spawn_actor n'accepte que les types built-in UE ("StaticMeshActor", "PointLight", etc.). Les noms de classe Blueprint ne sont pas supportés.

Requêtes parallèles massives → Timeout

Cause : Trop de connexions TCP simultanées saturent le serveur. Workaround : Appels séquentiels ou par petits groupes (max ~5 en parallèle).


Configuration MCP active

.claude/settings.json (chemin à adapter dans le nouveau projet) :

{
  "mcpServers": {
    "unrealMCP": {
      "type": "stdio",
      "command": "uv",
      "args": ["--directory", "<chemin-vers-Python-server>", "run", "unreal_mcp_server.py"]
    }
  }
}

.claude/settings.local.json — 36 outils MCP pré-approuvés (déjà en place dans ce projet).


Outils MCP disponibles (36)

get_actors_in_level, find_actors_by_name, spawn_actor, delete_actor,
set_actor_transform, get_actor_properties, set_actor_property,
spawn_blueprint_actor, focus_viewport, take_screenshot,
create_blueprint, add_component_to_blueprint, set_static_mesh_properties,
set_component_property, set_physics_properties, set_blueprint_property,
set_pawn_properties, compile_blueprint, find_blueprint_nodes,
add_blueprint_event_node, add_blueprint_input_action_node,
add_blueprint_function_node, add_blueprint_variable,
add_blueprint_self_reference, add_blueprint_get_self_component_reference,
connect_blueprint_nodes, create_input_mapping,
create_umg_widget_blueprint, add_text_block_to_widget,
add_button_to_widget, bind_widget_event, set_text_block_binding,
add_widget_to_viewport

Prochaines étapes

  1. Créer PythonServer/ dans le plugin — copie des fichiers Python depuis Python/ (ou équivalent dans la nouvelle structure)
  2. Créer UnrealMCPSetupCommands.h et .cpp — voir design ci-dessus
  3. Modifier UnrealMCPModule.h et .cpp — enregistrer le callback RegisterMenus
  4. Compiler le plugin — vérifier que le menu "Claude Code" apparaît dans l'éditeur
  5. Tester "Init Claude Code" — depuis un projet vierge avec le plugin en submodule