3dsmax-mcp Development Guide (Clean Version)
This guide is the single source of truth for building and debugging 3dsmax-mcp tools.
1) Hard Rules
Materials
- •Never use
execute_maxscriptfor material work. - •Always use:
- •
assign_material - •
set_material_property - •
set_material_properties - •
set_sub_material(Multi/Sub slot management)
- •
Only use execute_maxscript for material-adjacent tasks that have no dedicated tool, such as:
- •file I/O
- •creating texture map objects (e.g.
OSLMap,Bitmaptexture,ai_bump2d) - •assigning sub-materials inside a Multi/Sub when no tool covers it
Rendering and viewport
- •Do not render unless user explicitly asks for it.
- •Avoid screenshots by default. Only capture when:
- •The user explicitly asks to see the scene
- •Visual verification is truly necessary (e.g. debugging a visual issue you can't diagnose otherwise)
- •When capturing: prefer
capture_viewportorcapture_model. Only usecapture_screen enabled:truefor full UI/fullscreen needs.
2) Always Inspect Before You Change Anything
Never guess:
- •property names
- •class names
- •enum values
- •parameter types
Use these first:
- •
inspect_object→ quick overview (class, transform, modifiers, material, mesh stats) - •
inspect_properties target="baseobject"|"material"|"modifier"→ full typed properties - •
inspect_modifier_properties→ modifier params specifically - •
get_object_properties→ detailed object state (transform/material/modifiers)
Fallback inspection commands (MAXScript) when needed:
- •
showInterfaces obj - •
showMethods obj - •
getPropNames obj - •
classOf obj,superClassOf obj - •
showProperties obj.modifiers[i] - •
showProperties obj.material
3) Tool Selection Cheat Sheet
Understand an object
- •Overview →
inspect_object - •Typed properties →
inspect_properties - •Modifier params →
inspect_modifier_properties - •Full object details →
get_object_properties
Find things in the scene
- •List/filter objects →
get_scene_info - •List assigned materials →
get_materials - •Enumerate scene-wide class instances →
find_class_instances(usesuperclass) - •Instancing status →
get_instances - •Find objects by property →
find_objects_by_property - •Dependencies graph →
get_dependencies - •Fog/volume/lens effects →
get_effects - •State Sets / cameras →
get_state_sets,get_camera_sequence
Materials
- •Create + assign →
assign_material - •Set one property →
set_material_property - •Set many properties →
set_material_properties - •Fast slot discovery (low-token, default map-only + bitmap class hints) →
get_material_slots - •Multi/Sub slots →
set_sub_material - •Inspect material →
inspect_properties target="material"
Texture maps
- •Auto PBR from folder →
create_material_from_textures - •Create map →
create_texture_map(stored as global var) - •Set map params →
set_texture_map_properties - •Write OSL + create map →
write_osl_shader - •Wire map into material slot →
set_material_property value="{global_var_name}"
Modify scene objects
- •Set object prop →
set_object_property - •Add/remove modifier →
add_modifier/remove_modifier - •Enable/disable modifier →
set_modifier_state - •Scene-wide modifier edits →
batch_modify - •Collapse stack →
collapse_modifier_stack - •De-instance modifier →
make_modifier_unique - •Transform →
transform_object - •Effects →
toggle_effect/delete_effect
Organize
- •Parent/unparent →
set_parent - •Show/hide/freeze →
set_visibility - •Select →
select_objects - •Clone/instance →
clone_objects - •Batch rename →
batch_rename_objects
Build geometry
- •Primitives →
create_object - •Structures →
build_structure - •Grid placement →
place_grid_array/place_on_grid - •Circular placement →
place_circle - •Floor plans →
build_floor_plan
See the scene
- •Viewport only (safe default) →
capture_viewportorcapture_model - •Full UI panels / fullscreen →
capture_screen enabled:true - •Render →
render_scene - •Identify visually →
isolate_and_capture_selected
4) When execute_maxscript Is Allowed
Use it only when no dedicated tool exists, such as:
- •animation keyframing
- •custom scripted operations
- •render settings / environment setup
- •specialized Max features not covered by tools
5) Architecture (Bridge + Protocol)
- •MCP server: Python (FastMCP)
- •3ds Max side: MAXScript TCP listener
- •Address:
127.0.0.1:8765 - •Protocol: JSON + newline delimiter, one request/response per connection
- •Listener design:
.NET TcpListener+ timer polling (50ms) to stay non-blocking
6) Adding a New Tool (Python)
- •Create:
src/tools/<name>.py - •Import:
from ..server import mcp, client - •Decorate:
@mcp.tool() - •Build MAXScript string
- •Send it:
client.send_command(maxscript) - •Return:
response.get("result", "")(or sensible default)
If the operation can run long (e.g., render), set timeout:
- •
client.send_command(maxscript, timeout=300)
After editing tool files, restart the MCP server to load changes.
7) MAXScript Pitfalls (High-Frequency Errors)
Constructors
- •Do not use parentheses after class name when passing keyword params:
- •✅
ai_standard_surface name:"Mat1" metalness:1.0 - •❌
ai_standard_surface() name:"Mat1" metalness:1.0
- •✅
Case-insensitivity
- •MAXScript is case-insensitive.
Randrare the same variable. - •Use descriptive unique names (
ringRadius,tubeRadius, etc.).
Scope
- •
execute()runs in global scope. - •No
localat top level; use locals only inside functions/blocks.
View types
- •Use
#view_persp_user(not#view_persp) - •Examples:
#view_left,#view_front,#view_top,#view_iso_user,#view_persp_user
No built-in join
MAXScript has no stringJoin. Use manual concatenation loops.
Reserved names / keywords
- •Avoid global identifiers like:
output,result,bmp,foliage,floor,osl,OSLMap - •
byis a reserved keyword (cannot be a var or parameter name)
Noise modifier naming
- •
Noiseis the texture map, not the modifier. - •Modifier class is
Noisemodifier.
Temp path mismatch
- •
(getDir #temp)is Max’s temp, not OS temp. - •Use
.NET Path.GetTempPath()to match Python temp.
String escaping + JSON
- •Always escape user strings before embedding in MAXScript (use your project’s safe-name helper).
- •Build JSON manually; always escape strings using
escapeJsonString()frommcp_server.ms.
Python f-strings + braces
- •Double MAXScript braces in Python f-strings:
{{and}}, or use raw strings.
.NET strings
- •Convert .NET string to MAXScript string with
str as stringbefore using.countetc.
SubAnim keys
- •
numKeysisn’t available on SubAnim; access controller keys via the actual controller object.
8) Viewport Capture Rules
- •OSL maps show correctly in viewport only in High Quality mode.
- •Switch via:
actionMan.executeAction -844228238 "40"
- •Switch via:
- •Use
completeredraw()before capturing. - •Always frame before capture:
- •select target →
max zoomext sel→ capture
- •select target →
- •Avoid
viewport.setTMfor camera placement; it’s unreliable.
9) Scattering Policy
- •If user says “scatter”, default to procedural tools (not manual loop placement).
- •Prefer tyFlow over Particle Flow.
- •Built-in Scatter limitations:
- •Max 2025+
ScatterGeometryexists but some arrays are UI-only and not settable via MAXScript. - •Legacy Scatter compound object not creatable via MAXScript in Max 2026.
- •Max 2025+
Fallback when tools fail:
- •Convert distribution surface to mesh (
snapshotAsMesh) - •Sample faces, use
meshOp.getFaceCenter - •Orient using global
getFaceNormal mesh faceIdx - •Instance placement as last resort
10) Scene Organization Rules
- •Prefer Dummy-based hierarchies for clean outliner.
- •Don’t create a Dummy on first build of a single object; organize only when you’ll reuse/group.
Dummy workflow order:
- •Create all objects
- •Compute combined bounding box
- •Create Dummy
- •Set Dummy box size to bbox
- •Position Dummy at bbox center XY
- •Set pivot to minimum Z
- •Parent objects into Dummy
11) Instancing & Cloning Notes
- •
instancedoesn’t work reliably on group heads. - •Use
maxOps.cloneNodes ... cloneType:#instance newNodes:&cloneArr - •Instancing a Dummy alone does not bring children. To instance hierarchies:
- •clone full descendant list, or
- •group/attach depending on the situation
12) Splines (Key Properties)
Renderable spline params:
- •
render_displayRenderMesh - •
render_displayRenderSettings - •
render_thickness
Not a thing:
- •
render_viewport(doesn’t exist on SplineShape)
13) Booleans
- •Use
BooleanMod(modern), not ProBoolean. - •Add operands via
bm.BooleanModifier.AppendOperand ... - •Make cutters thick enough: operand B must fully intersect operand A.
14) Safety for Destructive Operations
- •Prefer Hold/Fetch for critical operations:
- •
holdMaxFile() - •revert with:
fetchMaxFile quiet:true
- •
For batch ops:
- •wrap with
disableSceneRedraw()/enableSceneRedraw()/redrawViews() - •preserve selection
- •allow abort via
keyboard.escPressed
15) Data Channel Modifier (DC)
Use tools:
- •Build full graph →
add_data_channel - •Inspect →
inspect_data_channel - •Change one operator →
set_data_channel_operator - •Add script operator →
add_dc_script_operator - •Presets →
list_dc_presets/load_dc_preset
Common DC pitfalls:
- •Must be Editable Mesh/Poly → convert first
- •
operator_orderis 0-based - •Operators not in order do not execute
- •GeoQuantize before element-level ops to avoid tearing
- •Node references must be actual nodes, not strings
- •Always include
vertex_output(output=0, Position) at the end of TransformElements/ColorElements pipelines — composite operators need it to write results to the mesh - •TransformElements.transformType actual values: 0=Position, 1=Rotation, 2=Scale%, 3=ScaleUniform (sequential, NOT the gapped 0,2,3,4 from Autodesk docs)
- •Use
"blend"key in operator dicts to setoperator_opsblend mode: 0=Replace, 1=Add, 2=Subtract, 3=Multiply, 4=Divide, 5=Dot, 6=Cross
16) Debug / Test Loop
- •Use
execute_maxscriptas an “escape hatch” for quick experiments. - •If you get
ConnectionRefusedError, the MAXScript listener isn’t running. - •Confirm comms/temp directory exists to validate bridge readiness.
- •Restart MCP server after Python tool edits.