llms.txt · diff
git:20261101.26ecf7e to git:20261101.007773d
2 added, 2 removed. Audit B to B.
# 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.9.44 <!-- x-release-please-version -->
**License:** MIT
**Repo:** https://github.com/dcc-mcp/dcc-mcp-maya
**Python:** >=3.7
**Maya:** 2020+
**Core dep:** `dcc-mcp-core>=0.20.0,<0.21.0`
---
## Quick Start
```python
import dcc_mcp_maya
handle = dcc_mcp_maya.start_server()
# Direct MCP host → handle.mcp_url() (OS-assigned) | 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`; LAN access is disabled by default; explicitly set a positive `DCC_MCP_GATEWAY_REMOTE_PORT` (for example `59765`) to enable it. 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: Optional[int] = None,
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: Optional[int] = None,
server_name: str = "maya-mcp",
server_version: str = "0.9.44", # 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()
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; Core API available since 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; Core API available since 0.15.0 and 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 — plain Python / tests) or after the first main-thread pump with a host dispatcher. With `MayaStandaloneDispatcher`, the caller must explicitly call `drain_queue()` to perform that pump.
* 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 refuse the named raw-execution tools with `DCC_MCP_MAYA_DISABLE_EXECUTE_PYTHON=1` / `DCC_MCP_MAYA_DISABLE_EXECUTE_MEL=1`, or refuse every code-bearing tool with `DCC_MCP_MAYA_DISABLE_ARBITRARY_SCRIPT=1`. Code-bearing = expressions (`create_expression` / `edit_expression`), `get_script_node` create, `introspect_eval`, `write_module`, `execute_python`, `execute_mel`. Typed and read-only tools stay available. This is an adapter-local tool policy, **not a sandbox** and not a Core/cross-DCC guarantee; see `README.md` "Restricted mode".
+ 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 refuse the named raw-execution tools with `DCC_MCP_MAYA_DISABLE_EXECUTE_PYTHON=1` / `DCC_MCP_MAYA_DISABLE_EXECUTE_MEL=1`, or refuse every code-bearing tool with `DCC_MCP_MAYA_DISABLE_ARBITRARY_SCRIPT=1`. Code-bearing = expressions (`create_expression` / `edit_expression`), `get_script_node` create, `introspect_eval`, `write_module`, `execute_python`, `execute_mel`, `maya-dev` `run_script` / `run_entrypoint` / `run_check` / `reload_modules` / `start_debugpy`, and `load_plugin`. `attach_project` (root registration only), `unload_plugin`, `list_plugins` and other typed/read-only tools stay available. This is an adapter-local tool policy, **not a sandbox** and not a Core/cross-DCC guarantee; see `README.md` "Restricted mode".
Tool naming: `{skill_name.replace("-","_")}__{script_stem}` (e.g. `maya_scene__new_scene`).
---
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DCC_MCP_MAYA_PORT` | unset (`0`) | Optional fixed TCP port; unset/`0` lets the OS assign one |
| `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 **all code-bearing tools** (expressions, script nodes, raw Python/MEL, `write_module`, `introspect_eval`). Adapter-local policy, not a sandbox |
+ | `DCC_MCP_MAYA_DISABLE_ARBITRARY_SCRIPT` | `0` | Same tokens — refuse **all code-bearing tools** (expressions, script nodes, raw Python/MEL, `write_module`, `introspect_eval`, `maya-dev` run/import/debug tools, `load_plugin`). Adapter-local policy, not a sandbox |
| `DCC_MCP_MAYA_VERSION_CHECK` | `1` | `0`=skip startup version self-check (running module vs installed distribution metadata, for `dcc-mcp-maya` and `dcc-mcp-core`) |
---
## 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/` | 30 built-in skill packages (245 typed MCP tools) |
| `docs/` | VitePress docs (EN + ZH) |
| `README.md` | Human overview |
| `AGENTS.md` | Progressive disclosure map |