Skip to content

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

python
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
) -> McpServerHandle

Create-or-return a singleton DCC MCP server and start it. Thread-safe.

ParameterTypeDefaultDescription
instance_holderlist(required)Single-element list used as mutable reference
lockthreading.Lock(required)Module-level lock for thread safety
server_classtype(required)DccServerBase subclass to instantiate
portint8765TCP port for the MCP HTTP server
dispatcherAny | NoneNonePre-created host dispatcher forwarded to the server constructor before skill discovery
dispatcher_factoryCallable[[], Any | None] | NoneNoneLazily creates a dispatcher only when a new singleton is constructed
register_builtinsboolTrueCall register_builtin_actions() after creation
extra_skill_pathslist[str] | NoneNoneAdditional skill directories
include_bundledboolTrueInclude dcc-mcp-core bundled skills
enable_hot_reloadboolFalseEnable skill hot-reload
hot_reload_env_varstr | NoneNoneEnv var for hot-reload override

start_embedded_dcc_server

python
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,
    ...
) -> McpServerHandle

Adapter bootstrap helper that fixes the safe order for embedded hosts:

  1. Create or receive the host dispatcher.
  2. Construct the DccServerBase subclass with that dispatcher.
  3. Discover/load skills.
  4. Start the HTTP server and gateway registration.

Use this when a DCC plugin has to build its dispatcher before any skill can be loaded:

python
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

python
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.

python
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

python
get_server_instance(instance_holder: list) -> server | None

Return 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:

  1. A host execution bridge that routes skill scripts and direct callables onto the host's UI / main thread.
  2. A declarative skill list so launch-on-startup is reproducible across sessions.

DccServerBase exposes HostExecutionBridge and register_builtin_actions(minimal_mode=...) for exactly this:

python
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:

python
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.

Released under the MIT License.