Server Factory API
Singleton factory helpers for DCC MCP server instances. Eliminates the boilerplate threading lock + None check that every adapter would otherwise duplicate.
Exported symbols: create_dcc_server, start_embedded_dcc_server, get_server_instance, make_start_stop
create_dcc_server
create_dcc_server(
*, instance_holder: list, lock: threading.Lock, server_class: type,
port: int = 8765, dispatcher=None, dispatcher_factory=None,
register_builtins: bool = True,
extra_skill_paths: list[str] | None = None,
include_bundled: bool = True, enable_hot_reload: bool = False,
hot_reload_env_var: str | None = None, **server_kwargs
) -> McpServerHandleCreate-or-return a singleton DCC MCP server and start it. Thread-safe.
| Parameter | Type | Default | Description |
|---|---|---|---|
instance_holder | list | (required) | Single-element list used as mutable reference |
lock | threading.Lock | (required) | Module-level lock for thread safety |
server_class | type | (required) | DccServerBase subclass to instantiate |
port | int | 8765 | TCP port for the MCP HTTP server |
dispatcher | Any | None | None | Pre-created host dispatcher forwarded to the server constructor before skill discovery |
dispatcher_factory | Callable[[], Any | None] | None | None | Lazily creates a dispatcher only when a new singleton is constructed |
register_builtins | bool | True | Call register_builtin_actions() after creation |
extra_skill_paths | list[str] | None | None | Additional skill directories |
include_bundled | bool | True | Include dcc-mcp-core bundled skills |
enable_hot_reload | bool | False | Enable skill hot-reload |
hot_reload_env_var | str | None | None | Env var for hot-reload override |
start_embedded_dcc_server
start_embedded_dcc_server(
*,
dcc_name: str,
instance_holder: list,
lock: threading.Lock,
server_class: type,
dispatcher_factory: Callable[[], Any | None] | None = None,
dispatcher: Any | None = None,
env_prefix: str | None = None,
...
) -> McpServerHandleAdapter bootstrap helper that fixes the safe order for embedded hosts:
- Create or receive the host dispatcher.
- Construct the
DccServerBasesubclass with that dispatcher. - Discover/load skills.
- Start the HTTP server and gateway registration.
Use this when a DCC plugin has to build its dispatcher before any skill can be loaded:
from dcc_mcp_core import start_embedded_dcc_server
_holder = [None]
_lock = threading.Lock()
def start_server(port=8765):
return start_embedded_dcc_server(
dcc_name="blender",
instance_holder=_holder,
lock=_lock,
server_class=BlenderMcpServer,
dispatcher_factory=create_blender_dispatcher,
env_prefix="DCC_MCP_BLENDER",
port=port,
)make_start_stop
make_start_stop(
server_class: type,
hot_reload_env_var: str | None = None,
dispatcher_factory: Callable[[], Any | None] | None = None,
) -> tuple[Callable, Callable]Generate a (start_server, stop_server) function pair for a DCC adapter. Zero-boilerplate.
from dcc_mcp_core import make_start_stop
start_server, stop_server = make_start_stop(
MyDccServer,
hot_reload_env_var="DCC_MCP_MYDCC_HOT_RELOAD",
dispatcher_factory=create_my_dcc_dispatcher,
)get_server_instance
get_server_instance(instance_holder: list) -> server | NoneReturn the current singleton instance (or None if not started).
Embedded-host wiring (issues #521, #525, #599, #604)
Embedded DCC plugins (Maya, Houdini, Unreal Python, Blender) typically need two pieces of glue beyond the bare server:
- A host execution bridge that routes skill scripts and direct callables onto the host's UI / main thread.
- A declarative skill list so launch-on-startup is reproducible across sessions.
DccServerBase exposes HostExecutionBridge and register_builtin_actions(minimal_mode=...) for exactly this:
from dcc_mcp_core import (
DccServerBase,
HostExecutionBridge,
InProcessCallableDispatcher,
MinimalModeConfig,
)
class MayaDccServer(DccServerBase):
@classmethod
def dcc_name(cls) -> str: return "maya"
# 1) Pass the execution bridge into the constructor BEFORE registering builtins.
dispatcher = InProcessCallableDispatcher() # or your Maya UI-thread subclass
bridge = HostExecutionBridge(dispatcher=dispatcher)
server = MayaDccServer(port=8765, execution_bridge=bridge)
# 2) Pin the boot-time skill set declaratively.
server.register_builtin_actions(minimal_mode=MinimalModeConfig(
skills=("scene_inspector", "render_queue"),
deactivate_groups={"render_queue": ("submit",)},
env_var_minimal="DCC_MCP_MAYA_MINIMAL",
))
server.start()dispatcher=... and register_inprocess_executor(...) remain supported for existing adapters. New adapters should keep the bridge and use bridge.dispatch_callable(...) for direct host work so skill scripts and dynamic calls share the same affinity metadata and error normalization.
Long-running host-native operations can return DeferredToolResult from the skill runner or a direct bridge callable. The bridge polls the completion callback until it returns a final JSON-serialisable value; when the original tools/call used the async job path (execution: async, timeout hint, or _meta.dcc.async), the existing JobManager row remains running until that final value completes or the deferred result times out:
from dcc_mcp_core import DeferredToolResult, skill_success
def start_render(params):
job = host_render_start(params)
def check_done():
if not host_render_finished(job):
return None
return skill_success("Render complete", output_path=host_render_path(job))
return DeferredToolResult(
check_is_finished=check_done,
timeout_secs=3600,
poll_interval_secs=0.25,
stdout="Render submitted",
)See Callable Dispatcher API for the full BaseDccCallableDispatcher / BaseDccCallableDispatcherFull / BaseDccPump contract and the MinimalModeConfig resolution order.