Skip to main content
Glama

Unreal Engine MCP Bridge

by gingerol
unreal_mcp_server.py15.7 kB
""" Unreal Engine MCP Server A simple MCP server for interacting with Unreal Engine. """ import logging import socket import sys import json from contextlib import asynccontextmanager from typing import AsyncIterator, Dict, Any, Optional from mcp.server.fastmcp import FastMCP # Configure logging with more detailed format logging.basicConfig( level=logging.DEBUG, # Change to DEBUG level for more details format='%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s', handlers=[ logging.FileHandler('unreal_mcp.log'), # logging.StreamHandler(sys.stdout) # Remove this handler to unexpected non-whitespace characters in JSON ] ) logger = logging.getLogger("UnrealMCP") # Configuration UNREAL_HOST = "127.0.0.1" UNREAL_PORT = 55557 class UnrealConnection: """Connection to an Unreal Engine instance.""" def __init__(self): """Initialize the connection.""" self.socket = None self.connected = False def connect(self) -> bool: """Connect to the Unreal Engine instance.""" try: # Close any existing socket if self.socket: try: self.socket.close() except: pass self.socket = None logger.info(f"Connecting to Unreal at {UNREAL_HOST}:{UNREAL_PORT}...") self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(5) # 5 second timeout # Set socket options for better stability self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # Set larger buffer sizes self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536) self.socket.connect((UNREAL_HOST, UNREAL_PORT)) self.connected = True logger.info("Connected to Unreal Engine") return True except Exception as e: logger.error(f"Failed to connect to Unreal: {e}") self.connected = False return False def disconnect(self): """Disconnect from the Unreal Engine instance.""" if self.socket: try: self.socket.close() except: pass self.socket = None self.connected = False def receive_full_response(self, sock, buffer_size=4096) -> bytes: """Receive a complete response from Unreal, handling chunked data.""" chunks = [] sock.settimeout(5) # 5 second timeout try: while True: chunk = sock.recv(buffer_size) if not chunk: if not chunks: raise Exception("Connection closed before receiving data") break chunks.append(chunk) # Process the data received so far data = b''.join(chunks) decoded_data = data.decode('utf-8') # Try to parse as JSON to check if complete try: json.loads(decoded_data) logger.info(f"Received complete response ({len(data)} bytes)") return data except json.JSONDecodeError: # Not complete JSON yet, continue reading logger.debug(f"Received partial response, waiting for more data...") continue except Exception as e: logger.warning(f"Error processing response chunk: {str(e)}") continue except socket.timeout: logger.warning("Socket timeout during receive") if chunks: # If we have some data already, try to use it data = b''.join(chunks) try: json.loads(data.decode('utf-8')) logger.info(f"Using partial response after timeout ({len(data)} bytes)") return data except: pass raise Exception("Timeout receiving Unreal response") except Exception as e: logger.error(f"Error during receive: {str(e)}") raise def send_command(self, command: str, params: Dict[str, Any] = None) -> Optional[Dict[str, Any]]: """Send a command to Unreal Engine and get the response.""" # Always reconnect for each command, since Unreal closes the connection after each command # This is different from Unity which keeps connections alive if self.socket: try: self.socket.close() except: pass self.socket = None self.connected = False if not self.connect(): logger.error("Failed to connect to Unreal Engine for command") return None try: # Match Unity's command format exactly command_obj = { "type": command, # Use "type" instead of "command" "params": params or {} # Use Unity's params or {} pattern } # Send without newline, exactly like Unity command_json = json.dumps(command_obj) logger.info(f"Sending command: {command_json}") self.socket.sendall(command_json.encode('utf-8')) # Read response using improved handler response_data = self.receive_full_response(self.socket) response = json.loads(response_data.decode('utf-8')) # Log complete response for debugging logger.info(f"Complete response from Unreal: {response}") # Check for both error formats: {"status": "error", ...} and {"success": false, ...} if response.get("status") == "error": error_message = response.get("error") or response.get("message", "Unknown Unreal error") logger.error(f"Unreal error (status=error): {error_message}") # We want to preserve the original error structure but ensure error is accessible if "error" not in response: response["error"] = error_message elif response.get("success") is False: # This format uses {"success": false, "error": "message"} or {"success": false, "message": "message"} error_message = response.get("error") or response.get("message", "Unknown Unreal error") logger.error(f"Unreal error (success=false): {error_message}") # Convert to the standard format expected by higher layers response = { "status": "error", "error": error_message } # Always close the connection after command is complete # since Unreal will close it on its side anyway try: self.socket.close() except: pass self.socket = None self.connected = False return response except Exception as e: logger.error(f"Error sending command: {e}") # Always reset connection state on any error self.connected = False try: self.socket.close() except: pass self.socket = None return { "status": "error", "error": str(e) } # Global connection state _unreal_connection: UnrealConnection = None def get_unreal_connection() -> Optional[UnrealConnection]: """Get the connection to Unreal Engine.""" global _unreal_connection try: if _unreal_connection is None: _unreal_connection = UnrealConnection() if not _unreal_connection.connect(): logger.warning("Could not connect to Unreal Engine") _unreal_connection = None else: # Verify connection is still valid with a ping-like test try: # Simple test by sending an empty buffer to check if socket is still connected _unreal_connection.socket.sendall(b'\x00') logger.debug("Connection verified with ping test") except Exception as e: logger.warning(f"Existing connection failed: {e}") _unreal_connection.disconnect() _unreal_connection = None # Try to reconnect _unreal_connection = UnrealConnection() if not _unreal_connection.connect(): logger.warning("Could not reconnect to Unreal Engine") _unreal_connection = None else: logger.info("Successfully reconnected to Unreal Engine") return _unreal_connection except Exception as e: logger.error(f"Error getting Unreal connection: {e}") return None @asynccontextmanager async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]: """Handle server startup and shutdown.""" global _unreal_connection logger.info("UnrealMCP server starting up") try: _unreal_connection = get_unreal_connection() if _unreal_connection: logger.info("Connected to Unreal Engine on startup") else: logger.warning("Could not connect to Unreal Engine on startup") except Exception as e: logger.error(f"Error connecting to Unreal Engine on startup: {e}") _unreal_connection = None try: yield {} finally: if _unreal_connection: _unreal_connection.disconnect() _unreal_connection = None logger.info("Unreal MCP server shut down") # Initialize server mcp = FastMCP( "UnrealMCP", description="Unreal Engine integration via Model Context Protocol", lifespan=server_lifespan ) # Import and register tools from tools.editor_tools import register_editor_tools from tools.blueprint_tools import register_blueprint_tools from tools.node_tools import register_blueprint_node_tools from tools.project_tools import register_project_tools from tools.umg_tools import register_umg_tools # Register tools register_editor_tools(mcp) register_blueprint_tools(mcp) register_blueprint_node_tools(mcp) register_project_tools(mcp) register_umg_tools(mcp) @mcp.prompt() def info(): """Information about available Unreal MCP tools and best practices.""" return """ # Unreal MCP Server Tools and Best Practices ## UMG (Widget Blueprint) Tools - `create_umg_widget_blueprint(widget_name, parent_class="UserWidget", path="/Game/UI")` Create a new UMG Widget Blueprint - `add_text_block_to_widget(widget_name, text_block_name, text="", position=[0,0], size=[200,50], font_size=12, color=[1,1,1,1])` Add a Text Block widget with customizable properties - `add_button_to_widget(widget_name, button_name, text="", position=[0,0], size=[200,50], font_size=12, color=[1,1,1,1], background_color=[0.1,0.1,0.1,1])` Add a Button widget with text and styling - `bind_widget_event(widget_name, widget_component_name, event_name, function_name="")` Bind events like OnClicked to functions - `add_widget_to_viewport(widget_name, z_order=0)` Add widget instance to game viewport - `set_text_block_binding(widget_name, text_block_name, binding_property, binding_type="Text")` Set up dynamic property binding for text blocks ## Editor Tools ### Viewport and Screenshots - `focus_viewport(target, location, distance, orientation)` - Focus viewport - `take_screenshot(filename, show_ui, resolution)` - Capture screenshots ### Actor Management - `get_actors_in_level()` - List all actors in current level - `find_actors_by_name(pattern)` - Find actors by name pattern - `spawn_actor(name, type, location=[0,0,0], rotation=[0,0,0], scale=[1,1,1])` - Create actors - `delete_actor(name)` - Remove actors - `set_actor_transform(name, location, rotation, scale)` - Modify actor transform - `get_actor_properties(name)` - Get actor properties ## Blueprint Management - `create_blueprint(name, parent_class)` - Create new Blueprint classes - `add_component_to_blueprint(blueprint_name, component_type, component_name)` - Add components - `set_static_mesh_properties(blueprint_name, component_name, static_mesh)` - Configure meshes - `set_physics_properties(blueprint_name, component_name)` - Configure physics - `compile_blueprint(blueprint_name)` - Compile Blueprint changes - `set_blueprint_property(blueprint_name, property_name, property_value)` - Set properties - `set_pawn_properties(blueprint_name)` - Configure Pawn settings - `spawn_blueprint_actor(blueprint_name, actor_name)` - Spawn Blueprint actors ## Blueprint Node Management - `add_blueprint_event_node(blueprint_name, event_type)` - Add event nodes - `add_blueprint_input_action_node(blueprint_name, action_name)` - Add input nodes - `add_blueprint_function_node(blueprint_name, target, function_name)` - Add function nodes - `connect_blueprint_nodes(blueprint_name, source_node_id, source_pin, target_node_id, target_pin)` - Connect nodes - `add_blueprint_variable(blueprint_name, variable_name, variable_type)` - Add variables - `add_blueprint_get_self_component_reference(blueprint_name, component_name)` - Add component refs - `add_blueprint_self_reference(blueprint_name)` - Add self references - `find_blueprint_nodes(blueprint_name, node_type, event_type)` - Find nodes ## Project Tools - `create_input_mapping(action_name, key, input_type)` - Create input mappings ## Best Practices ### UMG Widget Development - Create widgets with descriptive names that reflect their purpose - Use consistent naming conventions for widget components - Organize widget hierarchy logically - Set appropriate anchors and alignment for responsive layouts - Use property bindings for dynamic updates instead of direct setting - Handle widget events appropriately with meaningful function names - Clean up widgets when no longer needed - Test widget layouts at different resolutions ### Editor and Actor Management - Use unique names for actors to avoid conflicts - Clean up temporary actors - Validate transforms before applying - Check actor existence before modifications - Take regular viewport screenshots during development - Keep the viewport focused on relevant actors during operations ### Blueprint Development - Compile Blueprints after changes - Use meaningful names for variables and functions - Organize nodes logically - Test functionality in isolation - Consider performance implications - Document complex setups ### Error Handling - Check command responses for success - Handle errors gracefully - Log important operations - Validate parameters - Clean up resources on errors """ # Run the server if __name__ == "__main__": logger.info("Starting MCP server with stdio transport") mcp.run(transport='stdio')

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gingerol/UnrealEngine-ai-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server