# llms.txt — dcc-mcp-maya Core Reference for AI Agents

> One-page cheat sheet. For the exhaustive reference see llms-full.txt.

---

## Project

**dcc-mcp-maya** — Embed a standards-compliant MCP Streamable HTTP server directly inside Autodesk Maya.
**Version:** 0.8.8 <!-- x-release-please-version -->
**License:** MIT
**Repo:** https://github.com/loonghao/dcc-mcp-maya
**Python:** >=3.7
**Maya:** 2020+
**Core dep:** `dcc-mcp-core>=0.17.36,<1.0.0`

---

## Quick Start

```python
import dcc_mcp_maya
handle = dcc_mcp_maya.start_server(port=8765)
# Direct MCP host → http://127.0.0.1:8765/mcp  |  Gateway / REST clients → http://127.0.0.1:9765/v1/* (search, describe, call, call_batch)
handle.shutdown()
```

Plugin mode: copy `maya/plugin/dcc_mcp_maya_plugin.py` to `MAYA_PLUG_IN_PATH` and load it. Server auto-starts. Sidecar mode ensures the standalone gateway locally at `http://127.0.0.1:9765/mcp`; newer core sidecars also bind a LAN listener at `http://<this-machine-lan-ip>:59765/mcp` unless `DCC_MCP_GATEWAY_REMOTE_PORT=0`. The plugin forwards `--display-name`, `--instance-id`, and `--gateway-name` so admin/CLI diagnostics can tell Maya sessions and gateway ownership apart.

---

## Core API

### Module-Level Functions

```python
start_server(
    port: int = 8765,
    server_name: str = "maya-mcp",
    register_builtins: bool = True,
    extra_skill_paths: Optional[List[str]] = None,
    include_bundled: bool = True,
    gateway_port: Optional[int] = None,
    registry_dir: Optional[str] = None,
    dcc_version: Optional[str] = None,
    scene: Optional[str] = None,
    enable_hot_reload: bool = False,
    enable_gateway_failover: bool = True,
) -> McpServerHandle

stop_server() -> None
```

### MayaMcpServer Constructor

```python
MayaMcpServer(
    port: int = 8765,
    server_name: str = "maya-mcp",
    server_version: str = "0.8.8",  # x-release-please-version
    gateway_port: Optional[int] = None,
    registry_dir: Optional[str] = None,
    dcc_version: Optional[str] = None,
    scene: Optional[str] = None,
    enable_gateway_failover: bool = True,
    metrics_enabled: Optional[bool] = None,      # env: DCC_MCP_MAYA_METRICS=1
    job_storage_path: Optional[str] = None,      # env: DCC_MCP_MAYA_JOB_STORAGE
    job_recovery: Optional[str] = None,          # env: DCC_MCP_MAYA_JOB_RECOVERY
)
```

Key methods: `register_builtin_actions()`, `start()`, `stop()`, `attach_dispatcher(dispatcher)`, `publish_capability_snapshot()`, `build_capability_manifest(loaded_only=False)`.

---

## Gateway Capability Surface (issues #163 / #164 / #165)

The adapter exposes a compact manifest on top of the MCP channel so gateways and AI agents can enumerate every discoverable action (loaded *or* unloaded) without inflating `tools/list`:

```python
from dcc_mcp_maya import (
    MayaCapabilityManifestBuilder,   # SOLID projection catalog → records
    CapabilityRecord,                # compact per-action value object
    build_manifest_payload,          # add metadata + totals header
    MayaContextSnapshotProvider,     # scene / selection / frame snapshot
    collect_gateway_metadata,        # project snapshot → gateway fields
)

# Option 1 — programmatic access
server = MayaMcpServer(port=8765)
server.register_builtin_actions(minimal=True)
manifest = server.build_capability_manifest()
# {'schema_version': '1', 'dcc_type': 'maya', 'metadata': {...},
#  'totals': {'actions': N, 'loaded_actions': M, ...},
#  'capabilities': [{tool_slug, backend_tool, skill_name, summary, tags, ...}]}

# Option 2 — MCP tool (always registered before start)
# Agents call `dcc_capability_manifest` with `{loaded_only: bool}`.
```

Each record is <=640 B serialised and omits `inputSchema` — roughly 4× cheaper than a full `tools/list` entry.  Loaded-skill actions are exposed that MCP `tools/list` intentionally skips (core uses `__skill__*` stubs), so the manifest fills a real discovery gap.

**Gateway note:** When the client talks to **dcc-mcp-gateway** instead of Maya directly, treat **REST** (`POST /v1/search`, `/v1/describe`, `/v1/call`, `/v1/call_batch` on the gateway port, default `9765`) as the primary bounded surface for orchestration; reserve per-Maya `…/mcp` Streamable HTTP for MCP-native hosts. After discovery, use upstream `call_tools` / `POST /v1/call_batch` for ordered multi-invocations (see `dcc-mcp-core` `llms.txt` gateway rows).

Live sync with the gateway FileRegistry:

* `server.publish_capability_snapshot(reason="...")` — push current Maya `scene / version / documents / display_name` via `DccServerBase.update_gateway_metadata`.  Invoked automatically on `start()`, `load_skill(...)`, and `unload_skill(...)`.
* `server.set_context_snapshot_provider(MayaContextSnapshotProvider())` — already wired at construction.  Exposes `maya.cmds` state (scene, selection, frame, frame_range, up_axis, units, version) to core's post-tool `append_context_snapshot` wrapper and the per-DCC REST `/v1/context` (when mounted by core).

Project-state persistence (issue #576 / core 0.14.21):

* `dcc_mcp_maya._project_tools.attach_to_server(server)` — registers `project_save / project_load / project_resume / project_status` MCP tools that persist scene state to `<scene_dir>/.dcc-mcp/project.json`.  Auto-invoked by `register_builtin_actions()`; opt out with `DCC_MCP_MAYA_PROJECT_TOOLS=0`.
* Public symbols: `ProjectToolsIntegration`, `MayaSceneResolver`, `attach_project_tools`, `ENV_PROJECT_TOOLS`.

MCP resource publishing (issue #187, requires `dcc-mcp-core>=0.15.0`):

* `dcc_mcp_maya._resources.install_resources(server, snapshot_provider=...)` — wraps `server._server.resources()` (core's `ResourceHandle`).  Publishes the live Maya scene to `scene://current` via the existing `MayaContextSnapshotProvider`, registers producers for `maya-cmds://help/<command>`, `maya-cmds://flags/<command>`, `maya-api://signatures/<class>`, `maya-project://current`, and hooks Maya `scriptJob` events (`SceneSaved`, `SceneOpened`, `NewSceneOpened`, `SceneImported`, `DagObjectCreated`, `NameChanged`, `SelectionChanged`) so updates fire `notifications/resources/updated` for SSE subscribers.  Throttled to one publish per 500 ms (lead-edge + trail-edge timer) so a 1000-node bulk import collapses to ~5 SSE frames.
* Auto-invoked by `register_builtin_actions()`; opt out with `DCC_MCP_MAYA_RESOURCES=0`.
* Memory rule: every Maya call into `server._server.resources()` lives in `_resources.py::MayaResourceBinder` — skill scripts and plugin code go through the binder, never the raw Rust handle.
* Public symbols: `MayaResourceBinder`, `install_resources`, `SCHEME_MAYA_CMDS`, `SCHEME_MAYA_API`, `SCHEME_MAYA_PROJECT`, `DEFAULT_SCENE_EVENTS`, `DEFAULT_SCENE_THROTTLE_SECS`, `ENV_RESOURCES`.

Runtime readiness wiring (issue #184, requires `dcc-mcp-core>=0.15.0`; API landed in 0.14.28):

* Maya adapter owns the *wiring*; the three-state probe itself lives in core as `dcc_mcp_core.ReadinessProbe`.  Published to the inner Rust server via `McpHttpServer.set_readiness_probe(probe)` so `GET /v1/readyz` serves honest values (`200` only when all three bits green, `503` + `not-ready` envelope otherwise — core also rejects `tools/call` with `BACKEND_NOT_READY (-32002)` when not ready).
* `server.readiness_report()` → `{"process": bool, "dispatcher": bool, "dcc": bool}` shorthand.  `server.readiness` → `ReadinessBinder`; `server.readiness.probe` → the underlying `dcc_mcp_core.ReadinessProbe`.
* Transitions: `process` starts `True` (core default); `dispatcher` flips `True` the moment `MayaMcpServer.__init__` returns (executor always wired by then, whether inline or through a host dispatcher); `dcc` flips `True` synchronously in inline-executor mode (no host dispatcher — `mayapy` / tests) or after the first main-thread pump when a real host dispatcher is attached.
* Opt-in advisory timeout: `DCC_MCP_MAYA_READINESS_TIMEOUT_SECS=<positive int>` — consumed by orchestrators, never auto-fails the probe.
* Maya public symbols: `ReadinessBinder`, `install_readiness`, `resolve_readiness_timeout_secs`, `ENV_READINESS_TIMEOUT_SECS`.  Core public symbols used here: `dcc_mcp_core.ReadinessProbe` (with `set_dispatcher_ready` / `set_dcc_ready` / `report` / `is_ready` / `fully_ready`).

Qt recovery dialog detection (issue #241):

* `dcc_mcp_maya._recovery_dialog.scan_recovery_dialog()` polls Maya Qt top-level windows for C++ crash-recovery dialogs (`"Maya has stopped working"`, `"已停止工作"`, recovery-file titles). It is called automatically after `affinity: main` skill actions return, on the main-thread callable path.
* If `DCC_MCP_MAYA_AUTO_DISMISS_CRASH_DIALOG=1`, matching dialogs are dismissed best-effort via OK / Close / Reopen buttons or dialog `accept()` / `close()`.
* Tool result contexts and `scene://current` snapshots include `maya_recovered`, `maya_status`, and `maya_recovery_dialog` once a dialog is detected. Public symbols: `ENV_AUTO_DISMISS_CRASH_DIALOG`, `scan_recovery_dialog`, `current_recovery_status`, `clear_recovery_status`.

Shutdown safety nets (issue #186):

* `dcc_mcp_maya._shutdown_safety.ShutdownCoordinator` — composes four independent safety nets so non-cooperative Maya exits stop leaking `FileRegistry` rows: `MSceneMessage.kMayaExiting` hook, `atexit` fallback, crash-resilient process sentinel (OS drops on `kill -9`), opt-in defensive `__del__` guard.  Auto-installed from the Maya plugin's `initializePlugin` after `_start()`; torn down in `uninitializePlugin`.
* Each net opts out via env var: `DCC_MCP_MAYA_KMAYA_EXITING_HOOK=0`, `DCC_MCP_MAYA_ATEXIT_HOOK=0`, `DCC_MCP_MAYA_PROCESS_SENTINEL=0`.  Defensive `__del__` is off by default — enable with `DCC_MCP_MAYA_DEFENSIVE_DEL=1` in `mayapy` / test fixtures only.
* Public symbols: `ShutdownCoordinator`, `ProcessSentinel`, `DefensiveShutdownGuard`, `register_kmaya_exiting_hook`, `register_atexit_hook`, `write_process_sentinel`, `orphan_sentinels`, `ENV_KMAYA_EXITING_HOOK`, `ENV_ATEXIT_HOOK`, `ENV_PROCESS_SENTINEL`, `ENV_DEFENSIVE_DEL`.
* Support matrix: `docs/guide/shutdown-matrix.md` (EN) / `docs/zh/guide/shutdown-matrix.md` (ZH).


---

## Skill Authoring Helpers (api.py)

```python
from dcc_mcp_maya.api import (
    maya_success, maya_error, maya_warning, maya_from_exception,
    with_maya, require_cmds, get_cmds, is_maya_available,
    require_param, require_any_param, get_param_list, missing_param_error, MissingParamError,
    validate_node_exists, validate_node_type, batch_validate_nodes,
    ensure_valid_name, build_context_dict,
    scene_object_from_node, object_transform_from_node, bounding_box_from_node,
    maya_capabilities,
)
```

Decorator pattern (recommended for simple scripts):
```python
@with_maya
def create_sphere(radius: float = 1.0) -> dict:
    import maya.cmds as cmds
    result = cmds.polySphere(radius=radius)
    return maya_success("Created sphere", object_name=result[0])
```

---

## Skill Discovery & Loading

Agent workflow (skills-first — **default**):
1. `search_skills` / `dcc_capability_manifest` / `search_tools` — find the domain skill or gateway slug.
2. `load_skill("maya-primitives")` (or the matching skill) — activate tools with `inputSchema` + annotations.
3. Call the concrete tool (e.g. `maya_primitives__create_sphere`). Use `activate_group("extended")` when you need optional groups.

Reserve `maya_scripting__execute_python` / `execute_mel` for **escape hatches** (no skill yet, bulk in-Maya loops, OpenMaya-only). Use `maya_scripting__list_plugins`, `maya_scripting__load_plugin`, and `maya_scripting__unload_plugin` for plug-in lifecycle work. Operators can block arbitrary execution with `DCC_MCP_MAYA_DISABLE_EXECUTE_PYTHON=1`, `DCC_MCP_MAYA_DISABLE_EXECUTE_MEL=1`, or `DCC_MCP_MAYA_DISABLE_ARBITRARY_SCRIPT=1` (both).

Tool naming: `{skill_name.replace("-","_")}__{script_stem}` (e.g. `maya_scene__new_scene`).

---

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `DCC_MCP_MAYA_PORT` | `8765` | TCP port |
| `DCC_MCP_MAYA_SERVER_NAME` | `maya-mcp` | MCP init name |
| `DCC_MCP_MAYA_SKILL_PATHS` | — | Maya skill search roots (`;` Win / `:` Unix); Rez packages usually append `{root}/skills` |
| `DCC_MCP_SKILL_PATHS` | — | Global fallback skill search roots |
| `DCC_MCP_MINIMAL` | `1` | `0`=full mode, `1`=minimal mode |
| `DCC_MCP_DEFAULT_TOOLS` | — | Comma-separated startup skill list |
| `DCC_MCP_MAYA_METRICS` | `0` | `1`=Prometheus `/metrics` |
| `DCC_MCP_MAYA_JOB_STORAGE` | `<data_dir>/jobs.db` | SQLite persistence path |
| `DCC_MCP_MAYA_JOB_RECOVERY` | `drop` | `requeue`=resume idempotent jobs |
| `DCC_MCP_GATEWAY_PORT` | `9765` | Multi-instance gateway port (`0`=off) |
| `DCC_MCP_GATEWAY_NAME` | `dcc-mcp-gateway@<hostname>` | Human-readable standalone gateway label |
| `DCC_MCP_REGISTRY_DIR` | OS temp | Shared discovery registry |
| `DCC_MCP_MAYA_HOT_RELOAD` | `0` | `1`=watch skills for disk changes |
| `DCC_MCP_MAYA_DEV_ROOTS` | — | Optional path-list of trusted roots for `maya-dev` project attachment |
| `DCC_MCP_MAYA_AUTO_DISMISS_CRASH_DIALOG` | `0` | `1`=best-effort dismiss detected Maya Qt recovery dialogs after `affinity: main` tool calls |
| `DCC_MCP_MAYA_DISABLE_EXECUTE_PYTHON` | `0` | `1`/`true`/`yes`/`on` — refuse `execute_python` (skills-first enforcement) |
| `DCC_MCP_MAYA_DISABLE_EXECUTE_MEL` | `0` | Same tokens — refuse `execute_mel` only |
| `DCC_MCP_MAYA_DISABLE_ARBITRARY_SCRIPT` | `0` | Same tokens — refuse **both** `execute_python` and `execute_mel` |

---

## Key Files

| File | Purpose |
|------|---------|
| `src/dcc_mcp_maya/server.py` | `MayaMcpServer`, builtin skill discovery, metrics, jobs |
| `src/dcc_mcp_maya/dispatcher/` | Thread-affinity dispatchers + cancellation (directory module) |
| `src/dcc_mcp_maya/api.py` | Skill authoring helpers (18 symbols) |
| `src/dcc_mcp_maya/_recovery_dialog.py` | Qt-level crash/recovery dialog detector + status surfacing |
| `src/dcc_mcp_maya/plugin.py` | Maya plugin entry point |
| `src/dcc_mcp_maya/skills/` | 25 built-in skill packages (198 typed tool declarations) |
| `docs/` | VitePress docs (EN + ZH) |
| `README.md` | Human overview |
| `AGENTS.md` | Progressive disclosure map |
