TCP / MLLP Source

Listen for raw TCP or MLLP (Minimal Lower Layer Protocol) connections. MLLP is the standard transport protocol for HL7v2 messages in healthcare.

Overview

The TCP source opens a socket listener that accepts inbound connections and reads data from connected clients. In raw mode, the source reads data until the connection closes or a timeout is reached. In mllp mode, it uses MLLP framing to delimit individual HL7v2 messages within a persistent connection.

MLLP wraps each message with a start block character (0x0B), followed by the message content, an end block character (0x1C), and a carriage return (0x0D). This framing allows multiple messages to flow over a single TCP connection, which is the standard transport mechanism in hospital integration engines.

Note Most HL7v2 interfaces in production healthcare environments use MLLP on port 2575. Set mode: mllp and configure acknowledgment settings for standard HL7v2 interoperability.

Configuration

Configure the TCP source within the listener block of channel.yaml. Set type to tcp and provide options under the tcp key.

yaml
listener:
  type: tcp
  tcp:
    port: 2575
    mode: mllp
    max_connections: 50
    timeout_ms: 30000
    tls:
      cert_file: /etc/intu/certs/server.crt
      key_file: /etc/intu/certs/server.key
    ack:
      auto: true
      success_code: AA
      error_code: AE
      reject_code: AR

Properties

port int required
The TCP port to listen on. The standard MLLP port is 2575.
mode string optional
Transport mode. raw reads until connection close; mllp uses MLLP framing. Defaults to raw.
max_connections int optional
Maximum number of concurrent TCP connections. Additional connections are queued until a slot becomes available.
timeout_ms int optional
Idle timeout in milliseconds. Connections with no data for this duration are closed. Defaults to 30000 (30 seconds).
tls object optional
TLS configuration. Contains cert_file and key_file paths. Enables encrypted transport for the TCP listener.
ack object optional
Acknowledgment settings for MLLP mode. Controls automatic ACK generation after message processing.
response object optional
Custom response configuration. Allows sending a static or template-based response back to the client after processing.

ACK Properties

Property Type Description
auto bool When true, automatically generate an HL7 ACK message after processing. Defaults to true in MLLP mode.
success_code string ACK code for successful processing. Typically AA (Application Accept).
error_code string ACK code when an error occurs during processing. Typically AE (Application Error).
reject_code string ACK code when the message is rejected. Typically AR (Application Reject).

Complete Example

An MLLP listener on port 2575 that receives HL7v2 ADT messages, validates them, transforms the data, and forwards it to a database destination.

yaml
id: hl7-mllp-inbound
enabled: true

listener:
  type: tcp
  tcp:
    port: 2575
    mode: mllp
    max_connections: 100
    timeout_ms: 60000
    ack:
      auto: true
      success_code: AA
      error_code: AE
      reject_code: AR

validator:
  runtime: node
  entrypoint: validator.ts

transformer:
  runtime: node
  entrypoint: transformer.ts

destinations:
  - patient-db

TypeScript Transformer Example

This transformer parses an HL7v2 message received over MLLP and extracts patient demographic fields from the PID segment.

typescript
import { Message, TransformResult } from "@intu/sdk";

interface PatientDemographics {
  mrn: string;
  familyName: string;
  givenName: string;
  dateOfBirth: string;
  gender: string;
  address: {
    street: string;
    city: string;
    state: string;
    zip: string;
  };
  phone: string;
}

export default function transform(msg: Message): TransformResult {
  const raw = msg.body as string;
  const segments = raw.split("\r").filter(Boolean);
  const segmentMap = new Map<string, string[][]>();

  for (const seg of segments) {
    const fields = seg.split("|");
    const name = fields[0];
    if (!segmentMap.has(name)) segmentMap.set(name, []);
    segmentMap.get(name)!.push(fields);
  }

  const msh = segmentMap.get("MSH")?.[0];
  const pid = segmentMap.get("PID")?.[0];

  if (!msh || !pid) {
    return { success: false, error: "Missing MSH or PID segment" };
  }

  const nameParts = (pid[5] || "").split("^");
  const addrParts = (pid[11] || "").split("^");

  const patient: PatientDemographics = {
    mrn: pid[3]?.split("^")[0] || "",
    familyName: nameParts[0] || "",
    givenName: nameParts[1] || "",
    dateOfBirth: formatDate(pid[7]),
    gender: pid[8] || "",
    address: {
      street: addrParts[0] || "",
      city: addrParts[2] || "",
      state: addrParts[3] || "",
      zip: addrParts[4] || "",
    },
    phone: pid[13]?.split("^")[0] || "",
  };

  return {
    success: true,
    body: JSON.stringify(patient),
    contentType: "application/json",
  };
}

function formatDate(hl7Date: string | undefined): string {
  if (!hl7Date || hl7Date.length < 8) return "";
  return `${hl7Date.slice(0, 4)}-${hl7Date.slice(4, 6)}-${hl7Date.slice(6, 8)}`;
}