ceshi
This commit is contained in:
68
Plugins/UnrealAgentLink/Resources/tools/README.md
Normal file
68
Plugins/UnrealAgentLink/Resources/tools/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# UnrealAgentLink 工具能力(增量)
|
||||
|
||||
## 服务端请求:Actor Management
|
||||
|
||||
|
||||
- 统一变换接口 `actor.set_transform`
|
||||
- 结构:`targets`(选择器) + `operation`(操作)
|
||||
- `targets` 字段:
|
||||
- `names`: 字符串数组,指定 Actor 名称。
|
||||
- `paths`: 字符串数组,指定 Actor 路径。
|
||||
- `filter`: 筛选器对象,支持 `class` (包含匹配), `name_pattern` (通配符), `exclude_classes` (排除类名数组)。
|
||||
- `operation` 字段:
|
||||
- `space`: `"World"` (默认) 或 `"Local"`。
|
||||
- `snap_to_floor`: `true` (执行贴地)。
|
||||
- `set`: 绝对值设置 (`location`, `rotation`, `scale`)。
|
||||
- `add`: 增量设置 (`location`, `rotation`, `scale`),支持负数。
|
||||
- `multiply`: 倍乘设置 (`location`, `rotation`, `scale`)。
|
||||
- 示例 1:单体绝对设置(Z=200)
|
||||
```json
|
||||
{
|
||||
"ver":"1.0","type":"req","id":"t1","method":"actor.set_transform",
|
||||
"params":{
|
||||
"targets": {"names": ["MyCube"]},
|
||||
"operation": {
|
||||
"set": {"location": {"z": 200}}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 示例 2:批量增量(所有灯光 Z 轴上移 500,局部坐标系)
|
||||
```json
|
||||
{
|
||||
"ver":"1.0","type":"req","id":"t2","method":"actor.set_transform",
|
||||
"params":{
|
||||
"targets": {
|
||||
"filter": {"class": "Light"}
|
||||
},
|
||||
"operation": {
|
||||
"space": "Local",
|
||||
"add": {"location": {"z": 500}}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 示例 3:多选倍乘(Cube_1 和 Sphere_2 放大 2 倍)
|
||||
```json
|
||||
{
|
||||
"ver":"1.0","type":"req","id":"t3","method":"actor.set_transform",
|
||||
"params":{
|
||||
"targets": {
|
||||
"names": ["Cube_1", "Sphere_2"]
|
||||
},
|
||||
"operation": {
|
||||
"multiply": {"scale": {"x": 2, "y": 2, "z": 2}}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 响应(code 200):
|
||||
```json
|
||||
{"ver":"1.0","type":"res","id":"t1","code":200,"result":{"count":1,"actors":[{"name":"MyCube",...}]}}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
"""
|
||||
Blueprint Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for creating and manipulating Blueprint assets in Unreal Engine.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_blueprint_tools(mcp: FastMCP):
|
||||
"""Register Blueprint tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def create_blueprint(
|
||||
ctx: Context,
|
||||
name: str,
|
||||
parent_class: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a new Blueprint class."""
|
||||
# Import inside function to avoid circular imports
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("create_blueprint", {
|
||||
"name": name,
|
||||
"parent_class": parent_class
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Blueprint creation response: {response}")
|
||||
return response or {}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating blueprint: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_component_to_blueprint(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
component_type: str,
|
||||
component_name: str,
|
||||
location: List[float] = [],
|
||||
rotation: List[float] = [],
|
||||
scale: List[float] = [],
|
||||
component_properties: Dict[str, Any] = {}
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a component to a Blueprint.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
component_type: Type of component to add (use component class name without U prefix)
|
||||
component_name: Name for the new component
|
||||
location: [X, Y, Z] coordinates for component's position
|
||||
rotation: [Pitch, Yaw, Roll] values for component's rotation
|
||||
scale: [X, Y, Z] values for component's scale
|
||||
component_properties: Additional properties to set on the component
|
||||
|
||||
Returns:
|
||||
Information about the added component
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
# Ensure all parameters are properly formatted
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"component_type": component_type,
|
||||
"component_name": component_name,
|
||||
"location": location or [0.0, 0.0, 0.0],
|
||||
"rotation": rotation or [0.0, 0.0, 0.0],
|
||||
"scale": scale or [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
# Add component_properties if provided
|
||||
if component_properties and len(component_properties) > 0:
|
||||
params["component_properties"] = component_properties
|
||||
|
||||
# Validate location, rotation, and scale formats
|
||||
for param_name in ["location", "rotation", "scale"]:
|
||||
param_value = params[param_name]
|
||||
if not isinstance(param_value, list) or len(param_value) != 3:
|
||||
logger.error(f"Invalid {param_name} format: {param_value}. Must be a list of 3 float values.")
|
||||
return {"success": False, "message": f"Invalid {param_name} format. Must be a list of 3 float values."}
|
||||
# Ensure all values are float
|
||||
params[param_name] = [float(val) for val in param_value]
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding component to blueprint with params: {params}")
|
||||
response = unreal.send_command("add_component_to_blueprint", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Component addition response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding component to blueprint: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def set_static_mesh_properties(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
component_name: str,
|
||||
static_mesh: str = "/Engine/BasicShapes/Cube.Cube"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set static mesh properties on a StaticMeshComponent.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
component_name: Name of the StaticMeshComponent
|
||||
static_mesh: Path to the static mesh asset (e.g., "/Engine/BasicShapes/Cube.Cube")
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"component_name": component_name,
|
||||
"static_mesh": static_mesh
|
||||
}
|
||||
|
||||
logger.info(f"Setting static mesh properties with params: {params}")
|
||||
response = unreal.send_command("set_static_mesh_properties", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set static mesh properties response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting static mesh properties: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def set_component_property(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
component_name: str,
|
||||
property_name: str,
|
||||
property_value,
|
||||
) -> Dict[str, Any]:
|
||||
"""Set a property on a component in a Blueprint."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"component_name": component_name,
|
||||
"property_name": property_name,
|
||||
"property_value": property_value
|
||||
}
|
||||
|
||||
logger.info(f"Setting component property with params: {params}")
|
||||
response = unreal.send_command("set_component_property", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set component property response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting component property: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def set_physics_properties(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
component_name: str,
|
||||
simulate_physics: bool = True,
|
||||
gravity_enabled: bool = True,
|
||||
mass: float = 1.0,
|
||||
linear_damping: float = 0.01,
|
||||
angular_damping: float = 0.0
|
||||
) -> Dict[str, Any]:
|
||||
"""Set physics properties on a component."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"component_name": component_name,
|
||||
"simulate_physics": simulate_physics,
|
||||
"gravity_enabled": gravity_enabled,
|
||||
"mass": float(mass),
|
||||
"linear_damping": float(linear_damping),
|
||||
"angular_damping": float(angular_damping)
|
||||
}
|
||||
|
||||
logger.info(f"Setting physics properties with params: {params}")
|
||||
response = unreal.send_command("set_physics_properties", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set physics properties response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting physics properties: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def compile_blueprint(
|
||||
ctx: Context,
|
||||
blueprint_name: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Compile a Blueprint."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name
|
||||
}
|
||||
|
||||
logger.info(f"Compiling blueprint: {blueprint_name}")
|
||||
response = unreal.send_command("compile_blueprint", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Compile blueprint response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error compiling blueprint: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def set_blueprint_property(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
property_name: str,
|
||||
property_value
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set a property on a Blueprint class default object.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
property_name: Name of the property to set
|
||||
property_value: Value to set the property to
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"property_name": property_name,
|
||||
"property_value": property_value
|
||||
}
|
||||
|
||||
logger.info(f"Setting blueprint property with params: {params}")
|
||||
response = unreal.send_command("set_blueprint_property", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set blueprint property response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting blueprint property: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
# @mcp.tool() commented out, just use set_component_property instead
|
||||
def set_pawn_properties(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
auto_possess_player: str = "",
|
||||
use_controller_rotation_yaw: bool = None,
|
||||
use_controller_rotation_pitch: bool = None,
|
||||
use_controller_rotation_roll: bool = None,
|
||||
can_be_damaged: bool = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set common Pawn properties on a Blueprint.
|
||||
This is a utility function that sets multiple pawn-related properties at once.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint (must be a Pawn or Character)
|
||||
auto_possess_player: Auto possess player setting (None, "Disabled", "Player0", "Player1", etc.)
|
||||
use_controller_rotation_yaw: Whether the pawn should use the controller's yaw rotation
|
||||
use_controller_rotation_pitch: Whether the pawn should use the controller's pitch rotation
|
||||
use_controller_rotation_roll: Whether the pawn should use the controller's roll rotation
|
||||
can_be_damaged: Whether the pawn can be damaged
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure with detailed results for each property
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
# Define the properties to set
|
||||
properties = {}
|
||||
if auto_possess_player and auto_possess_player != "":
|
||||
properties["auto_possess_player"] = auto_possess_player
|
||||
|
||||
# Only include boolean properties if they were explicitly set
|
||||
if use_controller_rotation_yaw is not None:
|
||||
properties["bUseControllerRotationYaw"] = use_controller_rotation_yaw
|
||||
if use_controller_rotation_pitch is not None:
|
||||
properties["bUseControllerRotationPitch"] = use_controller_rotation_pitch
|
||||
if use_controller_rotation_roll is not None:
|
||||
properties["bUseControllerRotationRoll"] = use_controller_rotation_roll
|
||||
if can_be_damaged is not None:
|
||||
properties["bCanBeDamaged"] = can_be_damaged
|
||||
|
||||
if not properties:
|
||||
logger.warning("No properties specified to set")
|
||||
return {"success": True, "message": "No properties specified to set", "results": {}}
|
||||
|
||||
# Set each property using the generic set_blueprint_property function
|
||||
results = {}
|
||||
overall_success = True
|
||||
|
||||
for prop_name, prop_value in properties.items():
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"property_name": prop_name,
|
||||
"property_value": prop_value
|
||||
}
|
||||
|
||||
logger.info(f"Setting pawn property {prop_name} to {prop_value}")
|
||||
response = unreal.send_command("set_blueprint_property", params)
|
||||
|
||||
if not response:
|
||||
logger.error(f"No response from Unreal Engine for property {prop_name}")
|
||||
results[prop_name] = {"success": False, "message": "No response from Unreal Engine"}
|
||||
overall_success = False
|
||||
continue
|
||||
|
||||
results[prop_name] = response
|
||||
if not response.get("success", False):
|
||||
overall_success = False
|
||||
|
||||
return {
|
||||
"success": overall_success,
|
||||
"message": "Pawn properties set" if overall_success else "Some pawn properties failed to set",
|
||||
"results": results
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting pawn properties: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
logger.info("Blueprint tools registered successfully")
|
||||
430
Plugins/UnrealAgentLink/Resources/tools/blueprint/node_tools.py
Normal file
430
Plugins/UnrealAgentLink/Resources/tools/blueprint/node_tools.py
Normal file
@@ -0,0 +1,430 @@
|
||||
"""
|
||||
Blueprint Node Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for manipulating Blueprint graph nodes and connections.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_blueprint_node_tools(mcp: FastMCP):
|
||||
"""Register Blueprint node manipulation tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_event_node(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
event_name: str,
|
||||
node_position = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add an event node to a Blueprint's event graph.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
event_name: Name of the event. Use 'Receive' prefix for standard events:
|
||||
- 'ReceiveBeginPlay' for Begin Play
|
||||
- 'ReceiveTick' for Tick
|
||||
- etc.
|
||||
node_position: Optional [X, Y] position in the graph
|
||||
|
||||
Returns:
|
||||
Response containing the node ID and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
# Handle default value within the method body
|
||||
if node_position is None:
|
||||
node_position = [0, 0]
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"event_name": event_name,
|
||||
"node_position": node_position
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding event node '{event_name}' to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_event_node", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Event node creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding event node: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_input_action_node(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
action_name: str,
|
||||
node_position = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add an input action event node to a Blueprint's event graph.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
action_name: Name of the input action to respond to
|
||||
node_position: Optional [X, Y] position in the graph
|
||||
|
||||
Returns:
|
||||
Response containing the node ID and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
# Handle default value within the method body
|
||||
if node_position is None:
|
||||
node_position = [0, 0]
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"action_name": action_name,
|
||||
"node_position": node_position
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding input action node for '{action_name}' to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_input_action_node", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Input action node creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding input action node: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_function_node(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
target: str,
|
||||
function_name: str,
|
||||
params = None,
|
||||
node_position = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a function call node to a Blueprint's event graph.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
target: Target object for the function (component name or self)
|
||||
function_name: Name of the function to call
|
||||
params: Optional parameters to set on the function node
|
||||
node_position: Optional [X, Y] position in the graph
|
||||
|
||||
Returns:
|
||||
Response containing the node ID and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
# Handle default values within the method body
|
||||
if params is None:
|
||||
params = {}
|
||||
if node_position is None:
|
||||
node_position = [0, 0]
|
||||
|
||||
command_params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"target": target,
|
||||
"function_name": function_name,
|
||||
"params": params,
|
||||
"node_position": node_position
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding function node '{function_name}' to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_function_node", command_params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Function node creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding function node: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def connect_blueprint_nodes(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
source_node_id: str,
|
||||
source_pin: str,
|
||||
target_node_id: str,
|
||||
target_pin: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Connect two nodes in a Blueprint's event graph.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
source_node_id: ID of the source node
|
||||
source_pin: Name of the output pin on the source node
|
||||
target_node_id: ID of the target node
|
||||
target_pin: Name of the input pin on the target node
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"source_node_id": source_node_id,
|
||||
"source_pin": source_pin,
|
||||
"target_node_id": target_node_id,
|
||||
"target_pin": target_pin
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Connecting nodes in blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("connect_blueprint_nodes", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Node connection response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error connecting nodes: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_variable(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
variable_name: str,
|
||||
variable_type: str,
|
||||
is_exposed: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a variable to a Blueprint.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
variable_name: Name of the variable
|
||||
variable_type: Type of the variable (Boolean, Integer, Float, Vector, etc.)
|
||||
is_exposed: Whether to expose the variable to the editor
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"variable_name": variable_name,
|
||||
"variable_type": variable_type,
|
||||
"is_exposed": is_exposed
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding variable '{variable_name}' to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_variable", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Variable creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding variable: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_get_self_component_reference(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
component_name: str,
|
||||
node_position = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a node that gets a reference to a component owned by the current Blueprint.
|
||||
This creates a node similar to what you get when dragging a component from the Components panel.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
component_name: Name of the component to get a reference to
|
||||
node_position: Optional [X, Y] position in the graph
|
||||
|
||||
Returns:
|
||||
Response containing the node ID and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
# Handle None case explicitly in the function
|
||||
if node_position is None:
|
||||
node_position = [0, 0]
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"component_name": component_name,
|
||||
"node_position": node_position
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding self component reference node for '{component_name}' to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_get_self_component_reference", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Self component reference node creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding self component reference node: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_blueprint_self_reference(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
node_position = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a 'Get Self' node to a Blueprint's event graph that returns a reference to this actor.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
node_position: Optional [X, Y] position in the graph
|
||||
|
||||
Returns:
|
||||
Response containing the node ID and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
if node_position is None:
|
||||
node_position = [0, 0]
|
||||
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"node_position": node_position
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Adding self reference node to blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("add_blueprint_self_reference", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Self reference node creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding self reference node: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def find_blueprint_nodes(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
node_type = None,
|
||||
event_type = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Find nodes in a Blueprint's event graph.
|
||||
|
||||
Args:
|
||||
blueprint_name: Name of the target Blueprint
|
||||
node_type: Optional type of node to find (Event, Function, Variable, etc.)
|
||||
event_type: Optional specific event type to find (BeginPlay, Tick, etc.)
|
||||
|
||||
Returns:
|
||||
Response containing array of found node IDs and success status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"node_type": node_type,
|
||||
"event_type": event_type
|
||||
}
|
||||
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
logger.info(f"Finding nodes in blueprint '{blueprint_name}'")
|
||||
response = unreal.send_command("find_blueprint_nodes", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Node find response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error finding nodes: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
logger.info("Blueprint node tools registered successfully")
|
||||
@@ -0,0 +1 @@
|
||||
# Editor tools package
|
||||
@@ -0,0 +1 @@
|
||||
# Asset tools package
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Asset registry tools for Unreal Engine via MCP."""
|
||||
|
||||
from typing import Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def register_tools(mcp, connection=None):
|
||||
"""Register asset registry tools with the MCP server."""
|
||||
|
||||
# Import get_unreal_connection from parent module
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
@mcp.tool()
|
||||
async def get_asset_references(asset_path: str) -> Dict:
|
||||
"""Get all assets that reference the specified asset.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset
|
||||
|
||||
Returns:
|
||||
Dictionary containing list of referencing assets
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("get_asset_references", {
|
||||
"asset_path": asset_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def get_asset_dependencies(asset_path: str) -> Dict:
|
||||
"""Get all assets that the specified asset depends on.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset
|
||||
|
||||
Returns:
|
||||
Dictionary containing list of dependency assets
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("get_asset_dependencies", {
|
||||
"asset_path": asset_path
|
||||
})
|
||||
|
||||
logger.info("Asset registry tools registered")
|
||||
@@ -0,0 +1,165 @@
|
||||
"""Core asset management tools for Unreal Engine via MCP."""
|
||||
|
||||
from typing import Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def register_tools(mcp, connection=None):
|
||||
"""Register core asset tools with the MCP server."""
|
||||
|
||||
# Import get_unreal_connection from parent module
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
@mcp.tool()
|
||||
async def load_asset(asset_path: str) -> Dict:
|
||||
"""Load an asset into memory.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset to load
|
||||
|
||||
Returns:
|
||||
Dictionary with load status and asset information
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("load_asset", {
|
||||
"asset_path": asset_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def save_asset(asset_path: str, only_if_dirty: bool = True) -> Dict:
|
||||
"""Save an asset to disk.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset to save
|
||||
only_if_dirty: Only save if the asset has unsaved changes
|
||||
|
||||
Returns:
|
||||
Dictionary with save status
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("save_asset", {
|
||||
"asset_path": asset_path,
|
||||
"only_if_dirty": only_if_dirty
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def duplicate_asset(source_path: str, destination_path: str) -> Dict:
|
||||
"""Duplicate an existing asset.
|
||||
|
||||
Args:
|
||||
source_path: Path to the asset to duplicate
|
||||
destination_path: Path for the new duplicated asset
|
||||
|
||||
Returns:
|
||||
Dictionary with duplication status
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("duplicate_asset", {
|
||||
"source_path": source_path,
|
||||
"destination_path": destination_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def delete_asset(asset_path: str) -> Dict:
|
||||
"""Delete an asset from the project.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset to delete
|
||||
|
||||
Returns:
|
||||
Dictionary with deletion status
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("delete_asset", {
|
||||
"asset_path": asset_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def rename_asset(source_path: str, new_name: str) -> Dict:
|
||||
"""Rename an existing asset.
|
||||
|
||||
Args:
|
||||
source_path: Current path to the asset
|
||||
new_name: New name for the asset (without path)
|
||||
|
||||
Returns:
|
||||
Dictionary with rename status and new path
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("rename_asset", {
|
||||
"source_path": source_path,
|
||||
"new_name": new_name
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def move_asset(source_path: str, destination_path: str) -> Dict:
|
||||
"""Move an asset to a different location.
|
||||
|
||||
Args:
|
||||
source_path: Current path to the asset
|
||||
destination_path: New full path for the asset
|
||||
|
||||
Returns:
|
||||
Dictionary with move status
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("move_asset", {
|
||||
"source_path": source_path,
|
||||
"destination_path": destination_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def import_asset(file_path: str, destination_path: str) -> Dict:
|
||||
"""Import an external file as an asset.
|
||||
|
||||
Args:
|
||||
file_path: Path to the file on disk to import
|
||||
destination_path: Content browser path where to import the asset
|
||||
|
||||
Returns:
|
||||
Dictionary with import status and imported asset information
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("import_asset", {
|
||||
"file_path": file_path,
|
||||
"destination_path": destination_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def export_asset(asset_path: str, export_path: str) -> Dict:
|
||||
"""Export an asset to an external file.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset to export
|
||||
export_path: File path where to export the asset
|
||||
|
||||
Returns:
|
||||
Dictionary with export status
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("export_asset", {
|
||||
"asset_path": asset_path,
|
||||
"export_path": export_path
|
||||
})
|
||||
|
||||
logger.info("Core asset tools registered")
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Content browser tools for Unreal Engine via MCP."""
|
||||
|
||||
from typing import Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def register_tools(mcp, connection=None):
|
||||
"""Register content browser tools with the MCP server."""
|
||||
|
||||
# Import get_unreal_connection from parent module
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
@mcp.tool()
|
||||
async def list_assets(path: str = "/Game", type_filter: str = "", recursive: bool = False) -> Dict:
|
||||
"""List all assets in a given path.
|
||||
|
||||
Args:
|
||||
path: The content browser path to list (e.g., "/Game/MyFolder")
|
||||
type_filter: Optional asset type filter (e.g., "StaticMesh", "Material")
|
||||
recursive: Whether to search recursively in subdirectories
|
||||
|
||||
Returns:
|
||||
Dictionary containing list of assets and count
|
||||
"""
|
||||
params = {
|
||||
"path": path,
|
||||
"recursive": recursive
|
||||
}
|
||||
if type_filter:
|
||||
params["type_filter"] = type_filter
|
||||
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("list_assets", params)
|
||||
|
||||
@mcp.tool()
|
||||
async def get_asset_metadata(asset_path: str) -> Dict:
|
||||
"""Get detailed metadata for a specific asset.
|
||||
|
||||
Args:
|
||||
asset_path: Full path to the asset (e.g., "/Game/MyFolder/MyAsset")
|
||||
|
||||
Returns:
|
||||
Dictionary containing asset metadata including tags and properties
|
||||
"""
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("get_asset_metadata", {
|
||||
"asset_path": asset_path
|
||||
})
|
||||
|
||||
@mcp.tool()
|
||||
async def search_assets(search_text: str, type_filter: str = "") -> Dict:
|
||||
"""Search for assets by name or path.
|
||||
|
||||
Args:
|
||||
search_text: Text to search for in asset names and paths
|
||||
type_filter: Optional asset type filter
|
||||
|
||||
Returns:
|
||||
Dictionary containing matching assets
|
||||
"""
|
||||
params = {"search_text": search_text}
|
||||
if type_filter:
|
||||
params["type_filter"] = type_filter
|
||||
|
||||
conn = get_unreal_connection()
|
||||
if not conn:
|
||||
return {"status": "error", "error": "Failed to connect to Unreal Engine"}
|
||||
return conn.send_command("search_assets", params)
|
||||
|
||||
logger.info("Content browser tools registered")
|
||||
@@ -0,0 +1 @@
|
||||
# Landscape tools package
|
||||
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Landscape Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for managing landscapes in Unreal Engine through the Landscape Editor module.
|
||||
Following Epic's official structure: Editor/Landscape module patterns.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_landscape_tools(mcp: FastMCP):
|
||||
"""Register landscape tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def create_landscape(ctx: Context,
|
||||
size_x: int = 127,
|
||||
size_y: int = 127,
|
||||
sections_per_component: int = 1,
|
||||
quads_per_section: int = 63,
|
||||
location_x: float = 0.0,
|
||||
location_y: float = 0.0,
|
||||
location_z: float = 0.0) -> Dict[str, Any]:
|
||||
"""Create a landscape in the current level.
|
||||
|
||||
Args:
|
||||
size_x: Landscape size in X direction (default: 127)
|
||||
size_y: Landscape size in Y direction (default: 127)
|
||||
sections_per_component: Number of sections per component (default: 1)
|
||||
quads_per_section: Number of quads per section (default: 63)
|
||||
location_x: X location of the landscape (default: 0.0)
|
||||
location_y: Y location of the landscape (default: 0.0)
|
||||
location_z: Z location of the landscape (default: 0.0)
|
||||
|
||||
Returns:
|
||||
Dictionary containing the landscape creation result
|
||||
|
||||
Example:
|
||||
create_landscape(size_x=255, size_y=255, location_z=100.0)
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("create_landscape", {
|
||||
"size_x": size_x,
|
||||
"size_y": size_y,
|
||||
"sections_per_component": sections_per_component,
|
||||
"quads_per_section": quads_per_section,
|
||||
"location": {
|
||||
"x": location_x,
|
||||
"y": location_y,
|
||||
"z": location_z
|
||||
}
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Created landscape with size {size_x}x{size_y}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating landscape: {e}")
|
||||
return {"error": f"Error creating landscape: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def modify_landscape(ctx: Context, modification_type: str = "sculpt") -> Dict[str, Any]:
|
||||
"""Modify the landscape heightmap.
|
||||
|
||||
Args:
|
||||
modification_type: Type of modification to perform (default: "sculpt")
|
||||
|
||||
Returns:
|
||||
Dictionary containing the landscape modification result
|
||||
|
||||
Example:
|
||||
modify_landscape("sculpt")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("modify_landscape", {
|
||||
"modification_type": modification_type
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Modified landscape with type: {modification_type}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error modifying landscape: {e}")
|
||||
return {"error": f"Error modifying landscape: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def paint_landscape_layer(ctx: Context, layer_name: str) -> Dict[str, Any]:
|
||||
"""Paint a landscape material layer.
|
||||
|
||||
Args:
|
||||
layer_name: Name of the landscape layer to paint
|
||||
|
||||
Returns:
|
||||
Dictionary containing the landscape painting result
|
||||
|
||||
Example:
|
||||
paint_landscape_layer("Grass")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("paint_landscape_layer", {
|
||||
"layer_name": layer_name
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Painted landscape layer: {layer_name}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error painting landscape layer: {e}")
|
||||
return {"error": f"Error painting landscape layer: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def get_landscape_info(ctx: Context) -> List[Dict[str, Any]]:
|
||||
"""Get information about all landscapes in the current level.
|
||||
|
||||
Returns:
|
||||
List of dictionaries containing landscape information
|
||||
|
||||
Example:
|
||||
get_landscape_info()
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return []
|
||||
|
||||
response = unreal.send_command("get_landscape_info", {})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return []
|
||||
|
||||
# Check response format
|
||||
if "result" in response and "landscapes" in response["result"]:
|
||||
landscapes = response["result"]["landscapes"]
|
||||
logger.info(f"Found {len(landscapes)} landscapes in level")
|
||||
return landscapes
|
||||
elif "landscapes" in response:
|
||||
landscapes = response["landscapes"]
|
||||
logger.info(f"Found {len(landscapes)} landscapes in level")
|
||||
return landscapes
|
||||
|
||||
logger.warning(f"Unexpected response format: {response}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting landscape info: {e}")
|
||||
return []
|
||||
@@ -0,0 +1 @@
|
||||
# Level Editor tools package
|
||||
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
Level Editor Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for managing levels in Unreal Engine through the Level Editor module.
|
||||
Following Epic's official structure: Editor/LevelEditor module patterns.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_level_tools(mcp: FastMCP):
|
||||
"""Register level editor tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def create_level(ctx: Context, level_name: str) -> Dict[str, Any]:
|
||||
"""Create a new level.
|
||||
|
||||
Args:
|
||||
level_name: Name of the new level to create
|
||||
|
||||
Returns:
|
||||
Dictionary containing the created level information
|
||||
|
||||
Example:
|
||||
create_level("MyNewLevel")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("create_level", {"level_name": level_name})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Created level: {level_name}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating level: {e}")
|
||||
return {"error": f"Error creating level: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def save_level(ctx: Context) -> Dict[str, Any]:
|
||||
"""Save the current level.
|
||||
|
||||
Returns:
|
||||
Dictionary containing the save operation result
|
||||
|
||||
Example:
|
||||
save_level()
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("save_level", {})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info("Level saved successfully")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving level: {e}")
|
||||
return {"error": f"Error saving level: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def load_level(ctx: Context, level_path: str) -> Dict[str, Any]:
|
||||
"""Load a level.
|
||||
|
||||
Args:
|
||||
level_path: Path to the level to load (e.g., "/Game/Maps/MyLevel")
|
||||
|
||||
Returns:
|
||||
Dictionary containing the load operation result
|
||||
|
||||
Example:
|
||||
load_level("/Game/Maps/MyLevel")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("load_level", {"level_path": level_path})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Loaded level: {level_path}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading level: {e}")
|
||||
return {"error": f"Error loading level: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def set_level_visibility(ctx: Context, level_name: str, visible: bool = True) -> Dict[str, Any]:
|
||||
"""Set the visibility of a level.
|
||||
|
||||
Args:
|
||||
level_name: Name of the level to set visibility for
|
||||
visible: Whether the level should be visible (default: True)
|
||||
|
||||
Returns:
|
||||
Dictionary containing the visibility operation result
|
||||
|
||||
Example:
|
||||
set_level_visibility("MyLevel", True)
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("set_level_visibility", {
|
||||
"level_name": level_name,
|
||||
"visible": visible
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set level {level_name} visibility to {visible}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting level visibility: {e}")
|
||||
return {"error": f"Error setting level visibility: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def create_streaming_level(ctx: Context, level_path: str) -> Dict[str, Any]:
|
||||
"""Create a streaming level.
|
||||
|
||||
Args:
|
||||
level_path: Path to the level to add as streaming level
|
||||
|
||||
Returns:
|
||||
Dictionary containing the streaming level creation result
|
||||
|
||||
Example:
|
||||
create_streaming_level("/Game/Maps/StreamingLevel")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("create_streaming_level", {"level_path": level_path})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Created streaming level: {level_path}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating streaming level: {e}")
|
||||
return {"error": f"Error creating streaming level: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def load_streaming_level(ctx: Context, level_name: str) -> Dict[str, Any]:
|
||||
"""Load a streaming level.
|
||||
|
||||
Args:
|
||||
level_name: Name of the streaming level to load
|
||||
|
||||
Returns:
|
||||
Dictionary containing the streaming level load result
|
||||
|
||||
Example:
|
||||
load_streaming_level("StreamingLevel")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("load_streaming_level", {"level_name": level_name})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Loaded streaming level: {level_name}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading streaming level: {e}")
|
||||
return {"error": f"Error loading streaming level: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def unload_streaming_level(ctx: Context, level_name: str) -> Dict[str, Any]:
|
||||
"""Unload a streaming level.
|
||||
|
||||
Args:
|
||||
level_name: Name of the streaming level to unload
|
||||
|
||||
Returns:
|
||||
Dictionary containing the streaming level unload result
|
||||
|
||||
Example:
|
||||
unload_streaming_level("StreamingLevel")
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("unload_streaming_level", {"level_name": level_name})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Unloaded streaming level: {level_name}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error unloading streaming level: {e}")
|
||||
return {"error": f"Error unloading streaming level: {str(e)}"}
|
||||
@@ -0,0 +1,424 @@
|
||||
"""
|
||||
Editor Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for controlling the Unreal Editor viewport and other editor functionality.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_editor_tools(mcp: FastMCP):
|
||||
"""Register editor tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def get_actors_in_level(ctx: Context) -> List[Dict[str, Any]]:
|
||||
"""Get a list of all actors in the current level."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return []
|
||||
|
||||
response = unreal.send_command("get_actors_in_level", {})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return []
|
||||
|
||||
# Log the complete response for debugging
|
||||
logger.info(f"Complete response from Unreal: {response}")
|
||||
|
||||
# Check response format
|
||||
if "result" in response and "actors" in response["result"]:
|
||||
actors = response["result"]["actors"]
|
||||
logger.info(f"Found {len(actors)} actors in level")
|
||||
return actors
|
||||
elif "actors" in response:
|
||||
actors = response["actors"]
|
||||
logger.info(f"Found {len(actors)} actors in level")
|
||||
return actors
|
||||
|
||||
logger.warning(f"Unexpected response format: {response}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting actors: {e}")
|
||||
return []
|
||||
|
||||
@mcp.tool()
|
||||
def find_actors_by_name(ctx: Context, pattern: str) -> List[str]:
|
||||
"""Find actors by name pattern."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return []
|
||||
|
||||
response = unreal.send_command("find_actors_by_name", {
|
||||
"pattern": pattern
|
||||
})
|
||||
|
||||
if not response:
|
||||
return []
|
||||
|
||||
return response.get("actors", [])
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error finding actors: {e}")
|
||||
return []
|
||||
|
||||
@mcp.tool()
|
||||
def spawn_actor(
|
||||
ctx: Context,
|
||||
name: str,
|
||||
type: str,
|
||||
location: List[float] = [0.0, 0.0, 0.0],
|
||||
rotation: List[float] = [0.0, 0.0, 0.0],
|
||||
static_mesh: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a new actor in the current level.
|
||||
|
||||
Args:
|
||||
ctx: The MCP context
|
||||
name: The name to give the new actor (must be unique)
|
||||
type: The type of actor to create (e.g. StaticMeshActor, PointLight)
|
||||
location: The [x, y, z] world location to spawn at
|
||||
rotation: The [pitch, yaw, roll] rotation in degrees
|
||||
static_mesh: Optional path to static mesh for StaticMeshActor (e.g. /Engine/BasicShapes/Cube.Cube)
|
||||
|
||||
Returns:
|
||||
Dict containing the created actor's properties
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
# Ensure all parameters are properly formatted
|
||||
params = {
|
||||
"name": name,
|
||||
"type": type.upper(), # Make sure type is uppercase
|
||||
"location": location,
|
||||
"rotation": rotation
|
||||
}
|
||||
|
||||
# Add static_mesh parameter if provided
|
||||
if static_mesh:
|
||||
params["static_mesh"] = static_mesh
|
||||
|
||||
# Validate location and rotation formats
|
||||
for param_name in ["location", "rotation"]:
|
||||
param_value = params[param_name]
|
||||
if not isinstance(param_value, list) or len(param_value) != 3:
|
||||
logger.error(f"Invalid {param_name} format: {param_value}. Must be a list of 3 float values.")
|
||||
return {"success": False, "message": f"Invalid {param_name} format. Must be a list of 3 float values."}
|
||||
# Ensure all values are float
|
||||
params[param_name] = [float(val) for val in param_value]
|
||||
|
||||
logger.info(f"Creating actor '{name}' of type '{type}' with params: {params}")
|
||||
response = unreal.send_command("spawn_actor", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
# Log the complete response for debugging
|
||||
logger.info(f"Actor creation response: {response}")
|
||||
|
||||
# Handle error responses correctly
|
||||
if response.get("status") == "error":
|
||||
error_message = response.get("error", "Unknown error")
|
||||
logger.error(f"Error creating actor: {error_message}")
|
||||
return {"success": False, "message": error_message}
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating actor: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def delete_actor(ctx: Context, name: str) -> Dict[str, Any]:
|
||||
"""Delete an actor by name."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("delete_actor", {
|
||||
"name": name
|
||||
})
|
||||
return response or {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting actor: {e}")
|
||||
return {}
|
||||
|
||||
@mcp.tool()
|
||||
def set_actor_transform(
|
||||
ctx: Context,
|
||||
name: str,
|
||||
location: List[float] = None,
|
||||
rotation: List[float] = None,
|
||||
scale: List[float] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Set the transform of an actor."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {"name": name}
|
||||
if location is not None:
|
||||
params["location"] = location
|
||||
if rotation is not None:
|
||||
params["rotation"] = rotation
|
||||
if scale is not None:
|
||||
params["scale"] = scale
|
||||
|
||||
response = unreal.send_command("set_actor_transform", params)
|
||||
return response or {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting transform: {e}")
|
||||
return {}
|
||||
|
||||
@mcp.tool()
|
||||
def get_actor_properties(ctx: Context, name: str) -> Dict[str, Any]:
|
||||
"""Get all properties of an actor."""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("get_actor_properties", {
|
||||
"name": name
|
||||
})
|
||||
return response or {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting properties: {e}")
|
||||
return {}
|
||||
|
||||
@mcp.tool()
|
||||
def set_actor_property(
|
||||
ctx: Context,
|
||||
name: str,
|
||||
property_name: str,
|
||||
property_value,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set a property on an actor.
|
||||
|
||||
Args:
|
||||
name: Name of the actor
|
||||
property_name: Name of the property to set
|
||||
property_value: Value to set the property to
|
||||
|
||||
Returns:
|
||||
Dict containing response from Unreal with operation status
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("set_actor_property", {
|
||||
"name": name,
|
||||
"property_name": property_name,
|
||||
"property_value": property_value
|
||||
})
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set actor property response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting actor property: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
# @mcp.tool() commented out because it's buggy
|
||||
def focus_viewport(
|
||||
ctx: Context,
|
||||
target: str = None,
|
||||
location: List[float] = None,
|
||||
distance: float = 1000.0,
|
||||
orientation: List[float] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Focus the viewport on a specific actor or location.
|
||||
|
||||
Args:
|
||||
target: Name of the actor to focus on (if provided, location is ignored)
|
||||
location: [X, Y, Z] coordinates to focus on (used if target is None)
|
||||
distance: Distance from the target/location
|
||||
orientation: Optional [Pitch, Yaw, Roll] for the viewport camera
|
||||
|
||||
Returns:
|
||||
Response from Unreal Engine
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {}
|
||||
if target:
|
||||
params["target"] = target
|
||||
elif location:
|
||||
params["location"] = location
|
||||
|
||||
if distance:
|
||||
params["distance"] = distance
|
||||
|
||||
if orientation:
|
||||
params["orientation"] = orientation
|
||||
|
||||
response = unreal.send_command("focus_viewport", params)
|
||||
return response or {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error focusing viewport: {e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@mcp.tool()
|
||||
def spawn_blueprint_actor(
|
||||
ctx: Context,
|
||||
blueprint_name: str,
|
||||
actor_name: str,
|
||||
location: List[float] = [0.0, 0.0, 0.0],
|
||||
rotation: List[float] = [0.0, 0.0, 0.0]
|
||||
) -> Dict[str, Any]:
|
||||
"""Spawn an actor from a Blueprint.
|
||||
|
||||
Args:
|
||||
ctx: The MCP context
|
||||
blueprint_name: Name of the Blueprint to spawn from
|
||||
actor_name: Name to give the spawned actor
|
||||
location: The [x, y, z] world location to spawn at
|
||||
rotation: The [pitch, yaw, roll] rotation in degrees
|
||||
|
||||
Returns:
|
||||
Dict containing the spawned actor's properties
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
# Ensure all parameters are properly formatted
|
||||
params = {
|
||||
"blueprint_name": blueprint_name,
|
||||
"actor_name": actor_name,
|
||||
"location": location or [0.0, 0.0, 0.0],
|
||||
"rotation": rotation or [0.0, 0.0, 0.0]
|
||||
}
|
||||
|
||||
# Validate location and rotation formats
|
||||
for param_name in ["location", "rotation"]:
|
||||
param_value = params[param_name]
|
||||
if not isinstance(param_value, list) or len(param_value) != 3:
|
||||
logger.error(f"Invalid {param_name} format: {param_value}. Must be a list of 3 float values.")
|
||||
return {"success": False, "message": f"Invalid {param_name} format. Must be a list of 3 float values."}
|
||||
# Ensure all values are float
|
||||
params[param_name] = [float(val) for val in param_value]
|
||||
|
||||
logger.info(f"Spawning blueprint actor with params: {params}")
|
||||
response = unreal.send_command("spawn_blueprint_actor", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Spawn blueprint actor response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error spawning blueprint actor: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def take_screenshot(
|
||||
ctx: Context,
|
||||
filename: str,
|
||||
show_ui: bool = False,
|
||||
resolution: List[int] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Take a screenshot of the current viewport.
|
||||
|
||||
Args:
|
||||
ctx: The MCP context
|
||||
filename: Name for the screenshot file (without extension)
|
||||
show_ui: Whether to include UI in the screenshot
|
||||
resolution: Optional [width, height] for the screenshot
|
||||
|
||||
Returns:
|
||||
Dict containing screenshot information
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"filepath": filename,
|
||||
"show_ui": show_ui
|
||||
}
|
||||
|
||||
if resolution:
|
||||
if isinstance(resolution, list) and len(resolution) == 2:
|
||||
params["resolution"] = resolution
|
||||
|
||||
logger.info(f"Taking screenshot: {filename}")
|
||||
response = unreal.send_command("take_screenshot", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error taking screenshot: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
logger.info("Editor tools registered successfully")
|
||||
@@ -0,0 +1 @@
|
||||
# Engine tools package
|
||||
@@ -0,0 +1 @@
|
||||
# World tools package
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
World Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for runtime world operations in Unreal Engine.
|
||||
Following Epic's official structure: Engine/World module patterns.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_world_tools(mcp: FastMCP):
|
||||
"""Register world runtime tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def get_current_level_info(ctx: Context) -> Dict[str, Any]:
|
||||
"""Get information about the current level and world.
|
||||
|
||||
Returns:
|
||||
Dictionary containing current world and level information
|
||||
|
||||
Example:
|
||||
get_current_level_info()
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
response = unreal.send_command("get_current_level_info", {})
|
||||
|
||||
if not response:
|
||||
logger.warning("No response from Unreal Engine")
|
||||
return {"error": "No response from Unreal Engine"}
|
||||
|
||||
logger.info("Retrieved current level info")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting level info: {e}")
|
||||
return {"error": f"Error getting level info: {str(e)}"}
|
||||
|
||||
@mcp.tool()
|
||||
def query_assets(
|
||||
ctx: Context,
|
||||
scope: Dict[str, Any] = None,
|
||||
conditions: Dict[str, Any] = None,
|
||||
sort_by: str = None,
|
||||
limit: int = 20
|
||||
) -> Dict[str, Any]:
|
||||
"""Query assets in the current level/selection with performance filters.
|
||||
|
||||
Args:
|
||||
scope: {"type": "Level"|"Selection"|"ContentBrowser", "path": "..."}
|
||||
conditions: filter object (min_triangles, nanite_enabled, missing_collision, etc.)
|
||||
sort_by: "TriangleCount" | "TextureMemory" | "DiskSize"
|
||||
limit: max results
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.warning("Failed to connect to Unreal Engine")
|
||||
return {"error": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"scope": scope or {"type": "Level"},
|
||||
"conditions": conditions or {},
|
||||
"sort_by": sort_by or "TriangleCount",
|
||||
"limit": limit,
|
||||
}
|
||||
|
||||
response = unreal.send_command("level.query_assets", params)
|
||||
return response or {"error": "No response from Unreal Engine"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error querying assets: {e}")
|
||||
return {"error": f"Error querying assets: {str(e)}"}
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Project Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for managing project-wide settings and configuration.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_project_tools(mcp: FastMCP):
|
||||
"""Register project tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def create_input_mapping(
|
||||
ctx: Context,
|
||||
action_name: str,
|
||||
key: str,
|
||||
input_type: str = "Action"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create an input mapping for the project.
|
||||
|
||||
Args:
|
||||
action_name: Name of the input action
|
||||
key: Key to bind (SpaceBar, LeftMouseButton, etc.)
|
||||
input_type: Type of input mapping (Action or Axis)
|
||||
|
||||
Returns:
|
||||
Response indicating success or failure
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"action_name": action_name,
|
||||
"key": key,
|
||||
"input_type": input_type
|
||||
}
|
||||
|
||||
logger.info(f"Creating input mapping '{action_name}' with key '{key}'")
|
||||
response = unreal.send_command("create_input_mapping", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Input mapping creation response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating input mapping: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
logger.info("Project tools registered successfully")
|
||||
333
Plugins/UnrealAgentLink/Resources/tools/ui/umg_tools.py
Normal file
333
Plugins/UnrealAgentLink/Resources/tools/ui/umg_tools.py
Normal file
@@ -0,0 +1,333 @@
|
||||
"""
|
||||
UMG Tools for Unreal MCP.
|
||||
|
||||
This module provides tools for creating and manipulating UMG Widget Blueprints in Unreal Engine.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
# Get logger
|
||||
logger = logging.getLogger("UnrealMCP")
|
||||
|
||||
def register_umg_tools(mcp: FastMCP):
|
||||
"""Register UMG tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def create_umg_widget_blueprint(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
parent_class: str = "UserWidget",
|
||||
path: str = "/Game/UI"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new UMG Widget Blueprint.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the widget blueprint to create
|
||||
parent_class: Parent class for the widget (default: UserWidget)
|
||||
path: Content browser path where the widget should be created
|
||||
|
||||
Returns:
|
||||
Dict containing success status and widget path
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"parent_class": parent_class,
|
||||
"path": path
|
||||
}
|
||||
|
||||
logger.info(f"Creating UMG Widget Blueprint with params: {params}")
|
||||
response = unreal.send_command("create_umg_widget_blueprint", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Create UMG Widget Blueprint response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating UMG Widget Blueprint: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_text_block_to_widget(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
text_block_name: str,
|
||||
text: str = "",
|
||||
position: List[float] = [0.0, 0.0],
|
||||
size: List[float] = [200.0, 50.0],
|
||||
font_size: int = 12,
|
||||
color: List[float] = [1.0, 1.0, 1.0, 1.0]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a Text Block widget to a UMG Widget Blueprint.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the target Widget Blueprint
|
||||
text_block_name: Name to give the new Text Block
|
||||
text: Initial text content
|
||||
position: [X, Y] position in the canvas panel
|
||||
size: [Width, Height] of the text block
|
||||
font_size: Font size in points
|
||||
color: [R, G, B, A] color values (0.0 to 1.0)
|
||||
|
||||
Returns:
|
||||
Dict containing success status and text block properties
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"text_block_name": text_block_name,
|
||||
"text": text,
|
||||
"position": position,
|
||||
"size": size,
|
||||
"font_size": font_size,
|
||||
"color": color
|
||||
}
|
||||
|
||||
logger.info(f"Adding Text Block to widget with params: {params}")
|
||||
response = unreal.send_command("add_text_block_to_widget", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Add Text Block response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding Text Block to widget: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_button_to_widget(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
button_name: str,
|
||||
text: str = "",
|
||||
position: List[float] = [0.0, 0.0],
|
||||
size: List[float] = [200.0, 50.0],
|
||||
font_size: int = 12,
|
||||
color: List[float] = [1.0, 1.0, 1.0, 1.0],
|
||||
background_color: List[float] = [0.1, 0.1, 0.1, 1.0]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a Button widget to a UMG Widget Blueprint.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the target Widget Blueprint
|
||||
button_name: Name to give the new Button
|
||||
text: Text to display on the button
|
||||
position: [X, Y] position in the canvas panel
|
||||
size: [Width, Height] of the button
|
||||
font_size: Font size for button text
|
||||
color: [R, G, B, A] text color values (0.0 to 1.0)
|
||||
background_color: [R, G, B, A] button background color values (0.0 to 1.0)
|
||||
|
||||
Returns:
|
||||
Dict containing success status and button properties
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"button_name": button_name,
|
||||
"text": text,
|
||||
"position": position,
|
||||
"size": size,
|
||||
"font_size": font_size,
|
||||
"color": color,
|
||||
"background_color": background_color
|
||||
}
|
||||
|
||||
logger.info(f"Adding Button to widget with params: {params}")
|
||||
response = unreal.send_command("add_button_to_widget", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Add Button response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding Button to widget: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def bind_widget_event(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
widget_component_name: str,
|
||||
event_name: str,
|
||||
function_name: str = ""
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Bind an event on a widget component to a function.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the target Widget Blueprint
|
||||
widget_component_name: Name of the widget component (button, etc.)
|
||||
event_name: Name of the event to bind (OnClicked, etc.)
|
||||
function_name: Name of the function to create/bind to (defaults to f"{widget_component_name}_{event_name}")
|
||||
|
||||
Returns:
|
||||
Dict containing success status and binding information
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
# If no function name provided, create one from component and event names
|
||||
if not function_name:
|
||||
function_name = f"{widget_component_name}_{event_name}"
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"widget_component_name": widget_component_name,
|
||||
"event_name": event_name,
|
||||
"function_name": function_name
|
||||
}
|
||||
|
||||
logger.info(f"Binding widget event with params: {params}")
|
||||
response = unreal.send_command("bind_widget_event", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Bind widget event response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error binding widget event: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def add_widget_to_viewport(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
z_order: int = 0
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a Widget Blueprint instance to the viewport.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the Widget Blueprint to add
|
||||
z_order: Z-order for the widget (higher numbers appear on top)
|
||||
|
||||
Returns:
|
||||
Dict containing success status and widget instance information
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"z_order": z_order
|
||||
}
|
||||
|
||||
logger.info(f"Adding widget to viewport with params: {params}")
|
||||
response = unreal.send_command("add_widget_to_viewport", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Add widget to viewport response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error adding widget to viewport: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
@mcp.tool()
|
||||
def set_text_block_binding(
|
||||
ctx: Context,
|
||||
widget_name: str,
|
||||
text_block_name: str,
|
||||
binding_property: str,
|
||||
binding_type: str = "Text"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set up a property binding for a Text Block widget.
|
||||
|
||||
Args:
|
||||
widget_name: Name of the target Widget Blueprint
|
||||
text_block_name: Name of the Text Block to bind
|
||||
binding_property: Name of the property to bind to
|
||||
binding_type: Type of binding (Text, Visibility, etc.)
|
||||
|
||||
Returns:
|
||||
Dict containing success status and binding information
|
||||
"""
|
||||
from unreal_mcp_server import get_unreal_connection
|
||||
|
||||
try:
|
||||
unreal = get_unreal_connection()
|
||||
if not unreal:
|
||||
logger.error("Failed to connect to Unreal Engine")
|
||||
return {"success": False, "message": "Failed to connect to Unreal Engine"}
|
||||
|
||||
params = {
|
||||
"widget_name": widget_name,
|
||||
"text_block_name": text_block_name,
|
||||
"binding_property": binding_property,
|
||||
"binding_type": binding_type
|
||||
}
|
||||
|
||||
logger.info(f"Setting text block binding with params: {params}")
|
||||
response = unreal.send_command("set_text_block_binding", params)
|
||||
|
||||
if not response:
|
||||
logger.error("No response from Unreal Engine")
|
||||
return {"success": False, "message": "No response from Unreal Engine"}
|
||||
|
||||
logger.info(f"Set text block binding response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error setting text block binding: {e}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "message": error_msg}
|
||||
|
||||
logger.info("UMG tools registered successfully")
|
||||
Reference in New Issue
Block a user