Getting Started
Installation
Public Agent Skills
Install the Skill that matches the agent's task. Cloning this repository is not required:
| Task | Skill |
|---|---|
| Operate a live DCC, discover tools, or search the Marketplace | @loonghao/dcc-mcp |
| Create or modernize a complete DCC-MCP adapter/runtime | @loonghao/dcc-mcp-creator |
| Create, validate, or improve a DCC-specific Skill package | @loonghao/dcc-mcp-skills-creator |
# OpenClaw workspace: default live-DCC control Skill
openclaw skills install @loonghao/dcc-mcp
# Direct ClawHub CLI
npx --yes clawhub@0.23.1 install @loonghao/dcc-mcpSubstitute a creator slug only for its matching development task. Start a new agent turn after installation.
CLI from the dcc-mcp Skill
# Run from the installed dcc-mcp Skill directory.
python scripts/check_cli.py --ensure-cli --prettyFor agents, obtain user consent before installing or downloading a new binary. The bundled helper is fixed to the official dcc-mcp/dcc-mcp-core release. It validates the platform update manifest and CLI SHA-256 before replacing the binary, and fails closed on an invalid URL, manifest, digest, or download. SHA-256 verifies that the binary matches the release manifest. This helper is the bootstrap trust boundary; installed gateway-driven updates additionally verify the release workflow's detached Sigstore provenance.
Without the Skill, download the official installer to a local file, inspect it, and only then execute that file:
curl -fL https://raw.githubusercontent.com/dcc-mcp/dcc-mcp-core/main/scripts/install-cli.sh -o install-cli.sh
cat install-cli.sh
# After reviewing the file:
sh ./install-cli.shInvoke-WebRequest https://raw.githubusercontent.com/dcc-mcp/dcc-mcp-core/main/scripts/install-cli.ps1 -OutFile .\install-cli.ps1
Get-Content -Raw .\install-cli.ps1
# After reviewing the file and subject to the current execution policy:
& .\install-cli.ps1The installer uses the same fixed official source, manifest, and SHA-256 checks as the bundled helper. Never pipe it directly into a shell or bypass the machine's script execution policy.
Pin a release with sh ./install-cli.sh --version v0.19.63 or & .\install-cli.ps1 -Version v0.19.63.
Keep an official installation current with:
dcc-mcp-cli update check
dcc-mcp-cli update applyupdate apply requires the release-manifest SHA-256, verifies the downloaded CLI, and stages it for the next launch. Replacement re-verifies the staged bytes and restarts the CLI with the same arguments. It does not replace a running dcc-mcp-server; update that server in its own environment.
The CLI is also available as a standalone ZIP archive (dcc-mcp-cli-<version>-<platform>.zip) from each GitHub Release.
From PyPI
pip install dcc-mcp-coreFrom Source (requires Rust toolchain)
git clone https://github.com/dcc-mcp/dcc-mcp-core.git
cd dcc-mcp-core
pip install -e .TIP
Building from source requires the Rust toolchain. Install it from rustup.rs. The build is handled by maturin which compiles the Rust core and installs the Python package.
Requirements
- Python: 3.7–3.14. Native CPython 3.7 wheels are gated on Linux and Windows;
py37-liteprovides a sidecar fallback on other platforms. Python 3.8+ usesabi3-py38— see Python 3.7 LTS Policy. - Rust: >= 1.95 (for building from source)
- License: MIT
- Python Dependencies: Zero — everything is in the compiled Rust extension
Quick Start
Operate a live DCC from an agent
With dcc-mcp loaded, use the CLI as the structured control path:
dcc-mcp-cli dcc-types
dcc-mcp-cli list
dcc-mcp-cli search --query "create sphere" --dcc-type maya
dcc-mcp-cli describe <tool-slug>
dcc-mcp-cli call <tool-slug> --json '{"radius":2.0}'Use the slug returned by search; never construct it. If list returns zero instances, follow the Skill's consent-gated setup flow instead of switching to raw DCC scripting or generic GUI automation.
Skills-First: create_skill_server (recommended)
The fastest way to expose scripts as MCP tools. Create a SKILL.md in your script folder, then use create_skill_server to wire everything in one call:
import os
from dcc_mcp_core import create_skill_server, McpHttpConfig
# Point to your skill directories (per-app env var)
os.environ["DCC_MCP_MAYA_SKILL_PATHS"] = "/path/to/my-skills"
# One call: discover skills + start MCP HTTP server
server = create_skill_server("maya", McpHttpConfig())
handle = server.start()
print(f"Maya MCP server at {handle.mcp_url()}")
# The CLI and gateway discover this resolved URL through the shared registry.The local instance port is OS-assigned by default. Pass port=<number> only when an integration explicitly requires a fixed listener.
Or use SkillCatalog directly for more control:
import os
from dcc_mcp_core import SkillCatalog, ToolRegistry
os.environ["DCC_MCP_SKILL_PATHS"] = "/path/to/my-skills"
registry = ToolRegistry()
catalog = SkillCatalog(registry)
discovered = catalog.discover(dcc_name="maya")
print(f"Discovered {discovered} skills")
# Load a skill and inspect the registered tool names
tool_names = catalog.load_skill("maya-geometry")
print(tool_names)See the Skills System guide for writing SKILL.md files and advanced options.
Writing a Minimal SKILL.md
Create a skill in three steps:
# 1. Create the skill directory structure
mkdir -p my-skill/scripts
# 2. Write SKILL.md (agentskills.io top-level fields + metadata.dcc-mcp pointers)
cat > my-skill/SKILL.md << 'EOF'
---
name: my-skill
description: "Does something useful in Maya. Use when user asks to do X."
metadata:
dcc-mcp:
dcc: maya
version: "1.0.0"
search-hint: "keyword1, keyword2, related task"
tools: tools.yaml
---
# My Skill
Instructions for the AI agent on how to use this skill.
EOF
# 3. Declare tools in a sibling file
cat > my-skill/tools.yaml << 'EOF'
tools:
- name: do_thing
description: Do a useful Maya task.
source_file: scripts/do_thing.py
EOF
# 4. Add a script
cat > my-skill/scripts/do_thing.py << 'EOF'
import sys, json
def main():
params = json.loads(sys.stdin.read())
# ... do work ...
print(json.dumps({"success": True, "message": "Done"}))
if __name__ == "__main__":
main()
EOFThen set DCC_MCP_SKILL_PATHS to the parent directory and use create_skill_server or SkillCatalog.discover().
Tool Registry
from dcc_mcp_core import ToolRegistry
registry = ToolRegistry()
registry.register(
name="create_sphere",
description="Creates a sphere in the scene",
category="geometry",
tags=["geometry", "creation"],
dcc="maya",
)
tool = registry.get_action("create_sphere")
print(tool) # dict with tool metadata
maya_tools = registry.list_actions(dcc_name="maya")Action → Tool terminology
In v0.13+, the project renamed "action" → "tool" at the conceptual level. However, some Rust API method names (get_action, list_actions, search_actions) still use "action" for backward compatibility. These are not bugs — they are compatibility aliases.
Tool Results
from dcc_mcp_core import success_result, error_result
result = success_result("Created 5 spheres", prompt="Use modify next", count=5)
print(result.success) # True
print(result.message) # "Created 5 spheres"
print(result.context) # {"count": 5}
err = error_result("Failed", "file_not_found", prompt="Check path")
print(err.success) # FalseEvent Bus
from dcc_mcp_core import EventBus
bus = EventBus()
sid = bus.subscribe("scene.changed", lambda: print("Scene updated!"))
bus.publish("scene.changed")
bus.unsubscribe("scene.changed", sid)MCP HTTP Server
Expose your registry to AI clients (Claude Desktop, etc.) over HTTP in one call:
from dcc_mcp_core import ToolRegistry, McpHttpServer, McpHttpConfig
registry = ToolRegistry()
# ... register tools or load skills ...
config = McpHttpConfig(port=8765)
server = McpHttpServer(registry, config)
handle = server.start()
print(f"MCP server running at {handle.mcp_url()}")
# handle.shutdown() to shut downJob lifecycle notifications
Every tools/call emits SSE notifications on completion (issue #326):
notifications/progress— fires when the call included_meta.progressToken.notifications/$/dcc.jobUpdated— fires on every status transition whileMcpHttpConfig.enable_job_notificationsisTrue(default).notifications/$/dcc.workflowUpdated— emitted by the workflow executor (#348).
Disable the $/dcc.* channels with cfg.enable_job_notifications = False; the spec-mandated progress channel still fires whenever a token is supplied.
Instance-Bound Diagnostics
When multiple DCC instances run side-by-side (two Maya processes, Maya + Blender, etc.), each adapter server should be bound to its own DCC process so diagnostics (screenshot, audit log, metrics) target the right window and PID.
DccServerBase accepts three optional instance-binding kwargs and exposes four dcc_diagnostics__* MCP tools:
from dcc_mcp_core import DccServerBase
class MayaServer(DccServerBase):
def __init__(self, pid: int, window_title: str):
super().__init__(
dcc_name="maya",
builtin_skills_dir=None,
dcc_pid=pid, # owner DCC PID
dcc_window_title=window_title, # fallback match when PID lookup fails
# dcc_window_handle=0x00A1B2, # or pass an HWND directly
)
server = MayaServer(pid=12345, window_title="Autodesk Maya 2024")
handle = server.start() # exposes dcc_diagnostics__screenshot / audit_log /
# tool_metrics / process_status tools bound to
# this Maya instance onlyIf the PID can change at runtime, consult your adapter for how it refreshes diagnostics bindings; dcc_pid on :class:DccServerOptions is resolved at construction time.
from pathlib import Path
from dcc_mcp_core import DccServerBase
from dcc_mcp_core.server import DccServerOptions
skills_dir = Path(__file__).parent / "skills"
opts = DccServerOptions.from_env("maya", skills_dir, dcc_pid=12345)
server = DccServerBase(opts)For low-level servers built around McpHttpServer directly, call register_diagnostic_mcp_tools(server, dcc_name=..., dcc_pid=...) beforeserver.start() — per the "register all actions before start" rule.
Every DccServerBase owns one DiagnosticRuntimeState, available as server.diagnostic_state. If standalone code registers both MCP and IPC diagnostics, create one state and pass it as diagnostic_state= to both registration helpers so two DCC servers in one process never share recorder, capturer, dispatcher, or instance-context caches.
The base server also exposes instance-owned feedback_store, script_execution_context, and checkpoint_store. Its shared feedback registration forwards dcc_feedback__report to gateway /v1/feedback and only mirrors accepted receipts; adapters must not replace it with host-specific handlers. Pass the other components through the corresponding Core helper parameters for persistent script execution or checkpoint tools.
Development Setup
git clone https://github.com/dcc-mcp/dcc-mcp-core.git
cd dcc-mcp-core
# Install with vx (recommended)
vx just install
# Or manual setup
pip install maturin
maturin developRunning Tests
vx just test
vx just lintNext Steps
- Learn about Tools & Registry — the tool registration layer
- Explore Events & Telemetry for lifecycle hooks and lightweight execution metrics
- Check out the Skills System for zero-code script registration
- Expose tools with MCP HTTP Server
- See the Transport Layer for DCC communication
- Understand the Architecture of the 38-member Rust workspace
- Learn Skill Scopes & Policies for trust-based skill management
- Validate tool names with Naming Rules
Troubleshooting
Build/Import Errors
# Symbol in __init__.py but ImportError → rebuild the dev wheel
vx just dev
# Verify import works
python -c "import dcc_mcp_core; print(hasattr(dcc_mcp_core, 'MyNewSymbol'))"
# Verbose cargo build to catch errors
cargo build --workspace --features python-bindings 2>&1 | grep -E "error|warning" | head -30Common Mistakes
| Problem | Solution |
|---|---|
scan_and_load returns wrong results | Always unpack: skills, skipped = scan_and_load(...) — it returns a 2-tuple |
success_result context is empty | Pass kwargs directly: success_result("msg", count=5) — NOT context={"count":5} |
ToolDispatcher.call() not found | Use .dispatch(name, json_str) — there is no .call() method |
McpHttpServer tools not appearing | Register all tools BEFORE server.start() — the server reads the registry at startup |
SkillPolicy ImportError | SkillScope is exported for Python introspection; policy checks still belong on SkillMetadata methods and metadata.dcc-mcp.* policy keys |
| Main-thread dispatch confusion | Prefer HostExecutionBridge / InProcessCallableDispatcher via DccServerOptions; use low-level DeferredExecutor only with /guide/dcc-thread-safety |
| Skill scripts not discovered | Check DCC_MCP_SKILL_PATHS env var and dcc: field in SKILL.md matches your filter |
ToolMeta AttributeError | Rust-only type. Use ToolRegistry.set_tool_enabled() and list_tools_in_group() instead |
AI Agent Best Practices
When building tools for AI agents to consume:
- Design around user workflows, not raw API calls. A tool called
create_characteris better than three separate calls tocreate_joint,bind_skin,apply_animation. - Use
ToolAnnotationsto signal safety properties —read_only_hint=True,destructive_hint=False,idempotent_hint=True— so AI clients make informed choices. - Return stable error codes via
error_result("human-readable message", "machine_code"), put structured diagnostics in_meta, and provide actionable suggestions inprompt. - Use
next-toolsinside siblingtools.yamldeclarations to guide AI agents to follow-up tools (e.g.on-failure: [dcc_diagnostics__screenshot]). - Keep
tools/listsmall by using tool groups withdefault_active=falsefor power-user features. Agents activate groups on demand. - Validate all AI-provided inputs with
ToolValidator.from_schema_json()before execution — never trust LLM output blindly. - Write action-oriented descriptions — describe what the tool does and when to use it in the first sentence. Include specific keywords so
search_skills()can match. Bad: "Helper for geometry." Good: "Create a polygon sphere with configurable radius and subdivisions. Use when the user asks to create a sphere, ball, or round 3D object in Maya." - Always provide
on-failurechains for domain skills — point todcc_diagnostics__screenshotanddcc_diagnostics__audit_logso agents can debug failures automatically. - Declare dependencies under
metadata.dcc-mcp.dependsin every domain skill — ensures diagnostics are loaded before the skill's tools become available. - Tag every skill with
metadata.dcc-mcp.layer— infrastructure, domain, thin-harness, or example. Untagged skills cause routing ambiguity as the catalog grows.
MCP Tool Design Checklist
Before registering a new tool, verify:
- [ ] Single responsibility: Tool does one clear thing (not a kitchen-sink endpoint)
- [ ] Descriptive name: Follows
{skill}__{action}naming; self-explanatory action - [ ] Input schema: JSON Schema with per-parameter descriptions (≤100 chars each)
- [ ] Output schema: Python handlers return
ToolResultEnvelopevia.ok()/.fail()(orskill_success/skill_error) — never hand-rolled dicts - [ ] ToolAnnotations: Set
read_only_hint,destructive_hint,idempotent_hint,open_world_hint - [ ] Error taxonomy: Document error codes in
error_result()with actionablepromptsuggestions - [ ] Follow-up guidance:
next-tools.on-successfor the logical next step;next-tools.on-failurepointing to diagnostics - [ ] Description quality: Includes "what" + "when to use" + keywords for discoverability
Building a DCC Adapter with DccServerBase
DccServerBase is the recommended base class for building DCC adapters. It bundles all the boilerplate that every adapter needs:
from pathlib import Path
from dcc_mcp_core import DccServerBase, DccServerOptions
class BlenderMcpServer(DccServerBase):
def __init__(self, port: int | None = None, **kwargs):
opts = DccServerOptions.from_env(
"blender",
Path(__file__).parent / "skills",
port=port,
**kwargs,
)
super().__init__(options=opts)
def _version_string(self) -> str:
import bpy
return bpy.app.version_string
# That's it — skill management, hot-reload, gateway election are all inherited.
server = BlenderMcpServer(gateway_port=9765)
server.register_builtin_actions() # discover and load skills
server.enable_hot_reload() # optional: auto-reload on file changes
handle = server.start() # returns McpServerHandle
print(f"Running at {handle.mcp_url()}")For zero-boilerplate adapters, use make_start_stop:
from dcc_mcp_core import make_start_stop
start_server, stop_server = make_start_stop(
BlenderMcpServer,
hot_reload_env_var="DCC_MCP_BLENDER_HOT_RELOAD",
)DeferredExecutor — DCC Main-Thread Safety
Many DCCs (Maya, Blender, Houdini) require that API calls execute on the main thread. DeferredExecutor provides a task queue that the DCC event loop polls:
from dcc_mcp_core._core import DeferredExecutor # low-level bridge; prefer HostExecutionBridge for adapters
# Create a queue (capacity = max pending tasks)
executor = DeferredExecutor(capacity=16)
# Submit a callable from any thread (e.g. from MCP HTTP handler)
executor.execute(lambda: maya.cmds.sphere(radius=1.0))
# In the DCC main-loop callback (e.g. Maya's idleCallback, Blender's app.handlers):
executor.poll_pending() # runs all queued callables on the main threadNote:
DeferredExecutoris not yet in the public__init__.py— import directly fromdcc_mcp_core._core. This will be promoted to the public API in a future release.