#!/usr/bin/env python3
import asyncio
import json
import logging
import os
import socket
import sys
from typing import Any, Dict, List, Optional, Sequence
try:
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
except ImportError:
print("Error: MCP SDK not installed. Install with: pip install mcp", file=sys.stderr)
sys.exit(1)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("maya-mcp-expanded")
class SimpleMayaConnection:
"""Simple connection to Maya's command port."""
def __init__(self, host: str = "localhost", port: int = 4434):
self.host = host
self.port = port
logger.info(f"Maya connection: {host}:{port}")
def execute_python(self, code: str, timeout: float = 30.0) -> str:
"""Execute Python code in Maya and return result."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((self.host, self.port))
sock.sendall(code.encode('utf-8'))
result_parts = []
while True:
try:
chunk = sock.recv(4096)
if not chunk:
break
result_parts.append(chunk.decode('utf-8', errors='replace'))
except socket.timeout:
break
result = ''.join(result_parts).strip()
sock.close()
return result
except ConnectionRefusedError:
raise RuntimeError(
f"Cannot connect to Maya at {self.host}:{self.port}\n"
"Make sure Maya command port is open."
)
except Exception as e:
logger.error(f"Maya execution failed: {e}")
raise
class ExpandedMayaMCPServer:
"""Expanded MCP Server with comprehensive Maya tools."""
def __init__(self):
self.server = Server("maya-mcp-expanded")
self.maya_host = os.getenv("MAYA_HOST", "localhost")
self.maya_port = int(os.getenv("MAYA_PORT", "4434"))
self.maya = SimpleMayaConnection(self.maya_host, self.maya_port)
logger.info("Expanded Maya MCP Server initialized")
self._register_handlers()
def _register_handlers(self):
"""Register MCP protocol handlers."""
@self.server.list_tools()
async def list_tools() -> list[Tool]:
"""Return all available Maya tools."""
return self._get_all_tools()
@self.server.call_tool()
async def call_tool(name: str, arguments: dict) -> Sequence[TextContent]:
"""Execute a Maya tool."""
logger.info(f"Tool call: {name}")
try:
code = self._build_maya_code(name, arguments)
result = self.maya.execute_python(code)
result_text = self._parse_result(result)
return [TextContent(
type="text",
text=f"✓ {name} completed:\n\n{result_text}"
)]
except Exception as e:
error_msg = f"Error in {name}: {str(e)}"
logger.error(error_msg)
return [TextContent(type="text", text=f"✗ {error_msg}")]
def _get_all_tools(self) -> list[Tool]:
"""Define all available tools."""
tools = [
# BASIC PRIMITIVES
Tool(
name="create_polygon_primitive",
description="Create polygon primitives: sphere, cube, cylinder, plane, torus, cone, pyramid, pipe, prism, helix, soccer ball. Objects created at origin with scale [1,1,1].",
inputSchema={
"type": "object",
"properties": {
"primitive_type": {
"type": "string",
"enum": ["sphere", "cube", "cylinder", "plane", "torus", "cone", "pyramid", "pipe", "prism", "helix", "soccer"],
"description": "Type of primitive"
},
"name": {"type": "string", "description": "Optional object name"},
"subdivisions": {"type": "integer", "description": "Detail level (8-40)", "default": 20}
},
"required": ["primitive_type"]
}
),
Tool(
name="create_nurbs_primitive",
description="Create NURBS primitives: sphere, cube, cylinder, cone, plane, torus, circle, square. Smooth, organic shapes.",
inputSchema={
"type": "object",
"properties": {
"primitive_type": {
"type": "string",
"enum": ["sphere", "cube", "cylinder", "cone", "plane", "torus", "circle", "square"],
"description": "Type of NURBS primitive"
},
"name": {"type": "string", "description": "Optional object name"}
},
"required": ["primitive_type"]
}
),
# LIGHTS
Tool(
name="create_light",
description="Create lights: point, directional, spot, area, ambient, volume. Control intensity, color.",
inputSchema={
"type": "object",
"properties": {
"light_type": {
"type": "string",
"enum": ["point", "directional", "spot", "area", "ambient", "volume"],
"description": "Type of light"
},
"name": {"type": "string"},
"intensity": {"type": "number", "default": 1.0},
"color": {
"type": "array",
"items": {"type": "number"},
"minItems": 3,
"maxItems": 3,
"description": "RGB color [0-1, 0-1, 0-1]"
}
},
"required": ["light_type"]
}
),
# CAMERAS
Tool(
name="create_camera",
description="Create a camera. Can set focal length, film gate.",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"},
"focal_length": {"type": "number", "default": 35.0, "description": "Focal length in mm"}
}
}
),
# CURVES
Tool(
name="create_curve",
description="Create NURBS curves: line, circle, arc, spiral, helix, square, text. Useful for path animation, modeling guides.",
inputSchema={
"type": "object",
"properties": {
"curve_type": {
"type": "string",
"enum": ["line", "circle", "arc", "spiral", "helix", "square", "text"],
"description": "Type of curve"
},
"name": {"type": "string"},
"points": {
"type": "array",
"items": {
"type": "array",
"items": {"type": "number"},
"minItems": 3,
"maxItems": 3
},
"description": "For line curve: array of [x,y,z] points"
},
"radius": {"type": "number", "default": 1.0, "description": "For circle/arc"},
"text": {"type": "string", "description": "Text to create (for text curves)"}
},
"required": ["curve_type"]
}
),
# MESH OPERATIONS
Tool(
name="mesh_extrude",
description="Extrude faces to create depth. Select object first. Can extrude all faces or specific faces.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string", "description": "Object name"},
"distance": {"type": "number", "default": 1.0, "description": "Extrude distance"},
"divisions": {"type": "integer", "default": 1, "description": "Number of divisions"}
},
"required": ["object"]
}
),
Tool(
name="mesh_bevel",
description="Bevel (chamfer) edges for smooth corners. Makes objects look more realistic.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"offset": {"type": "number", "default": 0.1, "description": "Bevel size"},
"segments": {"type": "integer", "default": 1, "description": "Number of segments"}
},
"required": ["object"]
}
),
Tool(
name="mesh_smooth",
description="Smooth mesh to add subdivision. Makes objects look rounder and more organic.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"divisions": {"type": "integer", "default": 1, "description": "Subdivision levels (1-3)"}
},
"required": ["object"]
}
),
Tool(
name="mesh_boolean",
description="Boolean operations: union (combine), difference (subtract), intersection. Combine multiple objects into one.",
inputSchema={
"type": "object",
"properties": {
"object1": {"type": "string", "description": "First object"},
"object2": {"type": "string", "description": "Second object"},
"operation": {
"type": "string",
"enum": ["union", "difference", "intersection"],
"description": "Boolean operation type"
}
},
"required": ["object1", "object2", "operation"]
}
),
Tool(
name="mesh_combine",
description="Combine multiple meshes into one object. Useful for organization.",
inputSchema={
"type": "object",
"properties": {
"objects": {
"type": "array",
"items": {"type": "string"},
"description": "List of object names to combine"
},
"name": {"type": "string", "description": "Name for combined object"}
},
"required": ["objects"]
}
),
Tool(
name="mesh_separate",
description="Separate combined mesh into individual objects.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string", "description": "Object to separate"}
},
"required": ["object"]
}
),
# DEFORMERS
Tool(
name="add_deformer",
description="Add deformers: bend, twist, wave, flare, sine. Non-destructively deform geometry.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string", "description": "Object to deform"},
"deformer_type": {
"type": "string",
"enum": ["bend", "twist", "wave", "flare", "sine"],
"description": "Type of deformer"
},
"amount": {"type": "number", "default": 0.5, "description": "Deformation amount"}
},
"required": ["object", "deformer_type"]
}
),
# MATERIALS
Tool(
name="create_material",
description="Create and assign materials: lambert (matte), blinn (shiny), phong (plastic), aiStandardSurface (PBR). Set color and properties.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string", "description": "Object to assign material to"},
"material_type": {
"type": "string",
"enum": ["lambert", "blinn", "phong", "aiStandardSurface"],
"default": "lambert"
},
"name": {"type": "string", "description": "Material name"},
"color": {
"type": "array",
"items": {"type": "number"},
"minItems": 3,
"maxItems": 3,
"description": "RGB color [0-1, 0-1, 0-1]"
},
"metalness": {"type": "number", "description": "Metallic (0-1, for aiStandardSurface)"},
"roughness": {"type": "number", "description": "Roughness (0-1, for aiStandardSurface)"}
},
"required": ["object"]
}
),
# GROUPING
Tool(
name="group_objects",
description="Group multiple objects together. Useful for organization and hierarchy.",
inputSchema={
"type": "object",
"properties": {
"objects": {
"type": "array",
"items": {"type": "string"},
"description": "Objects to group"
},
"name": {"type": "string", "description": "Group name"}
},
"required": ["objects"]
}
),
Tool(
name="parent_object",
description="Parent one object under another. Child inherits parent's transforms.",
inputSchema={
"type": "object",
"properties": {
"child": {"type": "string"},
"parent": {"type": "string", "description": "Parent object (null to unparent)"}
},
"required": ["child"]
}
),
# DUPLICATION
Tool(
name="duplicate_object",
description="Duplicate an object. Can create multiple copies with transforms.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"copies": {"type": "integer", "default": 1, "description": "Number of copies"},
"translate_offset": {
"type": "array",
"items": {"type": "number"},
"minItems": 3,
"maxItems": 3,
"description": "Offset for each copy [x, y, z]"
}
},
"required": ["object"]
}
),
# SCENE ORGANIZATION
Tool(
name="arrange_in_grid",
description="Arrange objects in a grid pattern. Perfect for arrays of objects.",
inputSchema={
"type": "object",
"properties": {
"objects": {
"type": "array",
"items": {"type": "string"},
"description": "Objects to arrange"
},
"rows": {"type": "integer", "default": 3},
"columns": {"type": "integer", "default": 3},
"spacing": {"type": "number", "default": 2.0, "description": "Space between objects"}
},
"required": ["objects"]
}
),
Tool(
name="arrange_in_circle",
description="Arrange objects in a circle pattern.",
inputSchema={
"type": "object",
"properties": {
"objects": {
"type": "array",
"items": {"type": "string"}
},
"radius": {"type": "number", "default": 5.0}
},
"required": ["objects"]
}
),
# ANIMATION
Tool(
name="set_keyframe",
description="Set animation keyframe for object transform.",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"time": {"type": "number", "description": "Frame number"},
"attribute": {
"type": "string",
"enum": ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ", "scaleX", "scaleY", "scaleZ", "visibility"],
"description": "Attribute to keyframe"
},
"value": {"type": "number"}
},
"required": ["object", "attribute"]
}
),
# ORIGINAL CORE TOOLS
Tool(
name="list_scene_objects",
description="List objects in scene by type",
inputSchema={
"type": "object",
"properties": {
"object_type": {
"type": "string",
"enum": ["transform", "mesh", "camera", "light", "all"],
"default": "transform"
}
}
}
),
Tool(
name="get_object_info",
description="Get detailed info about an object",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}
),
Tool(
name="set_transform",
description="Set position, rotation, scale. Use scale for sizing (scale=[width,height,depth])",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"translate": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3},
"rotate": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3},
"scale": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3}
},
"required": ["object"]
}
),
Tool(
name="set_attribute",
description="Set attribute value (visibility, etc.)",
inputSchema={
"type": "object",
"properties": {
"object": {"type": "string"},
"attribute": {"type": "string"},
"value": {"oneOf": [{"type": "number"}, {"type": "boolean"}, {"type": "string"}]}
},
"required": ["object", "attribute", "value"]
}
),
Tool(
name="delete_object",
description="Delete object from scene",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}
),
Tool(
name="select_object",
description="Select object in Maya",
inputSchema={
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}
),
]
return tools
def _build_maya_code(self, tool_name: str, args: dict) -> str:
"""Build Python code for each tool."""
# POLYGON PRIMITIVES
if tool_name == "create_polygon_primitive":
ptype = args["primitive_type"]
name = args.get("name", "")
subdivs = args.get("subdivisions", 20)
name_arg = f'name="{name}"' if name else ""
type_map = {
"sphere": f"cmds.polySphere({name_arg}, subdivisionsAxis={subdivs}, subdivisionsHeight={subdivs})",
"cube": f"cmds.polyCube({name_arg})",
"cylinder": f"cmds.polyCylinder({name_arg}, subdivisionsAxis={subdivs})",
"plane": f"cmds.polyPlane({name_arg})",
"torus": f"cmds.polyTorus({name_arg}, subdivisionsAxis={subdivs})",
"cone": f"cmds.polyCone({name_arg})",
"pyramid": f"cmds.polyPyramid({name_arg})",
"pipe": f"cmds.polyPipe({name_arg})",
"prism": f"cmds.polyPrism({name_arg})",
"helix": f"cmds.polyHelix({name_arg})",
"soccer": f"cmds.polySoccerBall({name_arg})"
}
command = type_map.get(ptype, f"cmds.polyCube({name_arg})")
return f"""
import maya.cmds as cmds
import json
result = {command}
print(json.dumps({{"success": True, "result": result}}))
"""
# NURBS PRIMITIVES
elif tool_name == "create_nurbs_primitive":
ptype = args["primitive_type"]
name = args.get("name", "")
name_arg = f'name="{name}"' if name else ""
type_map = {
"sphere": f"cmds.sphere({name_arg})",
"cube": f"cmds.nurbsCube({name_arg})",
"cylinder": f"cmds.cylinder({name_arg})",
"cone": f"cmds.cone({name_arg})",
"plane": f"cmds.nurbsPlane({name_arg})",
"torus": f"cmds.torus({name_arg})",
"circle": f"cmds.circle({name_arg})",
"square": f"cmds.nurbsSquare({name_arg})"
}
command = type_map.get(ptype, f"cmds.sphere({name_arg})")
return f"""
import maya.cmds as cmds
import json
result = {command}
print(json.dumps({{"success": True, "result": result}}))
"""
# LIGHTS
elif tool_name == "create_light":
ltype = args["light_type"]
name = args.get("name", "")
intensity = args.get("intensity", 1.0)
color = args.get("color")
type_map = {
"point": "pointLight",
"directional": "directionalLight",
"spot": "spotLight",
"area": "areaLight",
"ambient": "ambientLight",
"volume": "volumeLight"
}
cmd = type_map.get(ltype, "pointLight")
return f"""
import maya.cmds as cmds
import json
light_name = cmds.{cmd}(name="{name}" if "{name}" else None)
if isinstance(light_name, list):
light_name = light_name[0]
cmds.setAttr(light_name + ".intensity", {intensity})
color = {repr(color)}
if color:
cmds.setAttr(light_name + ".color", color[0], color[1], color[2], type="double3")
print(json.dumps({{"success": True, "result": light_name}}))
"""
# CAMERA
elif tool_name == "create_camera":
name = args.get("name", "")
focal = args.get("focal_length", 35.0)
return f"""
import maya.cmds as cmds
import json
cam = cmds.camera(name="{name}" if "{name}" else None)
cmds.setAttr(cam[1] + ".focalLength", {focal})
print(json.dumps({{"success": True, "result": cam}}))
"""
# CURVES
elif tool_name == "create_curve":
ctype = args["curve_type"]
name = args.get("name", "")
points = args.get("points")
radius = args.get("radius", 1.0)
text = args.get("text", "")
if ctype == "line" and points:
points_str = ", ".join([f"({p[0]}, {p[1]}, {p[2]})" for p in points])
cmd = f"cmds.curve(point=[{points_str}], degree=1, name='{name}' if '{name}' else None)"
elif ctype == "circle":
cmd = f"cmds.circle(radius={radius}, name='{name}' if '{name}' else None)"
elif ctype == "square":
cmd = f"cmds.nurbsSquare(name='{name}' if '{name}' else None)"
elif ctype == "text":
cmd = f"cmds.textCurves(text='{text}', name='{name}' if '{name}' else None)"
else:
cmd = f"cmds.circle(name='{name}' if '{name}' else None)"
return f"""
import maya.cmds as cmds
import json
result = {cmd}
print(json.dumps({{"success": True, "result": result}}))
"""
# MESH OPERATIONS
elif tool_name == "mesh_extrude":
obj = args["object"]
distance = args.get("distance", 1.0)
divisions = args.get("divisions", 1)
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
# Select all faces
shape = cmds.listRelatives(obj, shapes=True)[0]
num_faces = cmds.polyEvaluate(obj, face=True)
faces = [f"{{obj}}.f[{{i}}]" for i in range(num_faces)]
cmds.select(faces)
cmds.polyExtrudeFacet(localTranslateZ={distance}, divisions={divisions})
cmds.select(clear=True)
print(json.dumps({{"success": True, "result": "Extruded {{}} faces".format(num_faces)}}))
"""
elif tool_name == "mesh_bevel":
obj = args["object"]
offset = args.get("offset", 0.1)
segments = args.get("segments", 1)
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
cmds.select(obj)
cmds.polyBevel3(offset={offset}, segments={segments})
cmds.select(clear=True)
print(json.dumps({{"success": True, "result": "Beveled"}}))
"""
elif tool_name == "mesh_smooth":
obj = args["object"]
divisions = args.get("divisions", 1)
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
cmds.polySmooth(obj, divisions={divisions})
print(json.dumps({{"success": True, "result": "Smoothed with {{}} divisions".format({divisions})}}))
"""
elif tool_name == "mesh_boolean":
obj1 = args["object1"]
obj2 = args["object2"]
operation = args["operation"]
op_map = {"union": 1, "difference": 2, "intersection": 3}
op_code = op_map.get(operation, 1)
return f"""
import maya.cmds as cmds
import json
obj1 = "{obj1}"
obj2 = "{obj2}"
if not (cmds.objExists(obj1) and cmds.objExists(obj2)):
print(json.dumps({{"success": False, "error": "One or both objects not found"}}))
else:
result = cmds.polyBoolOp(obj1, obj2, operation={op_code}, name=obj1 + "_boolean")
print(json.dumps({{"success": True, "result": result}}))
"""
elif tool_name == "mesh_combine":
objects = args["objects"]
name = args.get("name", "combined")
return f"""
import maya.cmds as cmds
import json
objects = {repr(objects)}
existing = [obj for obj in objects if cmds.objExists(obj)]
if len(existing) < 2:
print(json.dumps({{"success": False, "error": "Need at least 2 objects to combine"}}))
else:
result = cmds.polyUnite(*existing, name="{name}")
print(json.dumps({{"success": True, "result": result[0]}}))
"""
elif tool_name == "mesh_separate":
obj = args["object"]
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
cmds.select(obj)
result = cmds.polySeparate()
cmds.select(clear=True)
print(json.dumps({{"success": True, "result": result}}))
"""
# DEFORMERS
elif tool_name == "add_deformer":
obj = args["object"]
dtype = args["deformer_type"]
amount = args.get("amount", 0.5)
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
deformer = cmds.{dtype}(obj)[0]
# Set amount based on deformer type
if "{dtype}" == "bend":
cmds.setAttr(deformer + ".curvature", {amount})
elif "{dtype}" == "twist":
cmds.setAttr(deformer + ".startAngle", {amount} * 180)
cmds.setAttr(deformer + ".endAngle", {amount} * -180)
elif "{dtype}" == "wave":
cmds.setAttr(deformer + ".amplitude", {amount})
print(json.dumps({{"success": True, "result": deformer}}))
"""
# MATERIALS
elif tool_name == "create_material":
obj = args["object"]
mtype = args.get("material_type", "lambert")
name = args.get("name", "")
color = args.get("color")
metalness = args.get("metalness")
roughness = args.get("roughness")
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
# Create material
mat = cmds.shadingNode("{mtype}", asShader=True, name="{name}" if "{name}" else None)
sg = cmds.sets(renderable=True, noSurfaceShader=True, empty=True, name=mat + "SG")
cmds.connectAttr(mat + ".outColor", sg + ".surfaceShader")
# Set color
color = {repr(color)}
if color:
cmds.setAttr(mat + ".color", color[0], color[1], color[2], type="double3")
# Set PBR properties for aiStandardSurface
if "{mtype}" == "aiStandardSurface":
if {repr(metalness)} is not None:
cmds.setAttr(mat + ".metalness", {metalness})
if {repr(roughness)} is not None:
cmds.setAttr(mat + ".specularRoughness", {roughness})
# Assign to object
cmds.select(obj)
cmds.hyperShade(assign=mat)
cmds.select(clear=True)
print(json.dumps({{"success": True, "result": {{"material": mat, "shadingGroup": sg}}}}))
"""
# GROUPING
elif tool_name == "group_objects":
objects = args["objects"]
name = args.get("name", "group1")
return f"""
import maya.cmds as cmds
import json
objects = {repr(objects)}
existing = [obj for obj in objects if cmds.objExists(obj)]
if not existing:
print(json.dumps({{"success": False, "error": "No valid objects found"}}))
else:
group = cmds.group(*existing, name="{name}")
print(json.dumps({{"success": True, "result": group}}))
"""
elif tool_name == "parent_object":
child = args["child"]
parent = args.get("parent")
if parent:
return f"""
import maya.cmds as cmds
import json
if not cmds.objExists("{child}"):
print(json.dumps({{"success": False, "error": "Child not found"}}))
elif not cmds.objExists("{parent}"):
print(json.dumps({{"success": False, "error": "Parent not found"}}))
else:
cmds.parent("{child}", "{parent}")
print(json.dumps({{"success": True, "result": "Parented {child} under {parent}"}}))
"""
else:
return f"""
import maya.cmds as cmds
import json
if not cmds.objExists("{child}"):
print(json.dumps({{"success": False, "error": "Child not found"}}))
else:
cmds.parent("{child}", world=True)
print(json.dumps({{"success": True, "result": "Unparented {child} to world"}}))
"""
# DUPLICATION
elif tool_name == "duplicate_object":
obj = args["object"]
copies = args.get("copies", 1)
offset = args.get("translate_offset", [0, 0, 0])
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
duplicates = []
offset = {repr(offset)}
for i in range({copies}):
dup = cmds.duplicate(obj)[0]
if offset:
pos = cmds.getAttr(dup + ".translate")[0]
new_pos = [pos[j] + offset[j] * (i + 1) for j in range(3)]
cmds.setAttr(dup + ".translate", new_pos[0], new_pos[1], new_pos[2])
duplicates.append(dup)
print(json.dumps({{"success": True, "result": duplicates}}))
"""
# ARRANGEMENT
elif tool_name == "arrange_in_grid":
objects = args["objects"]
rows = args.get("rows", 3)
cols = args.get("columns", 3)
spacing = args.get("spacing", 2.0)
return f"""
import maya.cmds as cmds
import json
import math
objects = {repr(objects)}
existing = [obj for obj in objects if cmds.objExists(obj)]
if not existing:
print(json.dumps({{"success": False, "error": "No valid objects"}}))
else:
rows = {rows}
cols = {cols}
spacing = {spacing}
for i, obj in enumerate(existing):
row = i // cols
col = i % cols
x = col * spacing - (cols - 1) * spacing / 2
z = row * spacing - (rows - 1) * spacing / 2
cmds.setAttr(obj + ".translate", x, 0, z)
print(json.dumps({{"success": True, "result": f"Arranged {{len(existing)}} objects in grid"}}))
"""
elif tool_name == "arrange_in_circle":
objects = args["objects"]
radius = args.get("radius", 5.0)
return f"""
import maya.cmds as cmds
import json
import math
objects = {repr(objects)}
existing = [obj for obj in objects if cmds.objExists(obj)]
if not existing:
print(json.dumps({{"success": False, "error": "No valid objects"}}))
else:
radius = {radius}
count = len(existing)
for i, obj in enumerate(existing):
angle = (2 * math.pi * i) / count
x = radius * math.cos(angle)
z = radius * math.sin(angle)
cmds.setAttr(obj + ".translate", x, 0, z)
print(json.dumps({{"success": True, "result": f"Arranged {{count}} objects in circle"}}))
"""
# ANIMATION
elif tool_name == "set_keyframe":
obj = args["object"]
time = args.get("time")
attr = args["attribute"]
value = args.get("value")
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
attr = "{attr}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": "Object not found"}}))
else:
full_attr = obj + "." + attr
# Set value if provided
value = {repr(value)}
if value is not None:
cmds.setAttr(full_attr, value)
# Set keyframe
time = {repr(time)}
if time is not None:
cmds.setKeyframe(full_attr, time=time)
else:
cmds.setKeyframe(full_attr)
print(json.dumps({{"success": True, "result": f"Keyframe set on {{attr}}"}}))
"""
# ORIGINAL TOOLS (from simplified server)
elif tool_name == "list_scene_objects":
obj_type = args.get("object_type", "transform")
type_map = {
"transform": "type='transform'",
"mesh": "type='mesh'",
"camera": "cameras=True",
"light": "lights=True",
"all": "dagObjects=True"
}
flag = type_map.get(obj_type, "type='transform'")
return f"""
import maya.cmds as cmds
import json
objects = cmds.ls({flag}) or []
result = [{{"name": obj, "type": cmds.objectType(obj)}} for obj in objects]
print(json.dumps({{"success": True, "result": result}}))
"""
elif tool_name == "get_object_info":
obj_name = args["name"]
return f"""
import maya.cmds as cmds
import json
name = "{obj_name}"
if not cmds.objExists(name):
print(json.dumps({{"success": False, "error": "Object does not exist"}}))
else:
info = {{
"name": name,
"type": cmds.objectType(name),
"parent": cmds.listRelatives(name, parent=True),
"children": cmds.listRelatives(name, children=True)
}}
try:
info["translate"] = list(cmds.getAttr(name + ".translate")[0])
info["rotate"] = list(cmds.getAttr(name + ".rotate")[0])
info["scale"] = list(cmds.getAttr(name + ".scale")[0])
except:
pass
print(json.dumps({{"success": True, "result": info}}))
"""
elif tool_name == "set_transform":
obj = args["object"]
translate = args.get("translate")
rotate = args.get("rotate")
scale = args.get("scale")
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": f"Object '{{obj}}' does not exist"}}))
else:
try:
results = {{}}
translate = {repr(translate)}
if translate:
cmds.setAttr(obj + ".translate", translate[0], translate[1], translate[2], type="double3")
results["translate"] = translate
rotate = {repr(rotate)}
if rotate:
cmds.setAttr(obj + ".rotate", rotate[0], rotate[1], rotate[2], type="double3")
results["rotate"] = rotate
scale = {repr(scale)}
if scale:
cmds.setAttr(obj + ".scale", scale[0], scale[1], scale[2], type="double3")
results["scale"] = scale
print(json.dumps({{"success": True, "result": results}}))
except Exception as e:
print(json.dumps({{"success": False, "error": str(e)}}))
"""
elif tool_name == "set_attribute":
obj = args["object"]
attr = args["attribute"]
value = args["value"]
return f"""
import maya.cmds as cmds
import json
obj = "{obj}"
attr = "{attr}"
value = {repr(value)}
if not cmds.objExists(obj):
print(json.dumps({{"success": False, "error": f"Object '{{obj}}' does not exist"}}))
else:
try:
if isinstance(value, (list, tuple)) and len(value) == 3:
cmds.setAttr(obj + "." + attr, value[0], value[1], value[2], type="double3")
else:
cmds.setAttr(obj + "." + attr, value)
new_value = cmds.getAttr(obj + "." + attr)
print(json.dumps({{"success": True, "result": {{"attribute": attr, "value": new_value}}}}))
except Exception as e:
print(json.dumps({{"success": False, "error": str(e)}}))
"""
elif tool_name == "delete_object":
obj_name = args["name"]
return f"""
import maya.cmds as cmds
import json
name = "{obj_name}"
if cmds.objExists(name):
cmds.delete(name)
print(json.dumps({{"success": True, "result": "Deleted " + name}}))
else:
print(json.dumps({{"success": False, "error": "Object does not exist"}}))
"""
elif tool_name == "select_object":
obj_name = args["name"]
return f"""
import maya.cmds as cmds
import json
name = "{obj_name}"
if cmds.objExists(name):
cmds.select(name)
print(json.dumps({{"success": True, "result": "Selected " + name}}))
else:
print(json.dumps({{"success": False, "error": "Object does not exist"}}))
"""
else:
return f'print(json.dumps({{"success": False, "error": "Unknown tool"}}))'
def _parse_result(self, maya_output: str) -> str:
"""Parse Maya's output to extract result."""
try:
lines = maya_output.strip().split('\n')
for line in reversed(lines):
line = line.strip()
if line.startswith('{'):
data = json.loads(line)
if data.get("success"):
result = data.get("result", "")
if isinstance(result, (dict, list)):
return json.dumps(result, indent=2)
return str(result)
else:
error = data.get("error", "Unknown error")
raise RuntimeError(error)
return maya_output
except json.JSONDecodeError:
return maya_output
async def run(self):
"""Run the MCP server."""
logger.info("Starting Expanded Maya MCP Server...")
logger.info(f"Connecting to Maya at {self.maya_host}:{self.maya_port}")
logger.info("30+ tools available!")
async with stdio_server() as (read_stream, write_stream):
logger.info("MCP server running on STDIO")
await self.server.run(
read_stream,
write_stream,
self.server.create_initialization_options()
)
async def main():
"""Main entry point."""
try:
server = ExpandedMayaMCPServer()
await server.run()
except KeyboardInterrupt:
logger.info("Server stopped by user")
except Exception as e:
logger.exception(f"Server error: {e}")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())