Integrating CrewAI with SAP ECC using Node.js and `node-rfc`
Integrating CrewAI with SAP ECC using Node.js and node-rfc
Section titled “Integrating CrewAI with SAP ECC using Node.js and node-rfc”While Python is the lingua franca of AI, the Node.js ecosystem often has superior drivers for certain legacy protocols. SAP is a prime example: the node-rfc library is widely considered more robust, easier to install, and better maintained than its Python counterparts for specific asynchronous workflows.
This guide implements a Polyglot Bridge. We run a Python FastMCP server that acts as the “Brain,” which delegates the actual “Muscle” work of talking to SAP to a lightweight Node.js worker process.
The Architecture
Section titled “The Architecture”- CrewAI Agent: Connects directly to the MCP Server via SSE using the
mcpsparameter. - Python Host (
server.py): Accepts the request, validates it, and spawns a Node.js subprocess. - Node.js Worker (
sap_client.js): Reads JSON from stdin, executes the RFC call usingnode-rfc, and prints the result to stdout.
1. The Node.js Worker (sap_client.js)
Section titled “1. The Node.js Worker (sap_client.js)”This script handles the raw RFC communication. It reads a JSON payload from standard input, connects to SAP, runs the BAPI/RFC, and pipes the result back.
const noderfc = require("node-rfc");const fs = require('fs');
// 1. Read the full input payload from STDINconst inputData = fs.readFileSync(0, 'utf-8');let request;
try { request = JSON.parse(inputData);} catch (e) { console.error(JSON.stringify({ error: "Invalid JSON input" })); process.exit(1);}
// 2. Configure the SAP Client using Environment Variablesconst client = new noderfc.Client({ ashost: process.env.SAP_HOST, sysnr: process.env.SAP_SYSNR, client: process.env.SAP_CLIENT, user: process.env.SAP_USER, passwd: process.env.SAP_PASSWORD, lang: "EN"});
(async () => { try { // 3. Connect to SAP await client.open();
// 4. Invoke the Remote Function Call (RFC) // usage: client.call("BAPI_NAME", { PARAM: "VALUE" }) const result = await client.call(request.rfcName, request.params || {});
// 5. Write success output to STDOUT console.log(JSON.stringify({ status: "success", data: result }));
} catch (err) { // 6. Handle SAP Errors console.error(JSON.stringify({ status: "error", message: err.message, code: err.code })); } finally { // Close connection if open if (client.alive) { await client.close(); } }})();package.json:
{ "name": "sap-bridge", "version": "1.0.0", "dependencies": { "node-rfc": "^3.0.0" }}2. The Python Host (server.py)
Section titled “2. The Python Host (server.py)”This FastMCP server exposes the tool to your Agent. It acts as the controller, ensuring the Node process is called correctly and securing the environment.
import subprocessimport jsonimport osfrom fastmcp import FastMCP
# Initialize the MCP Servermcp = FastMCP("SAP Polyglot Bridge")
@mcp.tool()def call_sap_rfc(rfc_name: str, params: str = "{}") -> str: """ Executes an SAP RFC (Remote Function Call) via a Node.js bridge.
Args: rfc_name: The name of the SAP BAPI/RFC (e.g., 'BAPI_USER_GET_DETAIL'). params: A JSON string containing the import parameters for the RFC. """
# payload to send to Node.js try: parsed_params = json.loads(params) except json.JSONDecodeError: return "Error: 'params' must be a valid JSON string."
payload = json.dumps({ "rfcName": rfc_name, "params": parsed_params })
# Ensure your container has network access (e.g. via NordLayer) # This is critical for reaching on-prem SAP instances from the cloud.
try: # Spawn the Node.js worker process = subprocess.Popen( ["node", "sap_client.js"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=os.getcwd() )
# Pass data via stdin and get result stdout, stderr = process.communicate(input=payload)
if stderr and not stdout: return f"Bridge Error: {stderr.strip()}"
# Parse the JSON response from Node.js # We look for the last line in case of extraneous logs lines = stdout.strip().split('\n') last_line = lines[-1] if lines else "{}"
return last_line
except Exception as e: return f"System Error: {str(e)}"
if __name__ == '__main__': # Bind to 0.0.0.0 to support Docker networking mcp.run(transport='sse', host='0.0.0.0', port=8000)requirements.txt:
fastmcp==0.4.1uvicorn==0.27.13. The Dockerfile
Section titled “3. The Dockerfile”This is the most critical part. You cannot simply pip install SAP connectivity. You must inject the proprietary SAP NetWeaver RFC SDK into the container.
Prerequisite: Download SAP NW RFC SDK 7.50 (Linux x86_64) from the SAP Support Portal. Extract it so you have a folder named nwrfcsdk next to your Dockerfile.
# Use a slim Python baseFROM python:3.11-slim
# 1. Install System Dependencies & Node.js# We use curl to fetch the specific Node version setupRUN apt-get update && apt-get install -y \ curl \ build-essential \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/*
# 2. Setup Application DirectoryWORKDIR /app
# 3. Inject SAP Proprietary SDK# You must provide the 'nwrfcsdk' folder in your build context!COPY nwrfcsdk /usr/local/sap/nwrfcsdk
# 4. Configure Linker Paths for SAPENV SAPNWRFC_HOME=/usr/local/sap/nwrfcsdkENV LD_LIBRARY_PATH=$SAPNWRFC_HOME/lib
# 5. Install Python DependenciesCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# 6. Install Node.js DependenciesCOPY package.json .# npm install might require build-essential for binding compilationRUN npm install
# 7. Copy Application CodeCOPY server.py .COPY sap_client.js .
# 8. Network Exposure# Required for Railway/Docker connectivityEXPOSE 8000
# 9. Launch the ServerCMD ["python", "server.py"]4. Connecting CrewAI
Section titled “4. Connecting CrewAI”Once your Docker container is running (e.g., on http://localhost:8000), connect your CrewAI agent using the mcps parameter. This allows the agent to discover and use the call_sap_rfc tool dynamically.
from crewai import Agent, Task, Crew
# 1. Define the Agent with direct MCP access# If running in Docker locally, use localhost. If on Railway, use the public URL.sap_expert = Agent( role='SAP Integration Specialist', goal='Retrieve user data from SAP ECC', backstory='You are an expert in legacy ERP systems. You use the SAP Polyglot Bridge to fetch data.', mcps=["http://localhost:8000/sse"], # Connects to the server.py inside Docker verbose=True)
# 2. Define the Task# The agent will automatically find 'call_sap_rfc' from the MCP serverfetch_user_task = Task( description="Fetch details for SAP user 'JDOE' using BAPI_USER_GET_DETAIL. Ensure params are a valid JSON string.", agent=sap_expert, expected_output="JSON details of the user retrieved from SAP.")
# 3. Run the Crewcrew = Crew(agents=[sap_expert], tasks=[fetch_user_task])result = crew.kickoff()print(result)Why this works
Section titled “Why this works”By isolating the SAP node-rfc logic in a subprocess, you gain the stability of the Node.js SAP ecosystem while keeping your primary Agentic logic in Python. The Docker container encapsulates the complex library dependencies (LD_LIBRARY_PATH, SAP SDK), making deployment to platforms like Railway or AWS ECS seamless.
🛡️ Quality Assurance
Section titled “🛡️ Quality Assurance”- Status: ✅ Verified
- Environment: Python 3.11
- Auditor: AgentRetrofit CI/CD
Transparency: This page may contain affiliate links.