Skip to main content
Glama
README.md
<!-- mcp-name: io.github.Xxx91n/mcp-for-maya -->

**English** | [简体中文](README.zh-CN.md)

<p align="center">
  <img src="https://raw.githubusercontent.com/Xxx91n/mcp-for-maya/main/.github/assets/hero.svg" width="100%" alt="mcp-for-maya — Give AI agents eyes inside Autodesk Maya"/>
</p>

# mcp-for-maya

> MCP server giving AI agents spatial awareness of Autodesk Maya scenes

[![CI](https://github.com/Xxx91n/mcp-for-maya/actions/workflows/ci.yml/badge.svg)](https://github.com/Xxx91n/mcp-for-maya/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/mcp-for-maya?cacheSeconds=300)](https://pypi.org/project/mcp-for-maya/) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/Xxx91n/mcp-for-maya/blob/main/LICENSE) [![Python](https://img.shields.io/pypi/pyversions/mcp-for-maya)](https://pypi.org/project/mcp-for-maya/)

## What this is

`mcp-for-maya` is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that lets AI agents (such as Codex and Claude) directly control Autodesk Maya for 3D modeling, scene planning, and production-ready work.

**Core capability:** AI no longer writes code blindly. It inspects scene objects, materials, and spatial relationships at any time, auditing changes against real engineering rules.

> [!WARNING]
> This server runs Python code directly inside Maya by design, not by accident. For details, see [docs/threat-model.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/threat-model.md).

<img src="https://raw.githubusercontent.com/Xxx91n/mcp-for-maya/main/.github/assets/section-quick-start.svg" width="100%" alt="Quick Start"/>

> For live captures and examples, see [docs/assets/showcase.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/assets/showcase.md).

### 1. Install

```bash
# Install from PyPI (recommended)
pip install mcp-for-maya

# Or run instantly with uvx without installing
uvx mcp-for-maya

# Alternative: install directly from git with uv
uv tool install git+https://github.com/Xxx91n/mcp-for-maya.git

# Or install from cloned source
git clone https://github.com/Xxx91n/mcp-for-maya.git
cd mcp-for-maya
pip install -e .
```

### 2. Connect Maya

#### Option A: Automatic setup (recommended)

Once the MCP server is running, the agent automatically calls `maya_setup_guide` to guide you through connecting:

1. Make sure Maya is open
2. Give the agent any instruction (like "check my Maya scene")
3. If not connected, the agent runs diagnostics and can install `userSetup.py` (merges safely with backup before writing)
4. Restart Maya, and the command port opens automatically

#### Option B: Manual setup

Run this in Maya's Script Editor:

```python
import maya.cmds as cmds

cmds.commandPort(name=":7001", sourceType="python")
```

> **Tip**: Open Script Editor via Maya menu → Windows → General Editors → Script Editor. Make sure the tab language is set to **Python** (not MEL).

#### Option C: Persistent auto-connect

Save the following code as `userSetup.py` in your Maya scripts folder:

| Platform | Path |
|----------|------|
| Windows | `%MAYA_APP_DIR%\<version>\scripts\` |
| Linux | `~/maya/<version>/scripts/` |
| macOS | `~/Library/Preferences/Autodesk/maya/<version>/scripts/` |

```python
import maya.cmds as cmds

cmds.evalDeferred('cmds.commandPort(name=":7001", sourceType="python")', lowestPriority=True)
```

#### Multiple Maya instances

Each `commandPort` binds to a specific `host:port`. A second instance cannot use the same port, so **each Maya instance must use its own port**. Typical layout:

| Maya instance | commandPort | Notes |
|---------------|-------------|-------|
| Instance A | `:7001` python | Main workspace |
| Instance B | `:7002` python | Second instance |
| Instance A | `:7011` mel | MEL port (auto-detected and skipped to avoid spamming errors) |

Run `cmds.commandPort(name=":<port>", sourceType="python")` in each instance's Script Editor (or each one's `userSetup.py`). **Auto-scan** is the primary method — the server regularly scans local Maya listening ports and sets up connections. If an instance is missed, call `add_session(host, port)` manually. If port scanning touches other local TCP services, set `MAYA_MCP_INCLUDE_PORTS=7001,7002` (comma-separated, ranges like `7005-7010` supported) to limit the scan, or use `MAYA_MCP_EXCLUDE_PORTS=<port>` to ignore specific ports.

Note: A `commandPort` **does not persist across sessions** — it closes when Maya quits. To keep it persistent, add it to `userSetup.py` (Option C).

#### Troubleshooting

| Issue | Solution |
|-------|----------|
| `list_sessions` returns empty | Run `maya_setup_guide(action="diagnose")` |
| Port already in use | Close the other Maya instance or pick another port |
| userSetup.py does not run | Check that the file is in the right scripts folder, then restart Maya |
| Blocked by firewall | Make sure localhost:7001 can be accessed locally |
| Second Maya instance not found | Each instance needs its own port (same port causes bind conflicts); if scanning still misses it, use `add_session(host, port)` |
| Script Editor spams syntax errors | An older symptom of scanning MEL ports. Current versions ignore non-Python ports automatically; if it still happens, exclude the port with `MAYA_MCP_EXCLUDE_PORTS` |

### 3. Configure your MCP client

Codex `~/.codex/config.toml`:

```toml
[mcp_servers.maya]
command = "uvx"
args = ["mcp-for-maya"]
tool_timeout_sec = 120
```

For git source installs: `args = ["--from", "git+https://github.com/Xxx91n/mcp-for-maya.git", "mcp-for-maya"]`. For local source builds, set `command = "python"`, `args = ["-m", "maya_mcp_server"]`, and point `PYTHONPATH` to `<repo>/src` in `env`.

### 4. Start using it

Chat naturally in your agent:

> "Check out what's in my Maya scene, then put together a display shelf by the entrance"

The AI automatically calls `scene_snapshot()` → inspects the scene → builds the model → audits the result with `scene_review()`.

<img src="https://raw.githubusercontent.com/Xxx91n/mcp-for-maya/main/.github/assets/section-icev-workflow.svg" width="100%" alt="ICEV Workflow"/>

Every scene edit follows the **ICEV** workflow (also provided as an agent skill card in `skills/icev-workflow`):

```
┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ INSPECT  │ ──→ │ COMPUTE  │ ──→ │ EXECUTE  │ ──→ │ VERIFY   │
│ snapshot │     │  plan    │     │  apply   │     │  audit   │
└──────────┘     └──────────┘     └──────────┘     └──────────┘
```

1. **INSPECT**: Call `scene_snapshot()` to capture full spatial scene data
2. **COMPUTE**: Calculate positions, sizes, and spacing from the spatial data
3. **EXECUTE**: Run Maya Python code via `execute_code()`
4. **VERIFY**: Check the outcome with `scene_assert()` + `scene_review()`; GUI sessions can use visual tools to verify visually

<img src="https://raw.githubusercontent.com/Xxx91n/mcp-for-maya/main/.github/assets/section-capability-matrix.svg" width="100%" alt="Capability Matrix"/>

| Capability | Tools | Notes |
|------------|-------|-------|
| 🧊 **Spatial awareness** | `scene_snapshot` `scene_inspect` `scene_measure` | Single call gets the full scene spatial model; precise distance, overlap, and gap measurements |
| 🎨 **Aesthetic analysis** | `scene_aesthetics` | 5 dimensions: color balance (60-30-10), spatial composition (golden ratio / rule of thirds), proportion & scale (ergonomics), lighting quality (three-point setup, fill ratio, color temperature, decay), visual flow |
| 🎬 **Camera planning** | `camera_create` `camera_orbit` | 8 industry-standard camera types + orbit animation |
| 🛡️ **Disaster recovery** | `scene_checkpoint` `scene_rollback` `scene_checkpoint_list` | In-memory scene snapshots; rollback rebinds the scene back to the original file |
| 🧠 **Scene planning** | `scene_plan` | Scene health checks, zone balance, layout tips, conflict prevention, natural-language planning |
| 📋 **Engineering audit** | `scene_review` `scene_validate` `scene_assert` | 11 deterministic checks (0-100 score) + custom constraint checks + scene assertions |
| ⚡ **Code execution** | `execute_code` `write_module` | Run arbitrary Python in Maya / inject reusable modules |
| 👁️ **Visual loop** | `scene_viewport_snapshot` `scene_render_preview` | WYSIWYG viewport capture + single-frame playblast preview (GUI sessions only) |
| 🔌 **Session management** | `list_sessions` `add_session` `maya_setup_guide` | Multi-session discovery/attachment + setup diagnostics and installation guidance |
| 📦 **Asset library** | `asset_search` `asset_import` | Poly Haven CC0 models — downloads over HTTPS on the host (domain allowlist, MD5 check, size limits, local cache), Maya imports FBX with auto-wired textures, face-count guards, and duplicate prevention via `GRP_asset_<id>` |
| 📤 **Scene export** | `scene_export` | Export whole scenes or selected objects to FBX/OBJ/USD; format auto-detected from file extension (no guesswork), auto-creates folders, explicit overwrite opt-in, restores selection afterward |
| 🔎 **Scene-graph inspection** | `scene_describe` `scene_nodes` | Node self-description (exact types, attribute details, connection wiring with size caps) + bounded enumeration including materials and tool nodes, with clean pagination |

25 MCP tools in total.

## Tools in detail

### Spatial awareness

```python
# Full scene snapshot (one call returns spatial data for all objects)
scene_snapshot(detail="compact", format="cos")

# Inspect a specific object (including neighboring objects)
scene_inspect(target="wall_entrance", include_neighbors=True)

# Precise measurement (4 modes)
scene_measure(obj_a="wall_north", obj_b="counter_A", mode="clearance")

# Verify scene state
scene_assert(expectations='{"wall": {"exists": true, "position": [0,0,500]}}')
```

### Engineering audit

```python
# 11 deterministic checks (returns 0-100 score)
scene_review()

# Spatial constraint validation
scene_validate(rules='[{"type": "min_clearance", "value": 180}]')
```

### Camera planning

```python
# Create camera (8 industry-standard types)
camera_create(target="product_display", shot_type="medium", azimuth=30, elevation=15)
# Types: extreme_wide / wide / medium / close / extreme_close / bird_eye / low_angle / over_shoulder

# Orbit animation camera
camera_orbit(center=[0, 100, 0], radius=500, frames=120)
```

### Disaster recovery

```python
# Save checkpoint (in-memory snapshot via exportAll; no undo history)
scene_checkpoint(name="before_renovation")

# List all checkpoints
scene_checkpoint_list()

# Rollback (saves a safety snapshot first, then rebinds the scene name back to the original file; run scene_snapshot afterward to refresh scene state)
# Scope: Snapshots are self-contained; file references are flattened without modifying reference sources; assumes one scene file per session
scene_rollback(filename="cp_before_renovation.ma")
```

### Assets (Poly Haven, host-side download)

```python
asset_search(query="camera", asset_type="models", limit=20)  # Search Poly Haven index
# Each result includes dimensions ([x, y, z] in metres, Poly Haven order) and polycount (int).
# Either is null when Poly Haven publishes no valid value. Search makes no file downloads.
asset_import(asset_id="Camera_01", resolution="1k")
# Downloads via HTTPS to local cache (MD5 verified + SHA-256 audit),
# then Maya imports the local FBX grouped under GRP_asset_<asset_id>
# (duplicate calls skip re-importing; force=True forces a re-import).
# Textures wire up automatically by file suffix
# (diff/rough/metal/nor_gl/ao/disp...), using sRGB/Raw color spaces and bump2d normals.
# Single-part assets with simple texture names (Diffuse/Rough/Metal...)
# are also recognized and connected to the asset's material.
# Safety guards: default 100k polygon cap (bypass with allow_high_polycount), dimension sanity check.
```

### Scene export

```python
scene_export(path="D:/out/scene.fbx")  # Full scene export; format inferred from .fbx extension
scene_export(path="/tmp/kit", format="obj")  # Missing extension gets appended -> kit.obj
scene_export(
    path="D:/out/kit.usd", objects=["GEO_box"]
)  # Export selected; restores selection afterward
# format in {fbx,obj,usd}: format and extension conflicts throw an error immediately, never guess.
# Existing files are protected unless overwrite=True. prompt=False is forced
# (avoids dialog popups that could freeze the connection); does not touch the scene dirty flag.
# Alembic is not supported (AbcExport is outside the cmds.file interface).
```

### Visual loop (GUI sessions only)

```python
# Viewport WYSIWYG screenshot (includes HUD and selection highlights — see what the artist sees)
scene_viewport_snapshot(max_size=800, format="jpeg")

# Single-frame playblast preview (clean preview without HUD; supports camera selection, dimensions rounded up to multiples of 4)
scene_render_preview(camera="CAM_hero", width=640, height=360)

# Both tools return [image, JSON metadata]; headless sessions return a gui_session_required error
# Use format="png" for wireframe or line-art review; always rely on returned metadata for actual dimensions
```

## CoS notation

Default output uses **Chain-of-Symbol** notation to compress scene data. The original research paper reports roughly 65% token savings compared to JSON on demo scenes (arXiv:2305.10276, -65.8%).

```
SCENE[164obj, 5zones] UNIT=cm UP=y
shell (23obj) @(-11.8,178.8,145.7)
  GRP_floor[mesh]@(0,0,0) 1121.5x20x1530.5
  pasted__arch_wall[mesh]@(0,0,0) 100x300x10
entrance (6obj) @(157.3,162.6,-111.6)
  GRP_workshopFront[group]@(1162,-17,103) 227.4x200.9x193.3
```

## Agent skills

The repository includes two **Experimental** skill cards (under the `skills/` folder):

| Skill | Purpose |
|-------|---------|
| `skills/icev-workflow` | ICEV workflow discipline: every scene edit must follow Inspect→Compute→Execute→Verify |
| `skills/scene-review-playbook` | Audit guide: scoring details for the 11 checks and how to turn findings into actions |

<img src="https://raw.githubusercontent.com/Xxx91n/mcp-for-maya/main/.github/assets/section-audit-trust.svg" width="100%" alt="Audit & Trust"/>

`scene_review()` provides 11 universal checks (scores 0-100, normalized by rule weight):

| Check | Points | What it checks |
|-------|--------|----------------|
| spatial | 10 | Object, camera, and light counts |
| overlaps | 10 | Bounding box collisions (excluding parent-child) |
| conflicts | 10 | Object penetration checks |
| zones | 5 | Zone naming coverage |
| naming | 5 | Production naming conventions |
| components | 10 | GRP_ grouping and hierarchy nesting depth <= 4 |
| orphans | 5 | Empty groups and default object names |
| aesthetics | 15 | 5 aesthetic dimensions (color, composition, scale, lighting, flow) |
| lighting | 10 | Three-point lighting, fill ratio, and decay |
| organization | 10 | Overall hierarchy health |
| constraints | 5 | Custom rule violations |

## Security & privacy

- **Zero telemetry**: No tracking, no phone-home. This project contains zero tracking or unsolicited outgoing network code, as you can verify in source. The only outgoing network calls are from the two asset tools: they access `api.polyhaven.com` / `dl.polyhaven.org|.com` over HTTPS only when invoked (with domain allowlist, MD5 check, and file size limits in `polyhaven.py`).
- **Local single-user**: The command port binds to localhost only. Connected MCP clients are treated as trusted callers.
- **Safety net**: A unified pipeline (`pipeline.py` + `security.py`) checks parameters, enforces token-bucket rate limits (~100 calls/60s for reads, ~20 calls/60s for writes per session), scans patterns (warn-only by default), and maintains an independent JSONL audit log across all 25 tools. It catches accidental mistakes rather than malicious clients. See [docs/threat-model.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/threat-model.md) for the complete threat model.
- **Transactional safety**: `scene_checkpoint` and `scene_rollback` offer in-memory snapshots and explicit rollback (snapshots do not keep undo history; file references are flattened).
- **Strict policy mode (optional)**: Set `MAYA_MCP_DISABLE_EXECUTE=1`, `MAYA_MCP_DISABLE_WRITE_MODULE=1`, or `MAYA_MCP_DISABLE_ARBITRARY=1` (disables all three) to remove code execution tools from `tools/list` and reject calls with `[policy_disabled]`. Read-only tools remain active. These are guardrails against misuse, not impenetrable security boundaries against a malicious agent—agents holding `execute_code` are already inside the trust boundary. They simply reduce the blast radius during demos, testing, or in CI sandboxes. See [docs/threat-model.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/threat-model.md) and error codes in [docs/guide/error-codes.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/guide/error-codes.md).
- **`safe_mode` AST gate (optional)**: When `MAYA_MCP_SAFE_MODE=1` is set (off by default), code payload tools remain listed, but their `code` parameters must pass an AST allowlist that denies everything else by default: imports are limited to standard packages (`maya`, `math`, `json`, `re`, `collections`, `itertools`, `functools`, `dataclasses`, `typing`), blocked builtin functions reject upon reference rather than just call (`eval`, `exec`, `open`, `__import__`, getattr, setattr, etc.), accessing `__builtins__` or dunder attributes is rejected, and unparseable input is rejected immediately. Blocked calls fail with `[safe_mode_rejected]`, reporting the violated rule ID and logging to the audit trail. Successful calls return a `mode: "safe"` field, and the tool description adds a `Boundary:` notice. DISABLE flags take priority by removing tools first—safe_mode only restricts what stays. Like DISABLE flags, this serves as a safety net against accidents, not an impenetrable barrier. See [docs/threat-model.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/threat-model.md) and error codes in [docs/guide/error-codes.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/docs/guide/error-codes.md).
- Vulnerability reporting: See [SECURITY.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/SECURITY.md).

## Requirements

| Environment | Requirement |
|-------------|-------------|
| Host (runs the MCP server) | Python **>= 3.10** (`requires-python`); Windows / Linux / macOS |
| Injected helper (runs inside Maya) | Maya **>= 2023** (bundled Python >= 3.9, relies on `ast.unparse`; older versions will receive a clear error on injection) |
| Verified against | Maya **2024** GUI |

## Development

```bash
uv sync --frozen                  # Set up locked dev environment from uv.lock (or: pip install -e . --group dev, requires pip>=25.1)
python -m pytest tests/ -q       # Run tests
ruff check src tests             # Lint
mypy src                         # Type check
python -m maya_mcp_server -vv    # Run with DEBUG logging (-v=INFO, -vv=DEBUG)
```

For contribution workflow, see [CONTRIBUTING.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/CONTRIBUTING.md); for changelog, see [CHANGELOG.md](https://github.com/Xxx91n/mcp-for-maya/blob/main/CHANGELOG.md).

## Credits

Forked from [chadrik/maya-mcp-server](https://github.com/chadrik/maya-mcp-server), retaining its [MIT](./LICENSE) license.

TDQS

A4.1/5.0

Scored across 25 tools

Disambiguation4/5

Most tools have clearly distinct purposes, and the descriptions go to unusual lengths to document boundaries (e.g., scene_snapshot vs scene_inspect vs scene_describe, and scene_review vs scene_plan vs scene_aesthetics). However, the large cluster of scene_* analysis tools means more than one or two could still be confused without careful reading.

Naming Consistency4/5

Names are consistently snake_case and grouped with predictable prefixes such as scene_, camera_, and asset_. A few tools use different conventions (maya_setup_guide as a noun phrase, list_sessions/add_session vs scene_*), but overall the pattern is readable and stable.

Tool Count3/5

At 25 tools, the surface is at the heavy end for a single MCP server. The tools span several domains (scene analysis, checkpoints, cameras, assets, sessions, code execution), but the set feels broad enough that some consolidation could improve usability.

Completeness4/5

The server covers scene inspection, validation, planning, checkpoints, export, cameras, asset import, code execution, and session management. Direct object manipulation (create/transform/delete) is missing, but execute_code provides a workaround for those operations.

Maintenance

ActivityActive
ResponsivenessNo recent community issues