{"id":49881951,"url":"https://github.com/bluemoonfoundry/daz-mcp-server","last_synced_at":"2026-05-15T15:07:07.868Z","repository":{"id":348069494,"uuid":"1163368034","full_name":"bluemoonfoundry/daz-mcp-server","owner":"bluemoonfoundry","description":"MCP server wrapping the DazScriptServer HTTP plugin for DAZ Studio","archived":false,"fork":false,"pushed_at":"2026-04-10T20:55:30.000Z","size":547,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-10T22:24:00.608Z","etag":null,"topics":["daz","daz-sdk","daz-studio","daz3d","mcp","mcp-server","mcp-servers"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bluemoonfoundry.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-21T14:24:30.000Z","updated_at":"2026-04-09T04:18:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bluemoonfoundry/daz-mcp-server","commit_stats":null,"previous_names":["bluemoonfoundry/daz-mcp-server"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bluemoonfoundry/daz-mcp-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluemoonfoundry%2Fdaz-mcp-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluemoonfoundry%2Fdaz-mcp-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluemoonfoundry%2Fdaz-mcp-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluemoonfoundry%2Fdaz-mcp-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bluemoonfoundry","download_url":"https://codeload.github.com/bluemoonfoundry/daz-mcp-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluemoonfoundry%2Fdaz-mcp-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33070251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["daz","daz-sdk","daz-studio","daz3d","mcp","mcp-server","mcp-servers"],"created_at":"2026-05-15T15:07:06.907Z","updated_at":"2026-05-15T15:07:07.856Z","avatar_url":"https://github.com/bluemoonfoundry.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vangard-daz-mcp\n\n**Version 0.2.0** | MCP Server for DAZ Studio\n\nA [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that exposes DAZ Studio operations to Claude and other MCP clients. Built on [FastMCP](https://github.com/jlowin/fastmcp) and wraps the [DazScriptServer](https://github.com/bluemoonfoundry/daz-script-server) HTTP plugin.\n\n---\n\n## What Is This?\n\nThis MCP server allows Claude (via Claude Desktop or other MCP clients) to control DAZ Studio directly:\n- Query scene information (figures, cameras, lights, spatial positions)\n- Read and modify node properties (transforms, morphs)\n- Discover and apply morphs, including searching by name pattern\n- Traverse and manipulate scene hierarchies (parent/child, skeleton)\n- Apply emotional expressions to characters\n- Coordinate multi-character interactions (look-at, reach-toward, hug, handshake)\n- Execute batch operations (set multiple properties in one call, 5-10x faster)\n- Control cameras and viewport (orbit, frame, presets)\n- Create keyframe animations and export as image sequences\n- Trigger synchronous or asynchronous renders with cancellation support\n- Apply professional lighting presets and cinematography composition rules\n- Browse and query the DAZ content library\n- Save and restore named scene checkpoints\n- Execute arbitrary DazScript code\n- Access comprehensive DazScript documentation and examples\n\nThe server acts as a bridge: **MCP Client** ↔ **vangard-daz-mcp** ↔ **DazScriptServer plugin** ↔ **DAZ Studio**\n\n---\n\n## Prerequisites\n\nBefore using this server, you need:\n\n1. **DAZ Studio 4.5+** installed and running\n2. **DazScriptServer plugin** installed, configured, and active\n   - Download from: https://github.com/bluemoonfoundry/daz-script-server\n   - Plugin must be running on port 18811 (default)\n   - Authentication must be configured (API token)\n3. **Python 3.11+** for running the MCP server\n4. **uv** package manager (recommended) or pip\n\n---\n\n## Installation\n\n### Using uv (Recommended)\n\n```bash\n# Clone the repository\ngit clone https://github.com/bluemoonfoundry/vangard-daz-mcp.git\ncd vangard-daz-mcp\n\n# Install dependencies\nuv sync\n\n# Run the server\nuv run vangard-daz-mcp\n```\n\n### Using pip\n\n```bash\n# Install from source\npip install .\n\n# Run the server\nvangard-daz-mcp\n```\n\n---\n\n## Configuration\n\n### Environment Variables\n\nConfigure the server via environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DAZ_HOST` | `localhost` | DazScriptServer hostname |\n| `DAZ_PORT` | `18811` | DazScriptServer port |\n| `DAZ_TIMEOUT` | `30.0` | Request timeout in seconds (increase for long renders) |\n| `DAZ_API_TOKEN` | *(from file)* | API token for authentication |\n\n### Authentication\n\nThe server automatically reads the API token from `~/.daz3d/dazscriptserver_token.txt` (the file created by DazScriptServer).\n\n**Override with environment variable:**\n```bash\nexport DAZ_API_TOKEN=\"your-token-here\"\n```\n\n**Important:** DazScriptServer must have authentication enabled (default). The MCP server cannot connect without a valid token.\n\n---\n\n## Claude Desktop Configuration\n\nAdd this to your Claude Desktop config file:\n\n**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`\n**Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"daz-studio\": {\n      \"command\": \"uv\",\n      \"args\": [\n        \"--directory\",\n        \"/absolute/path/to/vangard-daz-mcp\",\n        \"run\",\n        \"vangard-daz-mcp\"\n      ],\n      \"env\": {\n        \"DAZ_TIMEOUT\": \"60.0\"\n      }\n    }\n  }\n}\n```\n\n**Note:** Replace `/absolute/path/to/vangard-daz-mcp` with the actual path on your system.\n\nAfter saving the config, restart Claude Desktop. The DAZ Studio tools will appear in Claude's tool palette.\n\n---\n\n## Available Tools\n\n### 📚 Documentation Tools\n\n#### `daz_script_help`\nGet DazScript documentation, examples, and best practices.\n\n**Arguments:**\n- `topic` (string, default `\"overview\"`): Documentation topic to retrieve\n\n**Available Topics:**\n- `overview` - DazScript environment basics\n- `gotchas` - Critical mistakes that cause timeouts or errors\n- `camera` - Camera creation, positioning, and aiming\n- `light` - Light creation, types, and three-point lighting\n- `environment` - Iray environment settings and lighting modes\n- `scene` - Scene management (new, save, load, selection)\n- `properties` - Node properties, transforms, and morphs\n- `content` - Browsing and loading content from library\n- `coordinates` - Coordinate system and positioning reference\n- `posing` - Figure posing, bone hierarchy, morphs vs poses, rotation gotchas\n- `morphs` - Morph discovery, searching, value ranges, and management\n- `hierarchy` - Scene hierarchy, parent-child relationships, parenting operations\n- `interaction` - Multi-character interaction, look-at mechanics, world-space posing\n- `batch` - Batch operations patterns and performance optimization\n- `viewport` - Viewport and camera control, spherical positioning, presets\n- `animation` - Keyframe animation, timeline control, image sequence export\n- `rendering` - Rendering workflows, multi-camera, batch render, animation export\n\n**Returns:** Formatted documentation with examples\n\n**Use when:** Before writing custom DazScript code to learn correct patterns and avoid common mistakes.\n\n**Example:**\n```\ndaz_script_help(\"camera\")  # Get camera documentation\ndaz_script_help(\"gotchas\") # Learn critical gotchas\n```\n\n---\n\n### 🔍 Inspection Tools\n\n#### `daz_status`\nCheck DAZ Studio connectivity and version.\n\n**Returns:**\n```json\n{\n  \"running\": true,\n  \"version\": \"1.3.0\"\n}\n```\n\n**Use when:** Verifying DAZ Studio is running and the connection works.\n\n---\n\n#### `daz_scene_info`\nGet a snapshot of the current scene.\n\n**Returns:**\n```json\n{\n  \"sceneFile\": \"/path/to/scene.duf\",\n  \"selectedNode\": \"Genesis 9\",\n  \"figures\": [\n    {\"name\": \"Genesis9\", \"label\": \"Genesis 9\", \"type\": \"DzFigure\"}\n  ],\n  \"cameras\": [\n    {\"name\": \"Camera\", \"label\": \"Camera 1\"}\n  ],\n  \"lights\": [\n    {\"name\": \"DistantLight\", \"label\": \"Distant Light\", \"type\": \"DzDistantLight\"}\n  ],\n  \"totalNodes\": 3247\n}\n```\n\n**Use when:** You need an overview of what's in the scene (characters, cameras, lights).\n\n**Note:** Does not enumerate all nodes (scenes can have 1000+ nodes). Use `daz_execute` for fine-grained queries.\n\n---\n\n#### `daz_get_node`\nRead all numeric properties of a node by its label or internal name.\n\n**Arguments:**\n- `node_label` (string): Display label or internal name (e.g., \"Genesis 9\")\n\n**Returns:**\n```json\n{\n  \"name\": \"Genesis9\",\n  \"label\": \"Genesis 9\",\n  \"type\": \"DzFigure\",\n  \"properties\": {\n    \"X Translate\": 0.0,\n    \"Y Translate\": 0.0,\n    \"Z Translate\": 0.0,\n    \"X Rotate\": 0.0,\n    \"Y Rotate\": 0.0,\n    \"Z Rotate\": 0.0,\n    \"Scale\": 100.0,\n    \"Head Size\": 0.5\n  }\n}\n```\n\n**Use when:** You need to read transforms, morphs, or other numeric properties on a node.\n\n---\n\n### 🔬 Morph Discovery Tools\n\n#### `daz_list_morphs`\nList all morphs (numeric properties) on a node with their current values.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n- `include_zero` (bool, default `False`): Include morphs with zero values\n\n**Returns:**\n```json\n{\n  \"morphs\": [\n    {\"label\": \"Height\", \"name\": \"Height\", \"value\": 1.05, \"path\": \"Morphs/Body\"},\n    {\"label\": \"Head Size\", \"name\": \"HeadSize\", \"value\": 0.9, \"path\": \"Morphs/Head\"}\n  ],\n  \"count\": 2,\n  \"nodeLabel\": \"Genesis 9\"\n}\n```\n\n**Use when:**\n- Discovering what morphs are available on a figure\n- Checking which morphs are currently active\n- Building morph selection UIs\n- Exploring character customization options\n\n**Example:**\n```\n# List only active morphs (non-zero values)\ndaz_list_morphs(\"Genesis 9\", include_zero=False)\n\n# List ALL available morphs (warning: may return 500-1000+ morphs)\ndaz_list_morphs(\"Genesis 9\", include_zero=True)\n```\n\n**Note:** Genesis figures can have 1000+ morphs. Use `include_zero=False` to see only active morphs, or use `daz_search_morphs` to filter by pattern.\n\n---\n\n#### `daz_search_morphs`\nSearch for morphs matching a name pattern.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n- `pattern` (string): Substring to search for (case-insensitive)\n- `include_zero` (bool, default `False`): Include morphs with zero values\n\n**Returns:**\n```json\n{\n  \"morphs\": [\n    {\"label\": \"Smile\", \"name\": \"Smile\", \"value\": 0.0, \"path\": \"Morphs/Expressions\"},\n    {\"label\": \"Smile Open\", \"name\": \"SmileOpen\", \"value\": 0.0, \"path\": \"Morphs/Expressions\"}\n  ],\n  \"count\": 2,\n  \"pattern\": \"smile\",\n  \"nodeLabel\": \"Genesis 9\"\n}\n```\n\n**Use when:**\n- Finding specific morphs (e.g., all smile morphs, head morphs)\n- Discovering morphs by category or body part\n- Building filtered morph lists\n\n**Example:**\n```\n# Find all smile-related morphs\ndaz_search_morphs(\"Genesis 9\", \"smile\", include_zero=True)\n\n# Find active head morphs only\ndaz_search_morphs(\"Genesis 9\", \"head\", include_zero=False)\n\n# Find all facial expression morphs\ndaz_search_morphs(\"Genesis 9\", \"express\", include_zero=True)\n```\n\n**Common search patterns:**\n- `\"smile\"`, `\"frown\"`, `\"express\"` - Facial expressions\n- `\"head\"`, `\"face\"`, `\"nose\"` - Facial features\n- `\"arm\"`, `\"leg\"`, `\"body\"` - Body parts\n- `\"muscle\"`, `\"tone\"`, `\"fit\"` - Body definition\n- `\"height\"`, `\"scale\"` - Size adjustments\n\n---\n\n### 🌳 Scene Hierarchy Tools\n\n#### `daz_get_node_hierarchy`\nGet complete hierarchy tree for a node with all descendants.\n\n**Arguments:**\n- `node_label` (string): Root node display label or internal name\n- `max_depth` (int, default `10`): Maximum recursion depth (0 = unlimited)\n\n**Returns:**\n```json\n{\n  \"node\": \"Genesis 9\",\n  \"hierarchy\": {\n    \"label\": \"Genesis 9\",\n    \"name\": \"Genesis9\",\n    \"type\": \"DzFigure\",\n    \"children\": [\n      {\n        \"label\": \"hip\",\n        \"name\": \"hip\",\n        \"type\": \"DzBone\",\n        \"children\": [...]\n      }\n    ]\n  },\n  \"totalDescendants\": 127\n}\n```\n\n**Use when:**\n- Understanding skeleton structure\n- Exploring bone relationships\n- Mapping complex scene hierarchies\n- Finding all descendants of a node\n\n**Example:**\n```\n# Get skeleton hierarchy with depth limit\ndaz_get_node_hierarchy(\"Genesis 9\", max_depth=3)\n\n# Get full hierarchy (warning: Genesis 9 has 100+ bones)\ndaz_get_node_hierarchy(\"Genesis 9\", max_depth=0)\n```\n\n---\n\n#### `daz_list_children`\nList direct children of a node.\n\n**Arguments:**\n- `node_label` (string): Parent node display label or internal name\n\n**Returns:**\n```json\n{\n  \"node\": \"hip\",\n  \"children\": [\n    {\"label\": \"pelvis\", \"name\": \"pelvis\", \"type\": \"DzBone\"},\n    {\"label\": \"lThighBend\", \"name\": \"lThighBend\", \"type\": \"DzBone\"},\n    {\"label\": \"rThighBend\", \"name\": \"rThighBend\", \"type\": \"DzBone\"}\n  ],\n  \"count\": 3\n}\n```\n\n**Use when:**\n- Exploring hierarchy one level at a time\n- Checking if a node has children\n- Building custom tree structures\n\n**Example:**\n```\n# List children of Genesis 9 root\ndaz_list_children(\"Genesis 9\")\n\n# Check if node has children\nresult = daz_list_children(\"Camera 1\")\nif result[\"count\"] == 0:\n    print(\"No children\")\n```\n\n---\n\n#### `daz_get_parent`\nGet parent node of a node.\n\n**Arguments:**\n- `node_label` (string): Child node display label or internal name\n\n**Returns:**\n```json\n{\n  \"node\": \"lHand\",\n  \"parent\": {\n    \"label\": \"lForearmBend\",\n    \"name\": \"lForearmBend\",\n    \"type\": \"DzBone\"\n  }\n}\n```\n\nReturns `null` for parent if node is a root (no parent).\n\n**Use when:**\n- Traversing hierarchy upward\n- Finding what contains a node\n- Checking if node is a root\n\n**Example:**\n```\n# Get parent of a bone\nresult = daz_get_parent(\"lHand\")\nprint(f\"Parent: {result['parent']['label']}\")\n\n# Check if node is root\nresult = daz_get_parent(\"Genesis 9\")\nif result[\"parent\"] is None:\n    print(\"This is a root node\")\n```\n\n---\n\n#### `daz_set_parent`\nSet parent of a node (parenting operation).\n\n**Arguments:**\n- `node_label` (string): Node to parent\n- `parent_label` (string): New parent node\n- `maintain_world_transform` (bool, default `True`): If true, adjust local transform to keep same world position\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"node\": \"Sword\",\n  \"newParent\": \"rHand\",\n  \"previousParent\": null\n}\n```\n\n**Use when:**\n- Attaching props to figures (e.g., weapon to hand)\n- Parenting cameras to nodes\n- Reorganizing scene hierarchy\n- Attaching clothing to bones\n\n**Example:**\n```\n# Attach sword to right hand (maintains position)\ndaz_set_parent(\"Sword\", \"rHand\", maintain_world_transform=True)\n\n# Parent camera to figure (follows figure)\ndaz_set_parent(\"Camera 1\", \"Genesis 9\", maintain_world_transform=True)\n\n# Attach bracelet to forearm\ndaz_set_parent(\"Bracelet\", \"lForearmBend\", maintain_world_transform=True)\n```\n\n**Note:** When `maintain_world_transform=True`, the node's world position stays the same, but local transform values (X/Y/Z Translate, Rotate) change to account for the new parent's transform.\n\n---\n\n### ⚡ Batch Operations\n\nBatch operations allow you to modify multiple nodes or properties in a single call, significantly improving performance. Each operation has individual error handling, so failures don't abort the entire batch.\n\n**Performance benefits:**\n- Single script call (all operations execute in one round-trip)\n- No HTTP/network overhead between operations\n- 5-10x faster than individual calls for typical batches\n- Individual error handling without aborting the batch\n\n**Common use cases:**\n- Applying facial expressions (multiple morphs at once)\n- Configuring lighting setups (multiple light properties)\n- Moving/rotating groups of props together\n- Showing/hiding groups of nodes for scene management\n- Resetting multiple cameras or lights to default values\n\n#### `daz_batch_set_properties`\nSet multiple properties on one or more nodes in a single call.\n\n**Arguments:**\n- `operations` (array): List of operation objects, each containing:\n  - `nodeLabel` (string): Display label of the node\n  - `propertyName` (string): Property label or internal name\n  - `value` (float): New value for the property\n\n**Returns:**\n```json\n{\n  \"results\": [\n    {\"success\": true, \"node\": \"Genesis 9\", \"property\": \"X Translate\", \"value\": 100},\n    {\"success\": false, \"node\": \"Missing\", \"error\": \"Node not found: Missing\"}\n  ],\n  \"successCount\": 1,\n  \"failureCount\": 1,\n  \"total\": 2\n}\n```\n\n**Use when:** Setting 3+ properties, applying facial expressions, configuring scene presets.\n\n**Example:**\n```\n# Apply \"surprised\" facial expression\ndaz_batch_set_properties([\n    {\"nodeLabel\": \"Genesis 9\", \"propertyName\": \"PHMEyesWide\", \"value\": 0.8},\n    {\"nodeLabel\": \"Genesis 9\", \"propertyName\": \"PHMBrowsUp\", \"value\": 0.7},\n    {\"nodeLabel\": \"Genesis 9\", \"propertyName\": \"PHMMouthOpen\", \"value\": 0.4}\n])\n\n# Configure lighting setup\ndaz_batch_set_properties([\n    {\"nodeLabel\": \"Key Light\", \"propertyName\": \"Flux\", \"value\": 2000},\n    {\"nodeLabel\": \"Fill Light\", \"propertyName\": \"Flux\", \"value\": 800},\n    {\"nodeLabel\": \"Rim Light\", \"propertyName\": \"Flux\", \"value\": 2500}\n])\n```\n\n**Performance:** Setting 10 morphs via batch is ~5-10x faster than 10 individual `daz_set_property` calls.\n\n---\n\n#### `daz_batch_transform`\nApply the same transform properties to multiple nodes.\n\n**Arguments:**\n- `node_labels` (array): List of node display labels to transform\n- `transforms` (object): Dictionary of property names to values (e.g., `{\"XTranslate\": 50, \"YRotate\": 45}`)\n\n**Returns:**\n```json\n{\n  \"results\": [\n    {\"success\": true, \"node\": \"Prop1\", \"applied\": [\"X Translate\", \"Y Rotate\"]},\n    {\"success\": false, \"node\": \"Missing\", \"error\": \"Node not found: Missing\"}\n  ],\n  \"successCount\": 1,\n  \"failureCount\": 1,\n  \"total\": 2\n}\n```\n\n**Use when:** Moving, rotating, or scaling multiple objects by the same amount.\n\n**Example:**\n```\n# Move multiple props to the right\ndaz_batch_transform(\n    [\"Chair\", \"Table\", \"Lamp\"],\n    {\"XTranslate\": 100}\n)\n\n# Rotate and scale multiple objects\ndaz_batch_transform(\n    [\"Prop1\", \"Prop2\", \"Prop3\"],\n    {\"YRotate\": 45, \"Scale\": 1.2}\n)\n\n# Reset rotation for all cameras\ndaz_batch_transform(\n    [\"Camera 1\", \"Camera 2\", \"Camera 3\"],\n    {\"XRotate\": 0, \"YRotate\": 0, \"ZRotate\": 0}\n)\n```\n\n**Note:** Only properties that exist on each node are applied. Missing properties are silently skipped.\n\n---\n\n#### `daz_batch_visibility`\nShow or hide multiple nodes in the viewport and renders.\n\n**Arguments:**\n- `node_labels` (array): List of node display labels to modify\n- `visible` (bool, default `True`): True to show nodes, False to hide them\n\n**Returns:**\n```json\n{\n  \"results\": [\n    {\"success\": true, \"node\": \"Ground\", \"visible\": false},\n    {\"success\": true, \"node\": \"Sky Dome\", \"visible\": false}\n  ],\n  \"successCount\": 2,\n  \"failureCount\": 0,\n  \"total\": 2\n}\n```\n\n**Use when:** Scene management, testing configurations, optimizing render times.\n\n**Example:**\n```\n# Hide all cameras\ndaz_batch_visibility([\"Camera 1\", \"Camera 2\", \"Camera 3\"], visible=False)\n\n# Hide environment elements for character close-up\ndaz_batch_visibility([\"Ground\", \"Sky Dome\", \"Background\"], visible=False)\n\n# Show all weapons\ndaz_batch_visibility([\"Sword\", \"Shield\", \"Helmet\"], visible=True)\n```\n\n**Note:** Hidden nodes remain in the scene but are not visible in viewport or renders.\n\n---\n\n#### `daz_batch_select`\nSelect multiple nodes in the DAZ Studio scene.\n\n**Arguments:**\n- `node_labels` (array): List of node display labels to select\n- `add_to_selection` (bool, default `False`): If True, add to current selection; if False, replace current selection\n\n**Returns:**\n```json\n{\n  \"selected\": [\"Genesis 9\", \"Genesis 8 Female\"],\n  \"count\": 2,\n  \"total\": 2\n}\n```\n\n**Use when:** Selecting groups of nodes for inspection or operations.\n\n**Example:**\n```\n# Select multiple characters\ndaz_batch_select([\"Genesis 9\", \"Genesis 8 Female\"])\n\n# Add props to current selection\ndaz_batch_select([\"Sword\", \"Shield\"], add_to_selection=True)\n\n# Select all lights\ndaz_batch_select([\"Spot Light 1\", \"Distant Light 1\", \"Point Light 1\"])\n```\n\n**Note:** Nodes that don't exist are silently skipped. Returns count of successful selections.\n\n---\n\n### 📷 Viewport and Camera Control\n\nViewport control tools enable programmatic camera positioning, framing, and preset management for automated scene photography and consistent camera angles.\n\n**Key capabilities:**\n- Switch active viewport camera\n- Position camera using spherical coordinates (orbit around target)\n- Auto-frame camera to show objects (calculates bounding box)\n- Save/load camera positions as presets (JSON-serializable)\n- Reusable camera angles across scenes\n\n#### `daz_set_active_camera`\nSet which camera is active in the DAZ Studio viewport.\n\n**Arguments:**\n- `camera_label` (string): Display label of the camera to activate\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"previousCamera\": \"Perspective View\"\n}\n```\n\n**Use when:** Switching between predefined camera angles, previewing from multiple viewpoints.\n\n**Example:**\n```\n# Switch to specific camera\ndaz_set_active_camera(\"Camera 1\")\n\n# Switch back to default\ndaz_set_active_camera(\"Perspective View\")\n```\n\n---\n\n#### `daz_orbit_camera_around`\nPosition camera orbiting around a target node at specified angle and distance.\n\n**Arguments:**\n- `camera_label` (string): Camera to position\n- `target_label` (string): Target node to orbit around\n- `distance` (float, default `200.0`): Distance from target in cm\n- `angle_horizontal` (float, default `45.0`): Horizontal angle in degrees (0=front/+Z, 90=right/+X)\n- `angle_vertical` (float, default `15.0`): Vertical angle in degrees (positive=above)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"target\": \"Genesis 9\",\n  \"position\": {\"x\": 141.4, \"y\": 151.8, \"z\": 141.4},\n  \"targetPosition\": {\"x\": 0, \"y\": 100, \"z\": 0}\n}\n```\n\n**Use when:** Character photography, product shots, turntable animations, establishing camera angles.\n\n**Example:**\n```\n# Front 3/4 view (classic portrait angle)\ndaz_orbit_camera_around(\"Camera 1\", \"Genesis 9\",\n                        distance=200, angle_horizontal=45, angle_vertical=15)\n\n# Side view from left\ndaz_orbit_camera_around(\"Camera 1\", \"Genesis 9\",\n                        distance=150, angle_horizontal=-90, angle_vertical=0)\n\n# Bird's eye view\ndaz_orbit_camera_around(\"Camera 1\", \"Genesis 9\",\n                        distance=300, angle_horizontal=0, angle_vertical=60)\n\n# Dramatic low angle\ndaz_orbit_camera_around(\"Camera 1\", \"Genesis 9\",\n                        distance=180, angle_horizontal=25, angle_vertical=-20)\n```\n\n**Angle reference:**\n- Horizontal: 0°=front(+Z), 90°=right(+X), 180°=back(-Z), -90°=left(-X)\n- Vertical: positive=above horizon, negative=below\n\n**Distance guidelines** (170cm tall figure):\n- Full body: 350-450cm\n- Portrait: 80-120cm\n- Face close-up: 30-50cm\n\n---\n\n#### `daz_frame_camera_to_node`\nFrame camera to show a node by positioning at calculated distance.\n\n**Arguments:**\n- `camera_label` (string): Camera to position\n- `node_label` (string): Node to frame\n- `distance` (float, optional): Distance from node center in cm. If not specified, auto-calculated as 2.5x largest bounding box dimension.\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"node\": \"Genesis 9\",\n  \"position\": {\"x\": 0, \"y\": 100, \"z\": 450},\n  \"nodeCenter\": {\"x\": 0, \"y\": 100, \"z\": 0},\n  \"nodeSize\": {\"x\": 50, \"y\": 170, \"z\": 40}\n}\n```\n\n**Use when:** Auto-framing objects of varying sizes, consistent framing across scenes.\n\n**Example:**\n```\n# Frame character (auto distance)\ndaz_frame_camera_to_node(\"Camera 1\", \"Genesis 9\")\n\n# Frame prop with specific distance\ndaz_frame_camera_to_node(\"Camera 1\", \"Sword\", distance=50)\n\n# Close-up on head\ndaz_frame_camera_to_node(\"Camera 1\", \"head\", distance=30)\n```\n\n**Note:** Camera is positioned in front (+Z) and aimed at node's bounding box center. Auto-calculated distance is 2.5x the largest dimension.\n\n---\n\n#### `daz_save_camera_preset`\nSave camera position and rotation as preset data.\n\n**Arguments:**\n- `camera_label` (string): Camera to save\n\n**Returns:**\n```json\n{\n  \"preset\": {\n    \"label\": \"Camera 1\",\n    \"transforms\": {\n      \"XTranslate\": 0, \"YTranslate\": 100, \"ZTranslate\": 300,\n      \"XRotate\": -10, \"YRotate\": 0, \"ZRotate\": 0,\n      \"XScale\": 1.0, \"YScale\": 1.0, \"ZScale\": 1.0\n    }\n  }\n}\n```\n\n**Use when:** Saving reusable camera angles, sharing camera positions across projects.\n\n**Example:**\n```python\n# Save camera position\npreset = daz_save_camera_preset(\"Camera 1\")\n\n# Store to file\nimport json\nwith open(\"portrait_camera.json\", \"w\") as f:\n    json.dump(preset, f)\n```\n\n**Note:** Preset data is JSON-serializable and can be applied to any camera.\n\n---\n\n#### `daz_load_camera_preset`\nRestore camera position and rotation from preset data.\n\n**Arguments:**\n- `camera_label` (string): Camera to modify\n- `preset` (dict): Preset dictionary from `daz_save_camera_preset()` (must contain `transforms` key)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"applied\": [\"XTranslate\", \"YTranslate\", \"ZTranslate\", \"XRotate\", \"YRotate\", \"ZRotate\"]\n}\n```\n\n**Use when:** Restoring saved camera positions, applying same angle to multiple cameras.\n\n**Example:**\n```python\n# Load preset from file\nimport json\nwith open(\"portrait_camera.json\") as f:\n    preset = json.load(f)\n\n# Apply to camera\ndaz_load_camera_preset(\"Camera 1\", preset[\"preset\"])\n\n# Apply same preset to multiple cameras\nfor cam in [\"Camera 1\", \"Camera 2\", \"Camera 3\"]:\n    daz_load_camera_preset(cam, preset[\"preset\"])\n```\n\n**Note:** Preset can be applied to any camera, not just the original. Useful for synchronizing multiple cameras.\n\n---\n\n### 🎬 Animation System\n\nAnimation tools enable keyframe-based property animation. Set keyframes at specific frames, and DAZ Studio interpolates smoothly between them. Supports animating any numeric property (transforms, morphs, lights, cameras).\n\n**Key capabilities:**\n- Set/get/remove keyframes on properties\n- Timeline control (current frame, frame range)\n- Export animations as image sequences\n- Copy and offset animations between properties\n- Inspect keyframe data programmatically\n\n**Common use cases:**\n- Character animation (walk cycles, gestures, facial expressions)\n- Camera animation (dolly, pan, zoom)\n- Product turntables (360° rotation)\n- Morph animations (smile fade, eye blink)\n- Multi-character choreography\n\n#### `daz_set_keyframe`\nSet a keyframe on a property at specified frame.\n\n**Arguments:**\n- `node_label` (string): Node display label\n- `property_name` (string): Property label or internal name\n- `frame` (int): Frame number (0-based)\n- `value` (float): Value at this frame\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"node\": \"Genesis 9\",\n  \"property\": \"X Translate\",\n  \"frame\": 0,\n  \"value\": 0.0\n}\n```\n\n**Use when:** Creating animations, defining key poses.\n\n**Example:**\n```\n# Animate movement (0 to 100cm over 30 frames)\ndaz_set_keyframe(\"Genesis 9\", \"XTranslate\", frame=0, value=0)\ndaz_set_keyframe(\"Genesis 9\", \"XTranslate\", frame=30, value=100)\n\n# Animate rotation (0 to 90 degrees)\ndaz_set_keyframe(\"Genesis 9\", \"YRotate\", frame=0, value=0)\ndaz_set_keyframe(\"Genesis 9\", \"YRotate\", frame=60, value=90)\n\n# Animate morph (fade in smile)\ndaz_set_keyframe(\"Genesis 9\", \"PHMSmile\", frame=0, value=0)\ndaz_set_keyframe(\"Genesis 9\", \"PHMSmile\", frame=15, value=0.8)\n```\n\n**Note:** DAZ Studio interpolates between keyframes automatically. Setting keyframe at existing frame updates the value.\n\n---\n\n#### `daz_get_keyframes`\nGet all keyframes for a property.\n\n**Arguments:**\n- `node_label` (string): Node display label\n- `property_name` (string): Property label or internal name\n\n**Returns:**\n```json\n{\n  \"keyframes\": [\n    {\"frame\": 0, \"value\": 0.0},\n    {\"frame\": 30, \"value\": 100.0}\n  ],\n  \"count\": 2\n}\n```\n\n**Use when:** Inspecting animations, copying keyframes, checking if property is animated.\n\n**Example:**\n```python\n# Get keyframes\nresult = daz_get_keyframes(\"Genesis 9\", \"XTranslate\")\nfor kf in result['keyframes']:\n    print(f\"Frame {kf['frame']}: {kf['value']}\")\n\n# Copy keyframes to another node\nfor kf in result['keyframes']:\n    daz_set_keyframe(\"Genesis 8\", \"XTranslate\", kf['frame'], kf['value'])\n```\n\n---\n\n#### `daz_remove_keyframe`\nRemove a keyframe at specified frame.\n\n**Arguments:**\n- `node_label` (string): Node display label\n- `property_name` (string): Property label or internal name\n- `frame` (int): Frame number\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"node\": \"Genesis 9\",\n  \"property\": \"X Translate\",\n  \"frame\": 15,\n  \"removed\": true\n}\n```\n\n**Use when:** Removing specific keyframes, editing animation timing.\n\n**Example:**\n```\n# Remove keyframe\ndaz_remove_keyframe(\"Genesis 9\", \"XTranslate\", frame=15)\n```\n\n**Note:** Returns `removed: false` if no keyframe exists at that frame (not an error).\n\n---\n\n#### `daz_clear_animation`\nRemove all keyframes from a property.\n\n**Arguments:**\n- `node_label` (string): Node display label\n- `property_name` (string): Property label or internal name\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"node\": \"Genesis 9\",\n  \"property\": \"X Translate\",\n  \"removed\": 5\n}\n```\n\n**Use when:** Clearing animations, resetting properties to static state.\n\n**Example:**\n```python\n# Clear animation\nresult = daz_clear_animation(\"Genesis 9\", \"XTranslate\")\nprint(f\"Removed {result['removed']} keyframes\")\n\n# Clear all transform animations\nfor prop in [\"XTranslate\", \"YTranslate\", \"ZTranslate\", \"XRotate\", \"YRotate\", \"ZRotate\"]:\n    daz_clear_animation(\"Genesis 9\", prop)\n```\n\n**Note:** More efficient than removing keyframes individually.\n\n---\n\n#### `daz_set_frame`\nSet current animation frame.\n\n**Arguments:**\n- `frame` (int): Frame number to move to\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"frame\": 30,\n  \"previousFrame\": 0\n}\n```\n\n**Use when:** Previewing animation, rendering specific frames.\n\n**Example:**\n```python\n# Jump to frame 30\ndaz_set_frame(30)\n\n# Render all frames\ninfo = daz_get_animation_info()\nfor frame in range(info['startFrame'], info['endFrame'] + 1):\n    daz_set_frame(frame)\n    daz_render(output_path=f\"frame_{frame:04d}.png\")\n```\n\n**Note:** Scene updates to show animated state at the frame.\n\n---\n\n#### `daz_set_frame_range`\nSet animation frame range (start and end).\n\n**Arguments:**\n- `start_frame` (int): First frame (typically 0)\n- `end_frame` (int): Last frame\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"startFrame\": 0,\n  \"endFrame\": 119,\n  \"previousStart\": 0,\n  \"previousEnd\": 30\n}\n```\n\n**Use when:** Defining animation length before creating keyframes.\n\n**Example:**\n```\n# 4-second animation (120 frames at 30fps)\ndaz_set_frame_range(0, 119)\n\n# 10-second animation\ndaz_set_frame_range(0, 299)\n```\n\n**Note:** Frame range is inclusive (frames 0-119 = 120 frames). Duration = (end - start + 1) / fps.\n\n---\n\n#### `daz_get_animation_info`\nGet animation timeline info (current frame, range, fps).\n\n**Returns:**\n```json\n{\n  \"currentFrame\": 0,\n  \"startFrame\": 0,\n  \"endFrame\": 119,\n  \"fps\": 30.0,\n  \"totalFrames\": 120,\n  \"durationSeconds\": 4.0\n}\n```\n\n**Use when:** Checking timeline state before rendering, calculating duration.\n\n**Example:**\n```python\n# Get timeline info\ninfo = daz_get_animation_info()\nprint(f\"Animation: {info['durationSeconds']} seconds ({info['totalFrames']} frames)\")\n\n# Render entire animation\nfor frame in range(info['startFrame'], info['endFrame'] + 1):\n    daz_set_frame(frame)\n    daz_render(output_path=f\"frame_{frame:04d}.png\")\n```\n\n**Note:** FPS is typically 30 in DAZ Studio.\n\n---\n\n### 🎥 Advanced Rendering Control\n\nAdvanced rendering tools provide programmatic control for multi-camera rendering, animation export, and batch rendering operations.\n\n**Key capabilities:**\n- Render from specific camera without changing viewport\n- Batch render from multiple cameras\n- Export animations as image sequences\n- Query render settings\n- Automated rendering workflows\n\n**Common use cases:**\n- Multi-angle product shots (front, side, top, perspective)\n- Character turntables (8-16 camera angles)\n- Animation export (frame-by-frame image sequences)\n- Test renders from multiple angles\n- Multi-camera animation rendering\n\n#### `daz_render_with_camera`\nRender from specific camera without changing active viewport camera.\n\n**Arguments:**\n- `camera_label` (string): Camera to render from\n- `output_path` (string, optional): Output file path (if not specified, renders to viewport)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"outputPath\": \"/path/to/render.png\"\n}\n```\n\n**Use when:** Multi-camera batch renders, testing camera angles without disrupting viewport.\n\n**Example:**\n```python\n# Render from specific camera\ndaz_render_with_camera(\"Camera 1\", output_path=\"/renders/cam1.png\")\n\n# Render from multiple cameras\nfor cam in [\"Front\", \"Side\", \"Top\"]:\n    daz_render_with_camera(cam, output_path=f\"/renders/{cam}.png\")\n```\n\n**Note:** Viewport camera remains unchanged. Previous render camera is restored automatically.\n\n---\n\n#### `daz_get_render_settings`\nGet current render settings and configuration.\n\n**Returns:**\n```json\n{\n  \"renderToFile\": true,\n  \"outputPath\": \"/path/to/output.png\",\n  \"currentCamera\": \"Camera 1\",\n  \"aspectRatio\": 1.777,\n  \"aspectWidth\": 16,\n  \"aspectHeight\": 9\n}\n```\n\n**Use when:** Verifying render configuration before batch operations, debugging render issues.\n\n**Example:**\n```python\n# Check render settings\nsettings = daz_get_render_settings()\nprint(f\"Render camera: {settings['currentCamera']}\")\nprint(f\"Aspect: {settings['aspectWidth']}x{settings['aspectHeight']}\")\n\n# Verify configuration\nif not settings['renderToFile']:\n    print(\"Warning: Render configured for viewport, not file\")\n```\n\n---\n\n#### `daz_batch_render_cameras`\nRender from multiple cameras in sequence.\n\n**Arguments:**\n- `cameras` (list[string]): List of camera labels\n- `output_dir` (string): Output directory\n- `base_filename` (string, default `\"render\"`): Base filename (camera name is appended)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"rendered\": [\n    {\"camera\": \"Front\", \"outputPath\": \"/renders/product_Front.png\"},\n    {\"camera\": \"Side\", \"outputPath\": \"/renders/product_Side.png\"}\n  ],\n  \"total\": 2\n}\n```\n\n**Use when:** Product photography, turntable renders, multi-angle test renders.\n\n**Example:**\n```python\n# Render from multiple cameras\ndaz_batch_render_cameras(\n    cameras=[\"Front\", \"Side\", \"Top\", \"Perspective\"],\n    output_dir=\"/renders\",\n    base_filename=\"product\"\n)\n# Generates: product_Front.png, product_Side.png, product_Top.png, product_Perspective.png\n\n# Turntable (8 cameras around character)\ncameras = [f\"Cam_{angle}\" for angle in [0, 45, 90, 135, 180, 225, 270, 315]]\ndaz_batch_render_cameras(cameras, \"/renders/turntable\", \"angle\")\n```\n\n**Note:** Camera names in filenames have non-alphanumeric chars replaced with underscores. Previous render camera is restored after batch.\n\n---\n\n#### `daz_render_animation`\nRender animation frame range as image sequence.\n\n**Arguments:**\n- `output_dir` (string): Output directory\n- `start_frame` (int, optional): First frame (default: animation range start)\n- `end_frame` (int, optional): Last frame (default: animation range end)\n- `filename_pattern` (string, default `\"frame\"`): Filename pattern (frame number appended)\n- `camera` (string, optional): Camera to render from (default: current render camera)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"rendered\": [\n    {\"frame\": 0, \"outputPath\": \"/animation/frame_0000.png\"},\n    {\"frame\": 1, \"outputPath\": \"/animation/frame_0001.png\"}\n  ],\n  \"total\": 120,\n  \"frames\": {\"start\": 0, \"end\": 119}\n}\n```\n\n**Use when:** Exporting animations, creating video sequences.\n\n**Example:**\n```python\n# Render entire animation (uses animation range)\ndaz_render_animation(output_dir=\"/animation\")\n# Generates: frame_0000.png, frame_0001.png, ..., frame_0119.png\n\n# Render specific frame range\ndaz_render_animation(\n    output_dir=\"/animation/clip\",\n    start_frame=30,\n    end_frame=60,\n    filename_pattern=\"clip\"\n)\n\n# Render animation from specific camera\ndaz_render_animation(\n    output_dir=\"/animation\",\n    camera=\"Camera 1\"\n)\n\n# Convert to video (using ffmpeg)\n# ffmpeg -framerate 30 -i frame_%04d.png -c:v libx264 -pix_fmt yuv420p output.mp4\n```\n\n**Note:** Frame numbers zero-padded to 4 digits (0000-9999). Timeline position and render camera restored after completion.\n\n---\n\n### 📐 Spatial Query Tools\n\nThese tools let you query the world-space position, size, and relationships of scene nodes.\n\n#### `daz_get_world_position`\nGet world-space position, local position, rotation, and scale of a node.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n\n**Returns:**\n```json\n{\n  \"node\": \"Genesis 9\",\n  \"worldPosition\": {\"x\": 0, \"y\": 0, \"z\": 0},\n  \"localPosition\": {\"x\": 0, \"y\": 0, \"z\": 0},\n  \"rotation\": {\"x\": 0, \"y\": 0, \"z\": 0},\n  \"scale\": {\"x\": 1, \"y\": 1, \"z\": 1}\n}\n```\n\n**Use when:** Finding exact world coordinates of a character or prop before placing another object relative to it.\n\n---\n\n#### `daz_get_bounding_box`\nGet bounding box (min/max corners, center, dimensions) of a node.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n\n**Returns:**\n```json\n{\n  \"node\": \"Genesis 9\",\n  \"min\": {\"x\": -30, \"y\": 0, \"z\": -15},\n  \"max\": {\"x\": 30, \"y\": 170, \"z\": 15},\n  \"center\": {\"x\": 0, \"y\": 85, \"z\": 0},\n  \"width\": 60, \"height\": 170, \"depth\": 30\n}\n```\n\n**Use when:** Auto-framing cameras, checking object sizes, placing objects on surfaces.\n\n---\n\n#### `daz_calculate_distance`\nCalculate distance and direction vector between two nodes.\n\n**Arguments:**\n- `from_label` (string): Source node\n- `to_label` (string): Target node\n\n**Returns:**\n```json\n{\n  \"from\": \"Alice\",\n  \"to\": \"Bob\",\n  \"distance\": 120.5,\n  \"direction\": {\"x\": 0.707, \"y\": 0, \"z\": 0.707}\n}\n```\n\n**Use when:** Checking if two characters are within interaction range, positioning props relative to figures.\n\n---\n\n#### `daz_get_spatial_relationship`\nNatural-language spatial relationship between two nodes.\n\n**Arguments:**\n- `from_label` (string): Reference node\n- `to_label` (string): Target node\n\n**Returns:**\n```json\n{\n  \"from\": \"Camera 1\",\n  \"to\": \"Genesis 9\",\n  \"distance\": 300.0,\n  \"direction\": \"in front of\",\n  \"angle\": 5.2,\n  \"overlap\": false\n}\n```\n\n**Use when:** Describing scene layout in natural language, verifying camera placement.\n\n---\n\n#### `daz_check_overlap`\nCheck if two nodes have overlapping bounding boxes.\n\n**Arguments:**\n- `node1_label` (string): First node\n- `node2_label` (string): Second node\n\n**Returns:**\n```json\n{\n  \"overlapping\": true,\n  \"penetrationDepth\": {\"x\": 2.1, \"y\": 0, \"z\": 0}\n}\n```\n\n**Use when:** Detecting interpenetration between characters, validating poses before rendering.\n\n---\n\n### 🔬 Property Introspection Tools\n\n#### `daz_inspect_properties`\nList all properties on a node, optionally filtered by type.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n- `filter_type` (string, default `\"all\"`): One of `\"all\"`, `\"numeric\"`, `\"transform\"`, `\"morph\"`, `\"bool\"`, `\"string\"`\n\n**Returns:**\n```json\n{\n  \"node\": \"Spot Light 1\",\n  \"properties\": [\n    {\"label\": \"Flux\", \"name\": \"Flux\", \"type\": \"numeric\", \"value\": 1500},\n    {\"label\": \"Shadow Softness\", \"name\": \"Shadow Softness\", \"type\": \"numeric\", \"value\": 0.5}\n  ],\n  \"count\": 2\n}\n```\n\n**Use when:** Discovering what properties are settable on a node (lights, cameras, props).\n\n**Example:**\n```\n# List all numeric properties on a spotlight\ndaz_inspect_properties(\"Spot Light 1\", filter_type=\"numeric\")\n\n# List transform properties only\ndaz_inspect_properties(\"Genesis 9\", filter_type=\"transform\")\n```\n\n---\n\n#### `daz_get_property_metadata`\nGet detailed metadata (min, max, default, type, path) for a single property.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n- `property_name` (string): Property label or internal name\n\n**Returns:**\n```json\n{\n  \"node\": \"Spot Light 1\",\n  \"property\": \"Flux\",\n  \"type\": \"numeric\",\n  \"value\": 1500,\n  \"default\": 1500,\n  \"min\": 0,\n  \"max\": 100000,\n  \"path\": \"General/Luminous Flux\"\n}\n```\n\n**Use when:** Finding valid ranges before setting a property, validating property names.\n\n---\n\n#### `daz_validate_script`\nStatic analysis of DazScript code for known anti-patterns. Does not require a DAZ Studio connection.\n\n**Arguments:**\n- `script` (string): DazScript source code to analyze\n\n**Returns:**\n```json\n{\n  \"valid\": false,\n  \"issues\": [\n    {\"severity\": \"error\", \"line\": 3, \"message\": \"Bare return at top level — wrap in IIFE\"},\n    {\"severity\": \"warning\", \"line\": 7, \"message\": \"getElementID() is not a function — use .elementID property\"}\n  ],\n  \"issueCount\": 2\n}\n```\n\n**Use when:** Before running a custom script via `daz_execute`, to catch common mistakes early.\n\n---\n\n### 💡 Lighting Preset Tools\n\n#### `daz_apply_lighting_preset`\nCreate a professional lighting setup in one command.\n\n**Arguments:**\n- `preset` (string): Lighting preset name\n- `subject_label` (string): Node to light (preset positions lights relative to subject's bounding box)\n\n**Presets:**\n- `three-point` — Key (front-right) + Fill (front-left) + Rim (back). General-purpose.\n- `rembrandt` — Key (45° side, high) + dim Fill. Dramatic portrait.\n- `butterfly` — Key (directly front, high). Glamour/beauty lighting.\n- `split` — Key (90° side). Half face lit, half in shadow. Moody.\n- `loop` — Key (35° side) + Fill + Rim. Natural-looking portrait.\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"preset\": \"three-point\",\n  \"subject\": \"Genesis 9\",\n  \"lights\": [\"Key Light\", \"Fill Light\", \"Rim Light\"]\n}\n```\n\nAll presets: aim lights at the subject's face height, set environment mode to Scene Only, and remove existing lights with the same names first.\n\n**Use when:** Setting up a scene for rendering without manually positioning individual lights.\n\n**Example:**\n```\n# Classic portrait lighting\ndaz_apply_lighting_preset(\"three-point\", \"Genesis 9\")\n\n# Dramatic moody lighting\ndaz_apply_lighting_preset(\"rembrandt\", \"Genesis 9\")\n```\n\n---\n\n#### `daz_validate_scene`\nValidate scene quality for rendering — checks lighting, cameras, collisions.\n\n**Returns:**\n```json\n{\n  \"score\": 75,\n  \"issues\": [\n    {\"category\": \"lighting\", \"severity\": \"medium\", \"message\": \"Only one light source — consider adding fill or rim light\"},\n    {\"category\": \"collision\", \"severity\": \"high\", \"message\": \"Alice and Bob bounding boxes overlap by 5cm\"}\n  ],\n  \"breakdown\": {\n    \"lighting\": 60,\n    \"cameras\": 100,\n    \"figures\": 100,\n    \"collisions\": 50\n  }\n}\n```\n\n**Score:** 0-100. Issues reduce the score. Checks: bounding box collisions between figures, insufficient lighting, no cameras, no figures.\n\n**Use when:** Before rendering to catch common setup problems.\n\n---\n\n### 🎭 Emotional Direction\n\n#### `daz_set_emotion`\nApply an emotional expression to a character (morphs + body language).\n\n**Arguments:**\n- `character_label` (string): Character display label\n- `emotion` (string): Emotion name\n- `intensity` (float, default `0.7`): Strength of the expression (0.0–1.0)\n\n**Supported emotions:** `happy`, `sad`, `angry`, `surprised`, `fearful`, `disgusted`, `neutral`, `excited`, `bored`, `confident`, `shy`, `loving`, `contemptuous`\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"character\": \"Genesis 9\",\n  \"emotion\": \"happy\",\n  \"intensity\": 0.7,\n  \"applied\": [\"PHMSmile\", \"PHMBrowsUp\", \"chest_forward\"],\n  \"notFound\": [\"PHMEyeSquintL\"]\n}\n```\n\nMissing morphs (due to figure generation differences) are reported in `not_found` without raising an error.\n\n**Use when:** Quickly applying a recognizable expression instead of manually searching for morph names.\n\n**Example:**\n```\n# Apply full happy expression\ndaz_set_emotion(\"Alice\", \"happy\")\n\n# Subtle confident look\ndaz_set_emotion(\"Bob\", \"confident\", intensity=0.4)\n```\n\n---\n\n### 📚 Content Library Navigation\n\n#### `daz_list_categories`\nList subdirectories in the content library under a parent path.\n\n**Arguments:**\n- `parent_path` (string, default `\"\"`): Path relative to content library root (e.g., `\"People/Genesis 9\"`)\n\n**Returns:**\n```json\n{\n  \"path\": \"People/Genesis 9\",\n  \"categories\": [\"Characters\", \"Hair\", \"Clothing\", \"Expressions\"],\n  \"count\": 4\n}\n```\n\n**Use when:** Browsing the content library to discover available categories.\n\n**Example:**\n```\n# List top-level categories\ndaz_list_categories(\"\")\n\n# Browse Genesis 9 subcategories\ndaz_list_categories(\"People/Genesis 9\")\n```\n\n---\n\n#### `daz_browse_category`\nList `.duf` files in a content library category.\n\n**Arguments:**\n- `category_path` (string): Path relative to content library root\n- `sort_by` (string, default `\"name\"`): Sort order: `\"name\"` or `\"date\"`\n\n**Returns:**\n```json\n{\n  \"path\": \"People/Genesis 9/Hair\",\n  \"files\": [\n    {\"name\": \"Ade Hair\", \"path\": \"/Library/People/Genesis 9/Hair/Ade Hair.duf\"},\n    {\"name\": \"Braid Updo\", \"path\": \"/Library/People/Genesis 9/Hair/Braid Updo.duf\"}\n  ],\n  \"count\": 2\n}\n```\n\n**Use when:** Finding content file paths to load with `daz_load_file`.\n\n---\n\n#### `daz_get_content_info`\nRead metadata from a `.duf` file without loading it.\n\n**Arguments:**\n- `file_path` (string): Absolute path to `.duf` file\n\n**Returns:**\n```json\n{\n  \"name\": \"Ade Hair\",\n  \"type\": \"wearable\",\n  \"requires\": [\"Genesis 9\"],\n  \"author\": \"Daz Originals\",\n  \"description\": \"Long flowing hair for Genesis 9\"\n}\n```\n\n**Use when:** Checking compatibility or requirements before loading content.\n\n---\n\n### 🎬 Scene Composition / Cinematography\n\n#### `daz_apply_composition_rule`\nPosition camera using a photography composition rule.\n\n**Arguments:**\n- `camera_label` (string): Camera to position\n- `subject_label` (string): Subject to compose around\n- `rule` (string, default `\"rule-of-thirds\"`): Composition rule\n\n**Rules:**\n- `rule-of-thirds` — Subject on right vertical third at eye level\n- `golden-ratio` — Subject at 1.618 golden section\n- `center-frame` — Subject centered, symmetric\n- `leading-lines` — Low angle with diagonal offset\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"subject\": \"Genesis 9\",\n  \"rule\": \"rule-of-thirds\"\n}\n```\n\n---\n\n#### `daz_frame_shot`\nFrame camera using a standard cinematic shot type.\n\n**Arguments:**\n- `camera_label` (string): Camera to position\n- `subject_label` (string): Subject to frame\n- `shot_type` (string): Shot type name\n\n**Shot types and distances:**\n- `extreme-close-up` — 25 cm (eyes/mouth detail)\n- `close-up` — 50 cm (face)\n- `medium-close-up` — 90 cm (head and shoulders)\n- `medium-shot` — 140 cm (waist up)\n- `medium-full` — 200 cm (knees up)\n- `full-shot` — 400 cm (whole body)\n- `wide-shot` — 700 cm (body + environment)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"subject\": \"Genesis 9\",\n  \"shotType\": \"medium-shot\",\n  \"distance\": 140\n}\n```\n\n**Example:**\n```\n# Frame a portrait shot\ndaz_frame_shot(\"Camera 1\", \"Genesis 9\", \"close-up\")\n\n# Frame full body\ndaz_frame_shot(\"Camera 1\", \"Genesis 9\", \"full-shot\")\n```\n\n---\n\n#### `daz_apply_camera_angle`\nApply a standard camera angle preset relative to a subject.\n\n**Arguments:**\n- `camera_label` (string): Camera to position\n- `subject_label` (string): Subject to angle toward\n- `angle` (string, default `\"eye-level\"`): Camera angle preset\n\n**Angles:**\n- `eye-level` — Neutral, camera at subject's eye height\n- `high-angle` — Above subject, looking down (vulnerable)\n- `low-angle` — Below eye level, looking up (powerful)\n- `dutch-angle` — Eye level + 15° Z-roll (unsettling)\n- `overhead` — Directly above (bird's-eye)\n- `worms-eye` — Ground level looking up\n- `over-shoulder` — Behind and to one side\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"camera\": \"Camera 1\",\n  \"subject\": \"Genesis 9\",\n  \"angle\": \"low-angle\"\n}\n```\n\n---\n\n### 💾 Scene Checkpoint System\n\n#### `daz_save_scene_state`\nSave current scene state (transforms, morphs, light properties) as a named checkpoint.\n\n**Arguments:**\n- `checkpoint_name` (string): Name for this checkpoint\n\n**What is captured:**\n- All figures/skeletons: transform properties + active (non-zero) morph values\n- All cameras: transform properties\n- All lights: transform properties + Flux, Shadow Softness, Spread Angle\n\n**What is NOT captured:** Materials, geometry, HDR dome settings, parenting relationships.\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"checkpoint\": \"before_lighting_test\",\n  \"nodesCaptured\": 5,\n  \"savedAt\": \"2026-04-09T10:15:00\"\n}\n```\n\n**Important:** Checkpoints are stored in MCP server process memory and are lost if the server restarts.\n\n**Example:**\n```python\n# Safe experimentation workflow\ndaz_save_scene_state(\"before_lighting_test\")\ndaz_apply_lighting_preset(\"rembrandt\", \"Genesis 9\")\n# Don't like it?\ndaz_restore_scene_state(\"before_lighting_test\")\n```\n\n---\n\n#### `daz_restore_scene_state`\nRestore scene state from a named checkpoint.\n\n**Arguments:**\n- `checkpoint_name` (string): Name of checkpoint to restore\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"checkpoint\": \"before_lighting_test\",\n  \"nodesRestored\": 5\n}\n```\n\n---\n\n#### `daz_list_checkpoints`\nList all saved checkpoints in the current session.\n\n**Returns:**\n```json\n{\n  \"checkpoints\": [\n    {\"name\": \"before_lighting_test\", \"savedAt\": \"2026-04-09T10:15:00\", \"nodeCount\": 5},\n    {\"name\": \"pose_v2\", \"savedAt\": \"2026-04-09T10:32:00\", \"nodeCount\": 5}\n  ],\n  \"count\": 2\n}\n```\n\n---\n\n### 🗺️ Scene Layout \u0026 Proximity\n\n#### `daz_get_scene_layout`\nFull spatial map of all scene nodes with positions and bounding boxes.\n\n**Arguments:**\n- `include_types` (list, optional): Filter by type. Values: `\"figures\"`, `\"cameras\"`, `\"lights\"`, `\"props\"`. Omit for all types.\n\n**Returns:**\n```json\n{\n  \"nodes\": [\n    {\n      \"label\": \"Genesis 9\", \"type\": \"DzFigure\",\n      \"position\": {\"x\": 0, \"y\": 0, \"z\": 0},\n      \"boundingBox\": {\"min\": {...}, \"max\": {...}, \"center\": {...}}\n    }\n  ],\n  \"count\": 8\n}\n```\n\n**Use when:** Getting a complete overview of scene spatial layout before adding or moving objects.\n\n---\n\n#### `daz_find_nearby_nodes`\nFind all nodes within a radius of a target node.\n\n**Arguments:**\n- `target_label` (string): Center node to search around\n- `radius` (float, default `200.0`): Search radius in cm\n- `include_types` (list, optional): Filter by type: `\"figures\"`, `\"cameras\"`, `\"lights\"`, `\"props\"`\n\n**Returns:**\n```json\n{\n  \"target\": \"Alice\",\n  \"radius\": 200,\n  \"nearby\": [\n    {\"label\": \"Bob\", \"type\": \"DzFigure\", \"distance\": 120.5, \"direction\": \"front-right\"},\n    {\"label\": \"Chair\", \"type\": \"prop\", \"distance\": 85.0, \"direction\": \"right\"}\n  ],\n  \"count\": 2\n}\n```\n\n**Direction labels:** `front`, `front-right`, `right`, `back-right`, `back`, `back-left`, `left`, `front-left`\n\n**Use when:** Finding all characters or props near a subject, checking interaction range.\n\n---\n\n### ⚡ Async Rendering Tools\n\nFor long-running operations (full renders, animation export, multi-camera batch), async tools return immediately with a `request_id`. The script executes serially on DAZ Studio's main thread — the scene is locked while it runs, so subsequent scene modifications queue behind it.\n\n**Key constraint:** DAZ Studio is single-threaded. Async means the HTTP connection is released immediately — execution is still serial.\n\n#### `daz_render_async`\nSubmit a render asynchronously. Returns immediately with a `request_id`.\n\n**Arguments:**\n- `output_path` (string, optional): Output file path\n\n**Returns:**\n```json\n{\n  \"request_id\": \"render-a3f2b891\",\n  \"status\": \"queued\",\n  \"submitted_at\": \"2026-04-09T10:15:00\"\n}\n```\n\n---\n\n#### `daz_render_with_camera_async`\nSubmit a camera-specific render asynchronously.\n\n**Arguments:**\n- `camera_label` (string): Camera to render from\n- `output_path` (string, optional): Output file path\n\n---\n\n#### `daz_batch_render_cameras_async`\nSubmit a multi-camera batch render asynchronously.\n\n**Arguments:**\n- `cameras` (list[string]): Camera labels\n- `output_dir` (string): Output directory\n- `base_filename` (string, default `\"render\"`): Base filename\n\n---\n\n#### `daz_render_animation_async`\nSubmit an animation render asynchronously.\n\n**Arguments:**\n- `output_dir` (string): Output directory\n- `start_frame` (int, optional): First frame\n- `end_frame` (int, optional): Last frame\n- `filename_pattern` (string, default `\"frame\"`): Filename prefix\n- `camera` (string, optional): Camera to render from\n\n---\n\n#### `daz_get_request_status`\nPoll the status of an async request (non-blocking, lightweight).\n\n**Arguments:**\n- `request_id` (string): Request ID from an async submit tool\n\n**Returns:**\n```json\n{\n  \"request_id\": \"render-a3f2b891\",\n  \"status\": \"running\",\n  \"progress\": 0.0,\n  \"elapsed_ms\": 3200,\n  \"queue_position\": 0\n}\n```\n\n**Status values:** `queued`, `running`, `completed`, `failed`, `cancelled`\n\n---\n\n#### `daz_get_request_result`\nFetch the final result of an async request.\n\n**Arguments:**\n- `request_id` (string): Request ID\n- `wait` (bool, default `True`): If True, blocks until complete (up to `timeout_seconds`)\n- `timeout_seconds` (int, default `300`): Max wait time when `wait=True`\n\n**Returns (completed):**\n```json\n{\n  \"success\": true,\n  \"result\": {...},\n  \"request_id\": \"render-a3f2b891\",\n  \"duration_ms\": 45230,\n  \"completed_at\": \"2026-04-09T10:15:47\",\n  \"status\": \"completed\"\n}\n```\n\n---\n\n#### `daz_cancel_request`\nCancel a queued or running async request.\n\n**Arguments:**\n- `request_id` (string): Request ID to cancel\n\nQueued requests are removed immediately. Running requests set a cancel flag and call `killRender()`.\n\n**Returns:**\n```json\n{\n  \"request_id\": \"render-a3f2b891\",\n  \"status\": \"cancelled\",\n  \"cancelled_at\": \"2026-04-09T10:15:05\"\n}\n```\n\n---\n\n#### `daz_list_requests`\nList all active and recently completed async requests.\n\n**Arguments:**\n- `status_filter` (string, optional): Filter by status: `\"queued\"`, `\"running\"`, `\"completed\"`, `\"failed\"`, `\"cancelled\"`\n\n**Returns:**\n```json\n{\n  \"requests\": [...],\n  \"total\": 3,\n  \"queued\": 1,\n  \"running\": 1,\n  \"completed\": 1\n}\n```\n\n---\n\n#### `daz_set_render_quality`\nSet render quality preset before rendering.\n\n**Arguments:**\n- `preset` (string): One of `\"draft\"`, `\"preview\"`, `\"good\"`, `\"final\"`\n\n| Preset | Typical time | Use case |\n|--------|-------------|----------|\n| `draft` | 30s–2min | Quick composition check |\n| `preview` | 2–5min | Client review |\n| `good` | 10–20min | High quality review |\n| `final` | 30min–2hr | Final output |\n\n**Returns:**\n```json\n{\n  \"preset\": \"draft\",\n  \"settings\": {\"Max Samples\": 100, \"Render Quality\": 0.5}\n}\n```\n\n---\n\n**Async workflow example:**\n\n```python\n# 1. Set quality and submit\ndaz_set_render_quality(\"final\")\nreq = daz_render_async(\"/renders/final.png\")\n\n# 2. Poll status\nwhile True:\n    status = daz_get_request_status(req[\"request_id\"])\n    if status[\"status\"] in (\"completed\", \"failed\", \"cancelled\"):\n        break\n    # come back later...\n\n# 3. Or use wait=True in one step\nresult = daz_get_request_result(req[\"request_id\"], wait=True, timeout_seconds=3600)\n```\n\n---\n\n### ✏️ Modification Tools\n\n#### `daz_set_property`\nSet a numeric property on a scene node.\n\n**Arguments:**\n- `node_label` (string): Node display label or internal name\n- `property_name` (string): Property display label or internal name\n- `value` (float): New value\n\n**Returns:**\n```json\n{\n  \"node\": \"Genesis 9\",\n  \"property\": \"X Translate\",\n  \"value\": 50.0\n}\n```\n\n**Units:**\n- Translation: centimeters\n- Rotation: degrees\n- Morphs: typically 0-1 or percentage\n\n**Use when:** Moving nodes, adjusting morphs, or changing any numeric property.\n\n**Example:**\n```\ndaz_set_property(node_label=\"Genesis 9\", property_name=\"X Translate\", value=100.0)\n```\n\n---\n\n#### `daz_load_file`\nLoad a DAZ Studio file into the scene.\n\n**Arguments:**\n- `file_path` (string): Absolute path to file (`.duf`, `.daz`, `.obj`, `.fbx`, etc.)\n- `merge` (bool, default `True`): If true, merge into scene; if false, replace scene\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"file\": \"/path/to/character.duf\"\n}\n```\n\n**Use when:** Loading characters, props, scenes, or any content files.\n\n**Example:**\n```\ndaz_load_file(file_path=\"/Library/Genesis 9/Character.duf\", merge=True)\n```\n\n---\n\n### 🎬 Rendering Tools\n\n#### `daz_render`\nTrigger a render using current DAZ Studio render settings.\n\n**Arguments:**\n- `output_path` (string, optional): Absolute path for output image (e.g., `\"C:/renders/output.png\"`)\n\n**Returns:**\n```json\n{\n  \"success\": true\n}\n```\n\n**Notes:**\n- Uses DAZ Studio's currently configured render settings (dimensions, quality, engine)\n- Blocks until render completes (increase `DAZ_TIMEOUT` for long renders)\n- If `output_path` is omitted, uses DAZ Studio's configured output path\n\n**Use when:** Rendering the current scene setup.\n\n---\n\n### 🎭 Multi-Character Interaction Tools\n\n#### `daz_look_at_point`\nMake character look at a world-space point with cascading body involvement.\n\n**Arguments:**\n- `character_label` (string): Character display label or internal name\n- `target_x` (float): World X coordinate (cm) to look at\n- `target_y` (float): World Y coordinate (cm) to look at\n- `target_z` (float): World Z coordinate (cm) to look at\n- `mode` (string, default `\"head\"`): How much body to involve\n  - `\"eyes\"` - Only rotate eyes\n  - `\"head\"` - Eyes + head rotation\n  - `\"neck\"` - Eyes + head + neck\n  - `\"torso\"` - Eyes + head + neck + chest\n  - `\"full\"` - Complete body rotation including hip\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"character\": \"Genesis 9\",\n  \"mode\": \"head\",\n  \"rotatedBones\": [\"lEye\", \"rEye\", \"head\"]\n}\n```\n\n**Use when:** Making a character look at a specific point in 3D space with natural body movement.\n\n**Example:**\n```\n# Look at point in front at eye level\ndaz_look_at_point(\"Genesis 9\", 0, 160, 200, mode=\"head\")\n\n# Full body turn to look behind\ndaz_look_at_point(\"Genesis 9\", 0, 140, -150, mode=\"full\")\n```\n\n---\n\n#### `daz_look_at_character`\nMake one character look at another character's face.\n\n**Arguments:**\n- `source_label` (string): Character who will look\n- `target_label` (string): Character to look at\n- `mode` (string, default `\"head\"`): Body involvement level (same options as `daz_look_at_point`)\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"source\": \"Alice\",\n  \"target\": \"Bob\",\n  \"mode\": \"head\",\n  \"targetPosition\": {\"x\": 50, \"y\": 163, \"z\": 0},\n  \"rotatedBones\": [\"lEye\", \"rEye\", \"head\"]\n}\n```\n\n**Use when:** Creating eye contact or attention between characters.\n\n**Example:**\n```\n# Alice looks at Bob\ndaz_look_at_character(\"Alice\", \"Bob\", mode=\"head\")\n\n# Bob turns whole body to face Alice\ndaz_look_at_character(\"Bob\", \"Alice\", mode=\"full\")\n```\n\n---\n\n#### `daz_reach_toward`\nPosition character's arm to reach toward a world-space point using pseudo-IK.\n\n**Arguments:**\n- `character_label` (string): Character display label or internal name\n- `side` (string): Which arm: `\"left\"` or `\"right\"`\n- `target_x` (float): World X coordinate (cm) to reach toward\n- `target_y` (float): World Y coordinate (cm) to reach toward\n- `target_z` (float): World Z coordinate (cm) to reach toward\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"character\": \"Genesis 9\",\n  \"side\": \"right\",\n  \"targetDistance\": 45.3,\n  \"bones\": [\"right shoulder\", \"right forearm\", \"right hand\"]\n}\n```\n\n**Use when:** Positioning hands to grasp objects, point at things, or reach toward targets.\n\n**Example:**\n```\n# Reach right hand toward object at chest height\ndaz_reach_toward(\"Genesis 9\", \"right\", 50, 130, 80)\n\n# Reach left hand toward object on left side\ndaz_reach_toward(\"Genesis 9\", \"left\", -60, 100, 50)\n```\n\n**Note:** Uses simplified IK approximation. For precise hand positioning, load artist-created pose presets.\n\n---\n\n#### `daz_interactive_pose`\nCoordinate two characters for interactive poses.\n\n**Arguments:**\n- `char1_label` (string): First character display label\n- `char2_label` (string): Second character display label\n- `interaction_type` (string, default `\"face-each-other\"`): Type of interaction\n  - `\"face-each-other\"` - Position and rotate to face each other\n  - `\"hug\"` - Both characters hug with arms around each other\n  - `\"shoulder-arm\"` - Char1 puts arm around char2's shoulders\n  - `\"handshake\"` - Both extend right hands for handshake\n- `distance` (float, optional): Spacing between characters in cm\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"char1\": \"Alice\",\n  \"char2\": \"Bob\",\n  \"interactionType\": \"hug\",\n  \"applied\": [\"facing\", \"hug arms\"]\n}\n```\n\n**Use when:** Creating common two-character interactions quickly.\n\n**Example:**\n```\n# Position characters facing each other at conversation distance\ndaz_interactive_pose(\"Alice\", \"Bob\", \"face-each-other\", distance=120)\n\n# Create tight hug\ndaz_interactive_pose(\"Alice\", \"Bob\", \"hug\", distance=30)\n\n# Bob puts arm around Alice's shoulders\ndaz_interactive_pose(\"Bob\", \"Alice\", \"shoulder-arm\")\n```\n\n**Note:** These are simplified interaction poses. Fine-tune positions afterward using `daz_set_property`.\n\n---\n\n### 🔧 Low-Level Tools\n\n#### `daz_execute`\nExecute arbitrary inline DazScript code.\n\n**Arguments:**\n- `script` (string): DazScript (JavaScript) source code\n- `args` (dict, optional): JSON object accessible in script as `args` variable\n\n**Returns:**\n```json\n{\n  \"success\": true,\n  \"result\": 42,\n  \"output\": [\"line from print()\"],\n  \"error\": null,\n  \"request_id\": \"a3f2b891\"\n}\n```\n\n**Script Requirements:**\n- Wrap returning scripts in IIFE: `(function(){ return 42; })()`\n- Global objects available: `Scene`, `App`, `MainWindow`\n- Access args via: `var value = args.myKey;`\n\n**Use when:** You need fine-grained control or operations not covered by high-level tools.\n\n**Example:**\n```javascript\nscript = \"(function(){ return Scene.getNumNodes(); })()\"\n```\n\n---\n\n#### `daz_execute_file`\nExecute a DazScript file from disk.\n\n**Arguments:**\n- `script_file` (string): Absolute path to `.dsa` or `.ds` file\n- `args` (dict, optional): JSON object accessible as `args` in the script\n\n**Returns:** Same format as `daz_execute`\n\n**Use when:** Running complex scripts stored in files, especially scripts that use `include()` or `getScriptFileName()`.\n\n---\n\n## Features\n\n### 🚀 Script Registry\nHigh-level tools (`daz_scene_info`, `daz_get_node`, etc.) use the DazScriptServer script registry:\n- Scripts are registered once at startup\n- Subsequent calls execute by ID (no retransmission)\n- Auto-reregistration on 404 (when DAZ Studio restarts)\n\n### 🔄 Automatic Reconnection\nIf DAZ Studio restarts and clears the session registry, the server automatically detects 404 responses, re-registers all scripts, and retries the operation.\n\n### 🛡️ Error Handling\n- Connection failures → Clear error messages (\"Ensure DAZ Studio is running...\")\n- Timeouts → Actionable guidance (increase `DAZ_TIMEOUT`)\n- Authentication failures → Token file location in error message\n- Script errors → Full error details with line numbers and captured output\n\n---\n\n## Usage Examples\n\n### Example 1: Check Status\n```python\n# In Claude Desktop, just ask:\n\"Check if DAZ Studio is running\"\n\n# Claude will use daz_status and report back\n```\n\n### Example 2: Load and Position Character\n```\n1. Load Genesis 9 from /Library/Genesis 9/Genesis9.duf\n2. Move it 100cm to the right\n3. Get the current scene info\n```\n\nClaude will:\n1. Call `daz_load_file(file_path=\"/Library/Genesis 9/Genesis9.duf\", merge=True)`\n2. Call `daz_set_property(node_label=\"Genesis 9\", property_name=\"X Translate\", value=100.0)`\n3. Call `daz_scene_info()` and report the results\n\n### Example 3: Custom Lighting Setup\n```\nExecute this DazScript to create a three-point light setup:\n- Key light at (200, 200, 200) pointing at origin\n- Fill light at (-100, 150, 150) pointing at origin\n- Rim light at (0, 180, -200) pointing at origin\n```\n\nClaude will use `daz_execute` with the appropriate DazScript code.\n\n### Example 4: Batch Rendering\n```\nRender the current scene to these output paths:\n- C:/renders/front.png\n- C:/renders/side.png\n- C:/renders/back.png\n\nBetween each render, rotate the character 90 degrees.\n```\n\nClaude will loop through, adjusting rotation and calling `daz_render` for each output.\n\n---\n\n## Troubleshooting\n\n### \"Cannot connect to DAZ Studio\"\n\n**Cause:** DazScriptServer plugin is not running or not listening on the expected port.\n\n**Solutions:**\n1. Open DAZ Studio\n2. Go to **Window → Panes → Daz Script Server**\n3. Click **Start Server**\n4. Verify it's running on port 18811 (or update `DAZ_PORT` env var)\n\n---\n\n### \"Authentication failed (HTTP 401)\"\n\n**Cause:** API token is missing or incorrect.\n\n**Solutions:**\n1. Check DazScriptServer UI shows authentication is enabled\n2. Verify token file exists: `~/.daz3d/dazscriptserver_token.txt`\n3. Copy token exactly from DazScriptServer UI\n4. Set `DAZ_API_TOKEN` environment variable if using custom location\n\n---\n\n### \"Request timed out after 30s\"\n\n**Cause:** Script or render took longer than the timeout.\n\n**Solutions:**\n1. Increase timeout: `export DAZ_TIMEOUT=120.0`\n2. Update Claude Desktop config with larger timeout in `env` section\n3. Restart Claude Desktop after config change\n\n---\n\n### \"Script execution failed\" or \"Node not found\"\n\n**Cause:** DazScript error (syntax, missing node, wrong property name).\n\n**Solutions:**\n1. Check the error message for line numbers and details\n2. Verify node labels match exactly (case-sensitive)\n3. Use `daz_get_node` to discover available property names\n4. Test scripts manually in DAZ Studio Script IDE first\n\n---\n\n## Development\n\n### Running Tests\n\n```bash\n# Run all tests\nuv run pytest tests/ -v\n\n# Run specific test\nuv run pytest tests/test_server.py::test_daz_status_ok -v\n```\n\n### Project Structure\n\n```\nvangard-daz-mcp/\n├── src/vangard_daz_mcp/\n│   ├── server.py              # Single-file MCP server (all tools)\n│   └── dazscript_docs.json    # DazScript documentation loaded by daz_script_help\n├── tests/\n│   └── test_server.py         # Test suite with respx mocks\n├── pyproject.toml             # Project config (version, dependencies)\n├── ASYNC_OPERATIONS.md        # Design doc for async rendering system\n├── IMPLEMENTATION_PLAN.md     # Phased feature roadmap\n└── README.md\n```\n\n### Architecture\n\n- **FastMCP 3.x server** with stdio transport\n- **httpx.AsyncClient** for HTTP requests to DazScriptServer\n- **Script registry** for high-level tools (auto-registration on startup)\n- **Module-level `_http_client`** shared across all tool calls\n- **lifespan context** manages client initialization and cleanup\n\n---\n\n## Requirements\n\n- **Python:** 3.11+\n- **Dependencies:**\n  - `fastmcp\u003e=2.0` - MCP server framework\n  - `httpx\u003e=0.27` - Async HTTP client\n- **Dev Dependencies:**\n  - `pytest\u003e=8.0`\n  - `pytest-asyncio\u003e=0.24`\n  - `respx\u003e=0.21` - HTTP mocking for tests\n\n---\n\n## Limitations\n\n- DAZ Studio must be running locally (no remote DAZ Studio support)\n- DazScriptServer plugin must be installed and active\n- All scene operations execute on DAZ Studio's main thread — operations are serialized even with async tools\n- While a render is running, no other scene operations can execute (scene is locked)\n- Scene checkpoints are in-memory only and lost if the MCP server restarts\n- No support for binary data (rendered images must be saved to disk, not returned directly)\n\n---\n\n## Related Projects\n\n- **DazScriptServer**: https://github.com/bluemoonfoundry/daz-script-server\n  - The HTTP plugin this server wraps\n  - Required prerequisite\n- **Model Context Protocol**: https://modelcontextprotocol.io\n  - Specification this server implements\n- **FastMCP**: https://github.com/jlowin/fastmcp\n  - Framework used to build this server\n\n---\n\n## Contributing\n\nContributions welcome! Areas for improvement:\n\n- Material property tools (read/set surface colors, textures, shader settings)\n- Support for binary data (screenshot capture, returning rendered images directly)\n- Integration tests with a real DAZ Studio instance\n- More DazScript documentation topics in `dazscript_docs.json`\n- Additional lighting presets and emotion definitions\n\n---\n\n## License\n\nThis project is provided as-is for use with DAZ Studio.\n\n**Author:** Blue Moon Foundry\n\nFor questions or issues, please open an issue on GitHub.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluemoonfoundry%2Fdaz-mcp-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbluemoonfoundry%2Fdaz-mcp-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluemoonfoundry%2Fdaz-mcp-server/lists"}