This document outlines potential enhancements to make the DazScriptServer more powerful and efficient, particularly for MCP (Model Context Protocol) server integrations.
-
Where should script caching/library functionality live?
- Answer: Hybrid approach - Plugin provides primitives, MCP provides convenience
-
What endpoints would be most useful?
- Script registry/library for reusable scripts
- Common DAZ operations as dedicated endpoints
- Batch execution for multiple operations
-
How can results be more useful?
- Structured response formats for common types
- Type metadata
- Better error context
Allow registering named scripts that can be executed by ID instead of sending full script text each time.
Register a script in the server's library.
Request:
{
"name": "get-selected-objects",
"description": "Returns list of currently selected objects",
"script": "var objs = Scene.getSelectedNodeList(); return objs.map(function(n) { return {name: n.name, type: n.className()}; });",
"parameters": ["includeHidden"]
}Response:
{
"success": true,
"script_id": "get-selected-objects",
"registered_at": "2026-03-20T14:23:45Z"
}List all registered scripts.
Response:
{
"scripts": [
{
"id": "get-selected-objects",
"description": "Returns list of currently selected objects",
"parameters": ["includeHidden"],
"registered_at": "2026-03-20T14:23:45Z"
}
]
}Execute a registered script by ID.
Request:
{
"args": {
"includeHidden": false
}
}Response:
{
"success": true,
"result": [
{"name": "Genesis 9", "type": "DzFigure"},
{"name": "Camera 1", "type": "DzCamera"}
],
"output": [],
"error": null,
"request_id": "b4f3c892"
}Remove a registered script.
- Network efficiency: Send script once, execute many times
- Convenience: Named operations instead of script text
- Validation: Script syntax checked once at registration
- Documentation: Self-documenting API with descriptions
- Persistence: Can save registry to disk
Storage:
// In DzScriptServerPane.h
struct RegisteredScript {
QString id;
QString description;
QString script;
QStringList parameters;
QDateTime registeredAt;
};
private:
QMap<QString, RegisteredScript> m_registeredScripts;
QMutex m_scriptRegistryMutex;Persistence:
- Save registry to
~/.daz3d/dazscriptserver_scripts.json - Load on startup
- Optional: Sync across multiple DAZ instances
Pre-register commonly used DAZ operations as built-in endpoints.
These would be pre-registered on server start:
Get basic scene information.
Response:
{
"scene_name": "Untitled",
"node_count": 47,
"selected_count": 2,
"cameras": ["Camera 1", "Camera 2"],
"lights": ["Distant Light 1", "Point Light 1"]
}Implementation: Pre-registered script that gathers scene metadata.
Get currently selected objects with detailed info.
Response:
{
"selection": [
{
"name": "Genesis 9",
"type": "DzFigure",
"visible": true,
"locked": false,
"position": [0, 0, 0],
"rotation": [0, 0, 0]
}
]
}Select objects by name or pattern.
Request:
{
"names": ["Genesis 9", "Camera 1"],
"pattern": "Light*",
"clearExisting": true
}Get scene node hierarchy as tree.
Response:
{
"root": {
"name": "Scene",
"children": [
{
"name": "Genesis 9",
"type": "DzFigure",
"children": [
{"name": "Head", "type": "DzBone"},
{"name": "Body", "type": "DzBone"}
]
}
]
}
}Export scene or objects to file.
Request:
{
"format": "obj",
"path": "/path/to/output.obj",
"selection_only": true,
"options": {
"scale": 1.0,
"include_materials": true
}
}- No script knowledge needed: REST API for common operations
- Type safety: Structured requests/responses
- MCP-friendly: Easy to integrate into MCP tools
- Performance: Built-in scripts are pre-validated
void DzScriptServerPane::registerBuiltinScripts() {
// Scene info
registerScript("builtin:scene-info",
"Get basic scene information",
"return {scene_name: Scene.getFilename(), node_count: Scene.getNumNodes(), ...};",
QStringList());
// Selection
registerScript("builtin:get-selection",
"Get selected objects",
"var sel = Scene.getSelectedNodeList(); return sel.map(...);",
QStringList());
// ... more built-in scripts
}Current responses return raw QVariant which loses type information:
{
"result": [123, 456, 789] // What do these numbers mean?
}Node Object:
{
"name": "Genesis 9",
"type": "DzFigure",
"id": "uuid-string",
"visible": true,
"locked": false,
"position": {"x": 0, "y": 0, "z": 0},
"rotation": {"x": 0, "y": 0, "z": 0},
"scale": {"x": 1, "y": 1, "z": 1}
}Property Object:
{
"name": "Translate X",
"value": 0.0,
"min": -1000.0,
"max": 1000.0,
"type": "float",
"animated": false
}Allow scripts to declare return type:
// @returns {NodeInfo[]}
var objs = Scene.getSelectedNodeList();
return objs.map(function(n) {
return {
name: n.name,
type: n.className(),
// ...
};
});Add type hints to responses:
{
"success": true,
"result": [...],
"result_type": "NodeInfo[]",
"result_schema": "https://daz3d.com/schemas/NodeInfo.json",
"output": [],
"error": null
}Execute multiple operations in a single request.
Request:
{
"operations": [
{
"id": "op1",
"script_id": "get-selected-objects"
},
{
"id": "op2",
"script_id": "get-scene-info"
},
{
"id": "op3",
"script": "return Scene.getNumNodes();"
}
],
"atomic": false
}Response:
{
"results": [
{
"id": "op1",
"success": true,
"result": [...]
},
{
"id": "op2",
"success": true,
"result": {...}
},
{
"id": "op3",
"success": true,
"result": 47
}
]
}atomic: true - All operations succeed or all fail (transaction).
- Performance: One round-trip for multiple operations
- Consistency: Atomic operations guarantee consistent state
- Convenience: Build complex workflows
Scripts with parameter placeholders.
Request:
{
"name": "set-property",
"description": "Set a property value on an object",
"template": true,
"script": "var node = Scene.findNodeByLabel('{{nodeName}}'); node.setProperty('{{propertyName}}', {{value}});",
"parameters": ["nodeName", "propertyName", "value"]
}Request:
{
"args": {
"nodeName": "Genesis 9",
"propertyName": "Translate X",
"value": 100.0
}
}Server substitutes {{nodeName}} → "Genesis 9" before execution.
- Reusability: Generic scripts with parameters
- Safety: Parameter validation before substitution
- Clarity: Explicit parameters vs. args object
Long-running operations (rendering, export) block HTTP threads.
Request:
{
"script": "/* long-running operation */",
"async": true
}Response (immediate):
{
"job_id": "job-abc123",
"status": "queued",
"created_at": "2026-03-20T14:23:45Z"
}Response:
{
"job_id": "job-abc123",
"status": "running",
"progress": 0.45,
"created_at": "2026-03-20T14:23:45Z",
"started_at": "2026-03-20T14:23:46Z"
}Response:
{
"job_id": "job-abc123",
"status": "completed",
"result": {...},
"output": [],
"error": null,
"duration_ms": 45230
}For real-time progress:
ws://localhost:18811/jobs/{id}/stream
// Messages:
{"type": "progress", "value": 0.25, "message": "Processing textures..."}
{"type": "progress", "value": 0.50, "message": "Rendering frame 1..."}
{"type": "complete", "result": {...}}Effort: Medium Value: High Dependencies: None
Implement:
- POST /scripts/register
- GET /scripts
- POST /scripts/{id}/execute
- DELETE /scripts/{id}
This alone provides huge value for MCP servers:
# MCP server setup
client.register_script("get-selection", script_text)
# Later, repeatedly:
result = client.execute_script("get-selection")Effort: Medium Value: High Dependencies: Phase 1
Implement 5-10 most common operations:
- Scene info
- Selection management
- Object queries
- Basic manipulation
Effort: Low-Medium Value: Medium Dependencies: None
Add structured response types and metadata.
Effort: Medium Value: Medium Dependencies: Phase 1
Useful for complex workflows.
Effort: Low Value: Low-Medium Dependencies: Phase 1
Nice-to-have, not essential.
Effort: High Value: Medium Dependencies: All above
Only if long-running operations are common.
Recommendation: Store in plugin, expose via API
// Persistent storage
void DzScriptServerPane::saveScriptRegistry() {
QFile file("~/.daz3d/dazscriptserver_scripts.json");
// Write m_registeredScripts to JSON
}
void DzScriptServerPane::loadScriptRegistry() {
// Load from JSON on startup
}Alternative: MCP-side caching is still possible and complementary.
Recommendation: Namespace separation
builtin:scene-info- Cannot be deleteduser:my-script- User-registeredsystem:import-preset- System scripts
Considerations:
- Validate script IDs (no path traversal)
- Limit registry size (max 100 scripts?)
- Rate-limit registration
- Optional: Require auth for registration
With Phase 1 implemented:
# MCP Server (one-time setup)
from daz_script_client import DazScriptClient
client = DazScriptClient("http://localhost:18811", token="...")
# Register commonly used scripts
client.register_script(
name="get-figure-morphs",
script="""
var fig = Scene.getSelectedNode(0);
var morphs = fig.getModifierList();
return morphs.map(function(m) {
return {name: m.name, value: m.getValue()};
});
"""
)
# Later, use repeatedly without resending script
@mcp_tool("daz_get_morphs")
def get_morphs():
return client.execute_script("get-figure-morphs")Benefit: Script sent once, executed many times.
Implement Phase 1: Script Registry
- Highest value/effort ratio
- Enables efficient MCP integration
- Foundation for future features
- ~300-400 lines of code
Add Phase 2: Built-in Operations
- Preregister 5-10 common operations
- Document in API reference
- Makes server immediately useful without script knowledge
Add Phase 3: Enhanced Responses and Phase 4: Batch Operations
- Better developer experience
- Performance optimization
Consider Phase 6: Async/Streaming if use cases emerge.
Where should functionality live? → Plugin provides script registry/library primitives, MCP servers use them for convenience
Most valuable enhancement? → Script registry (Phase 1) - enables caching without coupling to specific use cases
MCP or Plugin? → Both: Plugin provides efficient execution layer, MCP provides high-level abstractions
This hybrid approach gives maximum flexibility while optimizing network efficiency.