Skip to content

CrewAI agents processing complex SAP ECC data structures with Node.js `node-rfc`

CrewAI Agents Processing Complex SAP ECC Data Structures with Node.js node-rfc

Section titled “CrewAI Agents Processing Complex SAP ECC Data Structures with Node.js node-rfc”

While Python is the lingua franca of AI, the Node.js ecosystem often handles the asynchronous, event-driven nature of high-volume enterprise messaging better. More importantly, the node-rfc library for SAP is widely considered one of the most robust implementations of the SAP RFC protocol, particularly when dealing with deeply nested BAPI structures or complex table parameters that can be finicky in Python’s pyrfc.

This guide implements a Polyglot Bridge:

  1. CrewAI (Python): Orchestrates the logic and decision making.
  2. FastMCP (Python): Acts as the standard interface server.
  3. Node.js Worker: Executes the actual SAP RFC calls using node-rfc.

This architecture allows you to keep your agents in Python while leveraging the specific strengths of the Node.js SAP ecosystem.


We use a standard input/output (stdio) pipe to communicate between the Python MCP server and the Node.js worker. This avoids the overhead of running a secondary HTTP server inside the container.

graph LR
    A[CrewAI Agent] -->|SSE/HTTP| B(Python FastMCP Server)
    B -->|JSON via Stdin| C[Node.js Worker]
    C -->|RFC Protocol| D[(SAP ECC)]
    C -->|JSON via Stdout| B
    B -->|Result| A

This script acts as a “dumb terminal.” It waits for a JSON payload on stdin, executes the SAP function, and prints the result to stdout.

sap_client.js
const noderfc = require("node-rfc");
// Helper to read stdin fully
async function readStdin() {
return new Promise((resolve, reject) => {
let data = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => (data += chunk));
process.stdin.on("end", () => resolve(data));
process.stdin.on("error", reject);
});
}
(async () => {
try {
const inputData = await readStdin();
if (!inputData) {
throw new Error("No input data received via stdin");
}
const payload = JSON.parse(inputData);
const { connectionParams, rfcName, params } = payload;
if (!connectionParams || !rfcName) {
throw new Error("Missing connectionParams or rfcName in payload");
}
// Initialize Client
const client = new noderfc.Client(connectionParams);
// Open connection
await client.open();
// Call RFC
// node-rfc handles complex nested structures natively as JS objects
const result = await client.call(rfcName, params || {});
// Close connection
await client.close();
// Output success
console.log(JSON.stringify({ success: true, data: result }));
} catch (err) {
// Output error
console.log(JSON.stringify({
success: false,
error: err.message,
code: err.code || "UNKNOWN"
}));
process.exit(1);
}
})();

This Python server exposes the tool to CrewAI. It handles the “dirty work” of spawning the Node.js process and parsing its output.

server.py
import sys
import json
import subprocess
import shutil
from fastmcp import FastMCP
# Initialize FastMCP
mcp = FastMCP("SAP_Polyglot_Bridge")
# Check if node is available
NODE_PATH = shutil.which("node")
if not NODE_PATH:
raise RuntimeError("Node.js runtime not found. Please install Node.js.")
@mcp.tool()
def execute_sap_rfc(
ashost: str,
sysnr: str,
client: str,
user: str,
passwd: str,
rfc_name: str,
params: str = "{}"
) -> str:
"""
Executes an SAP RFC via a Node.js worker process.
Args:
ashost: SAP Application Server Host (IP or DNS)
sysnr: System Number (e.g., '00')
client: Client Number (e.g., '100')
user: SAP Username
passwd: SAP Password
rfc_name: Name of the Function Module (e.g., 'BAPI_SALESORDER_GETLIST')
params: JSON string of input parameters for the RFC.
"""
# Construct the payload for the Node.js worker
# We parse the incoming 'params' JSON string into a dict, then dump it back
# to ensure it's valid JSON structure for the bridge.
try:
rfc_params = json.loads(params)
except json.JSONDecodeError:
return json.dumps({"error": "Invalid JSON format in 'params' argument"})
worker_payload = {
"connectionParams": {
"ashost": ashost,
"sysnr": sysnr,
"client": client,
"user": user,
"passwd": passwd
},
"rfcName": rfc_name,
"params": rfc_params
}
try:
# Spawn the Node.js worker
process = subprocess.Popen(
[NODE_PATH, "sap_client.js"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Send data and get output
stdout, stderr = process.communicate(input=json.dumps(worker_payload))
if process.returncode != 0:
# Try to parse stdout for a structured error first
try:
err_json = json.loads(stdout)
return json.dumps(err_json)
except:
return json.dumps({
"success": False,
"error": f"Worker process failed: {stderr.strip()}"
})
# Return the raw JSON output from the worker
return stdout
except Exception as e:
return json.dumps({"success": False, "error": str(e)})
if __name__ == '__main__':
mcp.run(transport='sse', host='0.0.0.0', port=8000)

This is where the complexity lies. The SAP NWRFC SDK is proprietary. You cannot download it automatically. You must download it from the SAP Marketplace and place the nwrfcsdk folder in the root of your build context.

# Start with a Python base
FROM python:3.11-slim
# Install system dependencies and Node.js
RUN apt-get update && apt-get install -y \
nodejs \
npm \
libaio1 \
&& rm -rf /var/lib/apt/lists/*
# --- SAP NWRFC SDK SETUP ---
# NOTE: You must provide the 'nwrfcsdk' folder in your build context.
# Download from SAP Service Marketplace (Linux x86_64 version).
COPY nwrfcsdk /usr/local/sap/nwrfcsdk
# Configure dynamic linker
# Creates a config file telling Linux where to find SAP libraries
RUN echo "/usr/local/sap/nwrfcsdk/lib" > /etc/ld.so.conf.d/nwrfcsdk.conf \
&& ldconfig
# Set ENV variable required by node-rfc build process
ENV SAPNWRFC_HOME=/usr/local/sap/nwrfcsdk
# --- APPLICATION SETUP ---
WORKDIR /app
# Install Python dependencies
RUN pip install fastmcp
# Install Node.js dependencies
# node-rfc will compile bindings against the SDK found in SAPNWRFC_HOME
RUN npm install node-rfc
# Copy application code
COPY server.py .
COPY sap_client.js .
# Ensure your container has network access (e.g. via NordLayer)
# Expose the port for FastMCP/Railway
EXPOSE 8000
# Start the Python MCP Server
CMD ["python", "server.py"]

Once your Docker container is running (and port 8000 is mapped), you can connect your CrewAI agents to this bridge using the native mcps parameter. This eliminates the need for manual tool wrapping.

from crewai import Agent, Task, Crew
# 1. Define the Agent
# We point directly to the running Docker container using the 'mcps' list.
sap_agent = Agent(
role="SAP Systems Architect",
goal="Retrieve and analyze complex SAP data structures",
backstory="You are an expert in legacy ERP systems. You use a specialized Node.js bridge to talk to SAP.",
mcps=["http://localhost:8000/sse"],
verbose=True
)
# 2. Define the Task
# The agent will automatically discover the 'execute_sap_rfc' tool from the MCP server.
fetch_orders_task = Task(
description="""
Fetch the list of sales orders for customer '0000001000' from SAP.
Use the 'execute_sap_rfc' tool with the 'BAPI_SALESORDER_GETLIST' RFC.
The 'params' should be: {"CUSTOMER_NUMBER": "0000001000", "SALES_ORGANIZATION": "1000"}.
""",
expected_output="A summary of the sales orders found.",
agent=sap_agent
)
# 3. Execute
crew = Crew(
agents=[sap_agent],
tasks=[fetch_orders_task],
verbose=True
)
result = crew.kickoff()
print(result)
  1. Architecture Mismatch: Ensure you download the Linux x86_64 version of the SAP NWRFC SDK, not the Windows or Mac version, as Docker usually runs Linux.
  2. Missing Libraries: The libaio1 package is often required by SAP libraries on Debian/Ubuntu-based images.
  3. JSON Escaping: When the Agent generates the params string, it handles JSON escaping. However, deeply nested structures can sometimes confuse the LLM. It is often better to ask the LLM to generate the structure and then have a helper tool serialize it, though the Polyglot bridge handles stringified JSON input robustly.

  • Status: ✅ Verified
  • Environment: Python 3.11
  • Auditor: AgentRetrofit CI/CD

Transparency: This page may contain affiliate links.