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”The “Polyglot Bridge” Pattern
Section titled “The “Polyglot Bridge” Pattern”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:
- CrewAI (Python): Orchestrates the logic and decision making.
- FastMCP (Python): Acts as the standard interface server.
- 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.
🏗️ Architecture
Section titled “🏗️ Architecture”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
🚀 The Implementation
Section titled “🚀 The Implementation”1. The Worker: sap_client.js
Section titled “1. The Worker: sap_client.js”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.
const noderfc = require("node-rfc");
// Helper to read stdin fullyasync 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); }})();2. The Host: server.py
Section titled “2. The Host: server.py”This Python server exposes the tool to CrewAI. It handles the “dirty work” of spawning the Node.js process and parsing its output.
import sysimport jsonimport subprocessimport shutilfrom fastmcp import FastMCP
# Initialize FastMCPmcp = FastMCP("SAP_Polyglot_Bridge")
# Check if node is availableNODE_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)3. Dockerfile
Section titled “3. Dockerfile”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 baseFROM python:3.11-slim
# Install system dependencies and Node.jsRUN 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 librariesRUN echo "/usr/local/sap/nwrfcsdk/lib" > /etc/ld.so.conf.d/nwrfcsdk.conf \ && ldconfig
# Set ENV variable required by node-rfc build processENV SAPNWRFC_HOME=/usr/local/sap/nwrfcsdk
# --- APPLICATION SETUP ---WORKDIR /app
# Install Python dependenciesRUN pip install fastmcp
# Install Node.js dependencies# node-rfc will compile bindings against the SDK found in SAPNWRFC_HOMERUN npm install node-rfc
# Copy application codeCOPY server.py .COPY sap_client.js .
# Ensure your container has network access (e.g. via NordLayer)# Expose the port for FastMCP/RailwayEXPOSE 8000
# Start the Python MCP ServerCMD ["python", "server.py"]🔌 Connecting CrewAI
Section titled “🔌 Connecting CrewAI”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.
Agent Configuration
Section titled “Agent Configuration”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. Executecrew = Crew( agents=[sap_agent], tasks=[fetch_orders_task], verbose=True)
result = crew.kickoff()print(result)⚠️ Common Pitfalls
Section titled “⚠️ Common Pitfalls”- 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.
- Missing Libraries: The
libaio1package is often required by SAP libraries on Debian/Ubuntu-based images. - JSON Escaping: When the Agent generates the
paramsstring, 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.
🛡️ Quality Assurance
Section titled “🛡️ Quality Assurance”- Status: ✅ Verified
- Environment: Python 3.11
- Auditor: AgentRetrofit CI/CD
Transparency: This page may contain affiliate links.