Files

8.5 KiB

description
description
modification de l'installer pour tout intégrer dans le plugin unreal engine.

Architecture complète du plugin mcp avec Claude Code : 3 couches

Claude Code ←──stdio──→ Python MCP Server ←──TCP/55557──→ Plugin C++ Unreal (toi) (unreal_mcp_server.py) (UnrealMCPBridge) Couche 1 — Configuration Claude (.claude/settings.json) C'est le point d'entrée : Claude Code doit savoir comment démarrer le serveur MCP.

{ "mcpServers": { "unrealMCP": { "type": "stdio", "command": "uv", "args": [ "--directory", "C:\...\Python", "run", "unreal_mcp_server.py" ] } } } Ce qui se passe : Quand Claude Code démarre, il lit ce fichier, lance uv run unreal_mcp_server.py comme sous-processus, et communique avec lui via stdin/stdout (stdio = protocole MCP standard). Claude ne connaît pas Unreal, ne connaît pas TCP — il voit juste des "tools" exposés par ce serveur. C'est le seul fichier que tu changes pour intégrer dans un autre projet.

Couche 2 — Serveur Python MCP (unreal_mcp_server.py + tools/) C'est le traducteur entre le protocole MCP (Claude) et le protocole TCP (Unreal).

unreal_mcp_server.py fait deux choses :

A) Il expose des tools MCP à Claude via FastMCP (la lib Python officielle MCP) :

mcp = FastMCP("UnrealMCP", ...) register_editor_tools(mcp) # spawn_actor, get_actors_in_level... register_blueprint_tools(mcp) # create_blueprint, compile_blueprint... register_blueprint_node_tools(mcp) register_project_tools(mcp) register_umg_tools(mcp) mcp.run(transport='stdio') # ← communique avec Claude via stdio B) Il gère la connexion TCP vers Unreal via la classe UnrealConnection :

Pour chaque commande Claude → nouveau socket TCP sur 127.0.0.1:55557

command_obj = {"type": "spawn_actor", "params": {...}} self.socket.sendall(json.dumps(command_obj).encode('utf-8')) response_data = self.receive_full_response(self.socket) Les fichiers tools/*.py définissent chaque outil individuellement. Exemple dans editor_tools.py :

@mcp.tool() def spawn_actor(ctx, name, type, location=[0,0,0], rotation=[0,0,0]): unreal = get_unreal_connection() response = unreal.send_command("spawn_actor", { "name": name, "type": type, "location": location ... }) return response Le @mcp.tool() dit à Claude "cette fonction est un outil que tu peux appeler". La fonction traduit l'appel en JSON et l'envoie à Unreal en TCP.

Important : la connexion TCP est recréée à chaque commande (le plugin Unreal ferme le socket après chaque réponse, comme on peut le voir dans le send_command).

Couche 3 — Plugin C++ Unreal (Plugins/UnrealMCP/) C'est le serveur TCP intégré dans l'éditeur. Il tourne EN PERMANENCE dès qu'Unreal est ouvert.

UnrealMCPModule.cpp — Point d'entrée UE

void FUnrealMCPModule::StartupModule() { /* très simple */ } IMPLEMENT_MODULE(FUnrealMCPModule, UnrealMCP) C'est juste le bootstrap UE standard. Le vrai démarrage est dans le subsystem.

UnrealMCPBridge.cpp — Le cœur (UEditorSubsystem) C'est un EditorSubsystem : UE l'instancie automatiquement quand l'éditeur démarre.

void UUnrealMCPBridge::Initialize(FSubsystemCollectionBase& Collection) { Port = 55557; StartServer(); // ← ouvre le socket TCP dès le démarrage } StartServer() crée un socket TCP en écoute sur 127.0.0.1:55557, puis lance un thread dédié (FRunnableThread) pour ne pas bloquer le Game Thread :

ServerThread = FRunnableThread::Create( new FMCPServerRunnable(this, ListenerSocket), TEXT("UnrealMCPServerThread"), 0, TPri_Normal ); ExecuteCommand() — la partie critique. Quand un message JSON arrive du Python :

FString UUnrealMCPBridge::ExecuteCommand(const FString& CommandType, const TSharedPtr& Params) { // OBLIGATOIRE : tout appel à l'API Unreal doit être sur le Game Thread AsyncTask(ENamedThreads::GameThread, this, CommandType, Params, Promise mutable { if (CommandType == TEXT("spawn_actor")) ResultJson = EditorCommands->HandleCommand(CommandType, Params); else if (CommandType == TEXT("create_blueprint")) ResultJson = BlueprintCommands->HandleCommand(CommandType, Params); // ... dispatch vers les handlers Promise.SetValue(ResultString); // résultat JSON }); return Future.Get(); // bloque le thread réseau en attendant le Game Thread } Le pattern Promise/Future est essentiel : le thread réseau attend que le Game Thread ait exécuté la commande avant de renvoyer la réponse au Python.

MCPServerRunnable.cpp — Le thread réseau Tourne en boucle en attendant des connexions :

while (bRunning) { if (ListenerSocket->HasPendingConnection(bPending) && bPending) { ClientSocket = ListenerSocket->Accept(...); // Lit le JSON → extrait "type" et "params" FString Response = Bridge->ExecuteCommand(CommandType, Params); ClientSocket->Send(Response...); } FPlatformProcess::Sleep(0.1f); // évite busy-loop } Commands/ — Les handlers par domaine Fichier Commandes gérées UnrealMCPEditorCommands.cpp spawn_actor, delete_actor, set_actor_transform, spawn_blueprint_actor... UnrealMCPBlueprintCommands.cpp create_blueprint, add_component, compile_blueprint... UnrealMCPBlueprintNodeCommands.cpp add_blueprint_event_node, connect_blueprint_nodes... UnrealMCPUMGCommands.cpp create_umg_widget_blueprint, add_button_to_widget... UnrealMCPProjectCommands.cpp create_input_mapping... Résumé du flux complet

  1. Tu tapes "/build" dans Claude
  2. Claude appelle mcp__unrealMCP__spawn_actor(name="Wall", ...)
  3. → stdio → Python reçoit l'appel via FastMCP
  4. Python ouvre un socket TCP vers 127.0.0.1:55557
  5. Python envoie : {"type": "spawn_actor", "params": {"name": "Wall", ...}}
  6. C++ MCPServerRunnable reçoit le JSON sur son thread réseau
  7. C++ dispatch vers ExecuteCommand() → AsyncTask sur Game Thread
  8. Game Thread appelle EditorCommands->HandleCommand() → API Unreal native
  9. Résultat JSON renvoyé via Future → envoyé sur le socket TCP
  10. Python reçoit la réponse, la renvoie à Claude via stdio
  11. Claude voit {"status": "success", "result": {...}} Ce que tu ne peux PAS faire en Python (parce que l'API Unreal est thread-safe uniquement sur le Game Thread) — c'est pourquoi le C++ est indispensable : il est le seul à pouvoir faire l'AsyncTask(GameThread, ...) de manière sûre.

PROMPT en attente

J'aimerais automatiser le processus à la création d'un projet unreal en ajoutant un module cpp au projet afin qu'il créer tout les fichiers necessaire à la racine du projet unreal. ainsi, on pourrait juste cloner le plugin dans le dossier Plugin du projet unreeal, lancer la compilation, ajouter un menu dans unreal pour initialiser le projet claude code et là, le code

l'objectif de cette session de travail est de modifier le code du plugin afin que l'utilisateur puisse dans unreal initialiser le projet unreal complet pour être prêt à utiliser claude code. Donc il faut créer en c++ le bouton "init claude code" dont on a parlé hier. Je veux que le code soit bien découpé (pas tous dans un seul fichier). Nous pouvons même créer dans le plugin un nouveau module pour ces fonctionnalités. Les étapes utilisateur souhaités:

  • clone du repo dans le dossier plugin du projet unreal cible.
  • l'utilisateur lance unreal (le plugin compile automatiquement)
  • Un menu "Claude code" dans la menu bar est présent maintenant dans l'interface unreal.
  • Un bouton "Init Claude Code" avec l'infobulle "Initialisation de claude code pour ce projet" est présent dans ce menu. Il permet à l'utilisateur d'initialiser le projet pour l'utilisation de Claude Code.
  • Quand l'utilisateur clique ce bouton, une fenêtre de validation s'affiche pour demander : "étes-vous sûr de vouloir initialiser claude code pour ce projet ?". A noter que claude code ne peux être utilisé qu'avec des projet c++. Si ce n'est pas le cas, afficher un message "Claude Code ne peut être initialisé que dans un projet cpp".
  • si l'utilisateur valide l'initialisation, le plugin copie le dossier .claude, ainsi que le .mcp.json à la racine du projet unreal en adaptant les chemins en relatif.
  • si l'utilisateur ne valide pas. On ne fait rien.
  • Le plugin initialise uv et attend que le venv soit prés avant de continuer.
  • Quand tout est fini, il affiche une fenêtre (même en cas d'echec) recapitulant les modifications apportées et le status de l'instalattion (si tout c'est bien passé ou si il y a eu un/des problèmes). Peux tu faire ce travail ?