Skip to content

Transport Layer

v0.14 replaced the legacy transport stack (issue #251).

The old classes — TransportManager, FramedChannel, FramedIo, IpcListener (Python), ListenerHandle, RoutingStrategy, ConnectionPool, InstanceRouter, CircuitBreaker, MessageEnvelope, encode_request / encode_response / encode_notify / decode_envelope, connect_ipc — have been removed. Use the DccLink adapters built on ipckit documented below.

The Transport layer (dcc-mcp-transport crate) provides IPC communication between MCP servers and DCC application instances using DccLink framing over Named Pipes (Windows) or Unix Domain Sockets (macOS/Linux).

Overview

The new transport API is built around DccLink adapters — thin wrappers over ipckit IPC channels that use a binary wire format ([u32 len][u8 type][u64 seq][msgpack body]) for efficient framed communication.

python
from dcc_mcp_core import IpcChannelAdapter, DccLinkFrame

# Server side: create a named channel and wait for a client
server = IpcChannelAdapter.create("my-dcc")
server.wait_for_client()

# Client side: connect to the server
client = IpcChannelAdapter.connect("my-dcc")

# Send a frame
frame = DccLinkFrame(msg_type=1, seq=0, body=b"hello")
client.send_frame(frame)

# Receive a frame
received = server.recv_frame()
print(received.body)  # b"hello"

DccLinkFrame

Binary wire frame for DCC-Link protocol. Wire format: [u32 len][u8 type][u64 seq][msgpack body].

Message Types

TagTypeDescription
1CallRequest invocation
2ReplySuccessful response
3ErrError response
4ProgressProgress update
5CancelCancellation signal
6PushServer-pushed message
7PingHeartbeat request
8PongHeartbeat response

Constructor

python
from dcc_mcp_core import DccLinkFrame

frame = DccLinkFrame(msg_type=1, seq=0, body=b"hello")
ParameterTypeDescription
msg_typeintMessage type tag (1-8)
seqintSequence number
bodybytes | NonePayload bytes (defaults to b"")

Properties

PropertyTypeDescription
msg_typeintMessage type tag (1=Call, 2=Reply, 3=Err, 4=Progress, 5=Cancel, 6=Push, 7=Ping, 8=Pong)
seqintSequence number
bodybytesPayload bytes

Methods

MethodReturnsDescription
encode()bytesEncode the frame to [len][type][seq][body] bytes
decode(data)DccLinkFrameDecode a frame from bytes including the 4-byte length prefix (static)
python
frame = DccLinkFrame(msg_type=1, seq=0, body=b"payload")
encoded = frame.encode()
decoded = DccLinkFrame.decode(encoded)
assert decoded.msg_type == frame.msg_type
assert decoded.seq == frame.seq
assert decoded.body == frame.body

IpcChannelAdapter

Thin adapter over ipckit::IpcChannel using DCC-Link framing. Supports 1:1 connections over Named Pipes (Windows) or Unix Domain Sockets (macOS/Linux).

Creating a Server

python
from dcc_mcp_core import IpcChannelAdapter

server = IpcChannelAdapter.create("my-dcc")
server.wait_for_client()  # blocks until a client connects

Connecting as a Client

python
from dcc_mcp_core import IpcChannelAdapter

client = IpcChannelAdapter.connect("my-dcc")

Sending and Receiving Frames

python
from dcc_mcp_core import IpcChannelAdapter, DccLinkFrame

# Server side
server = IpcChannelAdapter.create("my-dcc")
server.wait_for_client()

# Client side
client = IpcChannelAdapter.connect("my-dcc")

# Client sends a Call frame
call_frame = DccLinkFrame(msg_type=1, seq=0, body=b"execute_python")
client.send_frame(call_frame)

# Server receives the frame
received = server.recv_frame()  # blocking; returns None if channel closed
if received is not None:
    print(received.msg_type)  # 1
    print(received.body)      # b"execute_python"

    # Server sends a Reply frame
    reply = DccLinkFrame(msg_type=2, seq=0, body=b"ok")
    server.send_frame(reply)

# Client receives the reply
response = client.recv_frame()

Static Methods

MethodReturnsDescription
create(name)IpcChannelAdapterCreate a server-side IPC channel
connect(name)IpcChannelAdapterConnect to an existing IPC channel

Instance Methods

MethodReturnsDescription
wait_for_client()NoneWait for a client to connect (server-side only)
send_frame(frame)NoneSend a DccLinkFrame to the peer
recv_frame()DccLinkFrame | NoneReceive a frame (blocking). Returns None if channel closed

GracefulIpcChannelAdapter

Extends IpcChannelAdapter with graceful shutdown and DCC main-thread integration. Use this in DCC plugins that need to process IPC messages on the main thread without blocking.

Creating a Graceful Server

python
from dcc_mcp_core import GracefulIpcChannelAdapter

server = GracefulIpcChannelAdapter.create("my-dcc")
server.bind_affinity_thread()  # call once on the DCC main thread
server.wait_for_client()

Pumping Messages on the Main Thread

In DCC applications, IPC messages must often be processed on the main thread. Use pump_pending() from an idle callback:

python
# Maya example: use scriptJob idleEvent
import maya.cmds as cmds

def on_idle():
    processed = server.pump_pending(budget_ms=50)
    # returns number of items processed

cmds.scriptJob(idleEvent="python(\"on_idle()\")")

Graceful Shutdown

python
server.shutdown()  # signals the channel to shut down gracefully

Static Methods

MethodReturnsDescription
create(name)GracefulIpcChannelAdapterCreate a server-side graceful IPC channel
connect(name)GracefulIpcChannelAdapterConnect to an existing graceful IPC channel

Instance Methods

MethodReturnsDescription
wait_for_client()NoneWait for a client to connect (server-side only)
send_frame(frame)NoneSend a DccLinkFrame to the peer
recv_frame()DccLinkFrame | NoneReceive a frame (blocking). Returns None if channel closed
shutdown()NoneSignal the channel to shut down gracefully
bind_affinity_thread()NoneBind the current thread as the affinity thread. Call once on the DCC main thread
pump_pending(budget_ms=100)intDrain pending work items on the affinity thread within the budget. Returns items processed

SocketServerAdapter

Multi-client IPC server using Unix Domain Sockets (macOS/Linux) or Named Pipes (Windows). Supports a bounded connection pool.

Creating a Socket Server

python
from dcc_mcp_core import SocketServerAdapter

server = SocketServerAdapter(
    path="/tmp/my-dcc.sock",  # Unix socket path or Windows pipe name
    max_connections=10,        # maximum concurrent connections
    connection_timeout_ms=30000,  # connection timeout in ms
)

print(server.socket_path)      # the path this server is listening on
print(server.connection_count) # number of currently connected clients

server.shutdown()  # gracefully shut down

Constructor

ParameterTypeDefaultDescription
pathstrSocket path (Unix) or pipe name (Windows)
max_connectionsint10Maximum concurrent connections
connection_timeout_msint30000Connection timeout in milliseconds

Properties

PropertyTypeDescription
socket_pathstrThe socket path this server is listening on
connection_countintNumber of currently connected clients

Instance Methods

MethodReturnsDescription
shutdown()NoneGracefully shut down the server (blocks until stopped)
signal_shutdown()NoneSignal shutdown without blocking

Transport Helpers

TransportAddress

Protocol-agnostic transport endpoint. Supports TCP, Named Pipes (Windows), and Unix Domain Sockets (macOS/Linux).

python
from dcc_mcp_core import TransportAddress

# Factory constructors
addr = TransportAddress.tcp("127.0.0.1", 18812)
addr = TransportAddress.named_pipe("maya-mcp")          # Windows
addr = TransportAddress.unix_socket("/tmp/maya.sock")   # macOS/Linux

# Platform-optimal local address (PID-unique)
addr = TransportAddress.default_local("maya", pid=12345)

# Parse from URI string
addr = TransportAddress.parse("tcp://127.0.0.1:18812")
Property/MethodReturnsDescription
schemestr"tcp", "pipe", or "unix"
is_localboolWhether this is a same-machine transport
is_tcpboolWhether this is TCP
is_named_pipeboolWhether this is a Named Pipe
is_unix_socketboolWhether this is a Unix Socket
to_connection_string()strURI string, e.g. "tcp://127.0.0.1:18812"

TransportScheme

Strategy for choosing the optimal communication channel:

ConstantDescription
AUTOAuto-select best transport (Named Pipe on Windows, Unix Socket on *nix)
TCP_ONLYAlways use TCP
PREFER_NAMED_PIPEPrefer Named Pipe, fall back to TCP
PREFER_UNIX_SOCKETPrefer Unix Socket, fall back to TCP
PREFER_IPCPrefer any IPC, fall back to TCP
python
from dcc_mcp_core import TransportScheme

addr = TransportScheme.AUTO.select_address("maya", "127.0.0.1", 18812, pid=12345)

ServiceEntry

Represents a discovered DCC service instance.

PropertyTypeDescription
dcc_typestrDCC application type (e.g. "maya")
instance_idstrUUID string
hoststrHost address
portintTCP port
versionstr | NoneDCC version
scenestr | NoneCurrently open scene/file
metadatadict[str, str]Arbitrary string-only metadata
extrasdict[str, Any]JSON-typed DCC metadata
statusServiceStatusInstance status
transport_addressTransportAddress | NonePreferred IPC address
last_heartbeat_msintLast heartbeat timestamp (Unix ms)

ServiceStatus

DCC service health status:

ConstantDescription
AVAILABLEReady to accept requests
BUSYProcessing; may accept more
UNREACHABLENot responding to heartbeats
SHUTTING_DOWNGraceful shutdown in progress

End-to-End Example

DCC Plugin (Server)

python
# Inside a Maya plugin
from dcc_mcp_core import GracefulIpcChannelAdapter, DccLinkFrame

server = GracefulIpcChannelAdapter.create("maya-ipc")
server.bind_affinity_thread()  # call once on main thread
server.wait_for_client()

# In Maya idle callback:
def on_idle():
    processed = server.pump_pending(budget_ms=50)

# Main message loop
while True:
    frame = server.recv_frame()
    if frame is None:
        break  # channel closed
    if frame.msg_type == 1:  # Call
        # Process the request...
        reply = DccLinkFrame(msg_type=2, seq=frame.seq, body=b"ok")
        server.send_frame(reply)

server.shutdown()

MCP Agent (Client)

python
from dcc_mcp_core import IpcChannelAdapter, DccLinkFrame

client = IpcChannelAdapter.connect("maya-ipc")

# Send a Call frame
call = DccLinkFrame(msg_type=1, seq=0, body=b"get_scene_info")
client.send_frame(call)

# Receive the Reply
reply = client.recv_frame()
if reply and reply.msg_type == 2:
    print(f"Result: {reply.body}")

Released under the MIT License.