"""
Maya MCP Server - FastMCP implementation for Smithery
Entry point for the Maya Model Context Protocol server using FastMCP
"""
try:
from fastmcp import FastMCP
except ImportError:
try:
from mcp.server.fastmcp import FastMCP
except ImportError:
from mcp import FastMCP
from pydantic import BaseModel, Field
from typing import List, Optional
import logging
import sys
import os
# Add parent directory to path for imports
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
try:
from maya_bridge import MayaBridge
from models import ObjectType
except ImportError as e:
logging.error(f"Import error: {e}")
# Fallback for basic functionality
class MayaBridge:
def __init__(self, **kwargs):
pass
def execute_command(self, cmd):
return type('Response', (), {'success': False, 'error': 'Maya not available'})()
class ObjectType:
pass
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create FastMCP server instance
mcp = FastMCP("Maya MCP Server")
# Global Maya bridge instance
maya_bridge = None
def get_maya_bridge() -> MayaBridge:
"""Get or create Maya bridge"""
global maya_bridge
if maya_bridge is None:
maya_bridge = MayaBridge(
host="localhost",
port=7022,
timeout=30,
debug=False
)
return maya_bridge
@mcp.tool()
def maya_create(
object_type: str,
name: Optional[str] = None
) -> str:
"""Create Maya objects (cubes, spheres, etc.)
Args:
object_type: Type of object to create (polyCube, polySphere, etc.)
name: Optional name for the object
"""
try:
bridge = get_maya_bridge()
# Build Maya command
if name:
command = f'import maya.cmds as cmds; result = cmds.{object_type}(name="{name}"); print(f"Created: {{result}}")'
else:
command = f'import maya.cmds as cmds; result = cmds.{object_type}(); print(f"Created: {{result}}")'
response = bridge.execute_command(command)
if response.success:
return f"Successfully created {object_type}" + (f" named '{name}'" if name else "")
else:
return f"Failed to create {object_type}: {response.error}"
except Exception as e:
logger.error(f"Error in maya_create: {e}")
return f"Error creating object: {str(e)}"
@mcp.tool()
def maya_select(objects: List[str]) -> str:
"""Select objects by name
Args:
objects: List of object names to select
"""
try:
bridge = get_maya_bridge()
if not objects:
return "Error: No objects specified for selection"
# Build selection command
objects_str = '", "'.join(objects)
command = f'import maya.cmds as cmds; cmds.select(["{objects_str}"]); print(f"Selected: {objects}")'
response = bridge.execute_command(command)
if response.success:
return f"Successfully selected {len(objects)} object(s): {', '.join(objects)}"
else:
return f"Failed to select objects: {response.error}"
except Exception as e:
logger.error(f"Error in maya_select: {e}")
return f"Error selecting objects: {str(e)}"
@mcp.tool()
def maya_execute(command: str) -> str:
"""Execute Python commands in Maya
Args:
command: Python command to execute in Maya
"""
try:
bridge = get_maya_bridge()
if not command.strip():
return "Error: Empty command provided"
response = bridge.execute_command(command)
if response.success:
return f"Command executed successfully. Output: {response.result}"
else:
return f"Command failed: {response.error}"
except Exception as e:
logger.error(f"Error in maya_execute: {e}")
return f"Error executing command: {str(e)}"
@mcp.tool()
def maya_get_selection() -> str:
"""Get currently selected objects"""
try:
bridge = get_maya_bridge()
command = 'import maya.cmds as cmds; selected = cmds.ls(selection=True); print(f"Selected objects: {selected}")'
response = bridge.execute_command(command)
if response.success:
return f"Current selection: {response.result}"
else:
return f"Failed to get selection: {response.error}"
except Exception as e:
logger.error(f"Error in maya_get_selection: {e}")
return f"Error getting selection: {str(e)}"
@mcp.tool()
def maya_get_scene_info(
include_transforms: bool = True,
include_attributes: bool = False
) -> str:
"""Get detailed scene information
Args:
include_transforms: Include transform information
include_attributes: Include attribute information
"""
try:
bridge = get_maya_bridge()
command = f'''
import maya.cmds as cmds
import json
scene_info = {{
"scene_name": cmds.file(query=True, sceneName=True) or "untitled",
"objects": cmds.ls(transforms=True) if {include_transforms} else [],
"total_objects": len(cmds.ls()),
"selected_objects": cmds.ls(selection=True)
}}
if {include_attributes}:
scene_info["attributes"] = {{}}
for obj in scene_info.get("objects", [])[:5]: # Limit to first 5 objects
try:
scene_info["attributes"][obj] = cmds.listAttr(obj, keyable=True) or []
except:
pass
print(json.dumps(scene_info, indent=2))
'''
response = bridge.execute_command(command)
if response.success:
return f"Scene information: {response.result}"
else:
return f"Failed to get scene info: {response.error}"
except Exception as e:
logger.error(f"Error in maya_get_scene_info: {e}")
return f"Error getting scene info: {str(e)}"
@mcp.tool()
def maya_transform(
objects: List[str],
translate: Optional[List[float]] = None,
rotate: Optional[List[float]] = None,
scale: Optional[List[float]] = None
) -> str:
"""Transform objects (move, rotate, scale)
Args:
objects: List of object names to transform
translate: Translation values [x, y, z]
rotate: Rotation values [x, y, z] in degrees
scale: Scale values [x, y, z]
"""
try:
bridge = get_maya_bridge()
if not objects:
return "Error: No objects specified for transformation"
commands = []
commands.append('import maya.cmds as cmds')
for obj in objects:
if translate:
commands.append(f'cmds.move({translate[0]}, {translate[1]}, {translate[2]}, "{obj}", absolute=True)')
if rotate:
commands.append(f'cmds.rotate({rotate[0]}, {rotate[1]}, {rotate[2]}, "{obj}", absolute=True)')
if scale:
commands.append(f'cmds.scale({scale[0]}, {scale[1]}, {scale[2]}, "{obj}", absolute=True)')
commands.append(f'print(f"Transformed objects: {objects}")')
command = '; '.join(commands)
response = bridge.execute_command(command)
if response.success:
return f"Successfully transformed {len(objects)} object(s): {', '.join(objects)}"
else:
return f"Failed to transform objects: {response.error}"
except Exception as e:
logger.error(f"Error in maya_transform: {e}")
return f"Error transforming objects: {str(e)}"
@mcp.tool()
def maya_delete(objects: List[str]) -> str:
"""Delete objects from scene
Args:
objects: List of object names to delete
"""
try:
bridge = get_maya_bridge()
if not objects:
return "Error: No objects specified for deletion"
objects_str = '", "'.join(objects)
command = f'import maya.cmds as cmds; cmds.delete(["{objects_str}"]); print(f"Deleted: {objects}")'
response = bridge.execute_command(command)
if response.success:
return f"Successfully deleted {len(objects)} object(s): {', '.join(objects)}"
else:
return f"Failed to delete objects: {response.error}"
except Exception as e:
logger.error(f"Error in maya_delete: {e}")
return f"Error deleting objects: {str(e)}"
@mcp.tool()
def maya_get_object_info(
object_name: str,
include_attributes: bool = True
) -> str:
"""Get detailed information about specific objects
Args:
object_name: Name of the object to inspect
include_attributes: Include attribute information
"""
try:
bridge = get_maya_bridge()
command = f'''
import maya.cmds as cmds
import json
try:
if not cmds.objExists("{object_name}"):
print("Object does not exist")
else:
info = {{
"name": "{object_name}",
"type": cmds.objectType("{object_name}"),
"transform": {{
"translate": cmds.getAttr("{object_name}.translate")[0] if cmds.attributeQuery("translate", node="{object_name}", exists=True) else None,
"rotate": cmds.getAttr("{object_name}.rotate")[0] if cmds.attributeQuery("rotate", node="{object_name}", exists=True) else None,
"scale": cmds.getAttr("{object_name}.scale")[0] if cmds.attributeQuery("scale", node="{object_name}", exists=True) else None
}}
}}
if {include_attributes}:
info["attributes"] = cmds.listAttr("{object_name}", keyable=True) or []
print(json.dumps(info, indent=2))
except Exception as e:
print(f"Error getting object info: {{e}}")
'''
response = bridge.execute_command(command)
if response.success:
return f"Object information: {response.result}"
else:
return f"Failed to get object info: {response.error}"
except Exception as e:
logger.error(f"Error in maya_get_object_info: {e}")
return f"Error getting object info: {str(e)}"
@mcp.tool()
def maya_list_objects(
object_type: str = "transform",
pattern: Optional[str] = None
) -> str:
"""List objects in scene by type
Args:
object_type: Type of objects to list (transform, mesh, camera, etc.)
pattern: Optional name pattern to filter objects
"""
try:
bridge = get_maya_bridge()
if pattern:
command = f'import maya.cmds as cmds; objects = cmds.ls("{pattern}", type="{object_type}"); print(f"Objects: {{objects}}")'
else:
command = f'import maya.cmds as cmds; objects = cmds.ls(type="{object_type}"); print(f"Objects: {{objects}}")'
response = bridge.execute_command(command)
if response.success:
return f"Objects of type '{object_type}': {response.result}"
else:
return f"Failed to list objects: {response.error}"
except Exception as e:
logger.error(f"Error in maya_list_objects: {e}")
return f"Error listing objects: {str(e)}"
@mcp.tool()
def maya_get_console_output() -> str:
"""Get console output from Maya Script Editor"""
try:
bridge = get_maya_bridge()
command = '''
import maya.cmds as cmds
try:
# Get recent console output
console_output = "Maya MCP Server is running and connected"
print(f"Console status: {console_output}")
except Exception as e:
print(f"Console error: {e}")
'''
response = bridge.execute_command(command)
if response.success:
return f"Console output: {response.result}"
else:
return f"Failed to get console output: {response.error}"
except Exception as e:
logger.error(f"Error in maya_get_console_output: {e}")
return f"Error getting console output: {str(e)}"
# Entry point for Smithery
def main():
"""Main entry point for Smithery deployment"""
return mcp
if __name__ == "__main__":
# For local testing
import asyncio
asyncio.run(mcp.run())