CrewAI and SOAP API integration using Node.js `soap` library
CrewAI and SOAP API Integration (Polyglot Bridge)
Section titled “CrewAI and SOAP API Integration (Polyglot Bridge)”When modernizing enterprise infrastructure, you will inevitably encounter SOAP (Simple Object Access Protocol) interfaces that are notoriously difficult to handle with standard Python libraries like zeep. Many legacy WSDLs were generated by .NET or Java systems that have quirks specific to the Node.js soap library ecosystem.
This guide implements a Polyglot Bridge: A Python-based MCP Server acting as the host, which delegates the heavy XML lifting to a specialized Node.js worker process.
The Architecture: Polyglot Bridge
Section titled “The Architecture: Polyglot Bridge”- Host (Python/FastMCP): Manages the Agent interface and runs the HTTP server.
- Worker (Node.js): Uses the battle-tested
soapnpm package to handle WSDL parsing and request execution. - Transport: Data flows via Standard I/O (stdin/stdout) between Python and Node.js.
1. The Worker: soap_client.js
Section titled “1. The Worker: soap_client.js”This Node.js script acts as a stateless function. It reads a JSON payload from stdin, executes the SOAP request, and prints the result to stdout.
/** * soap_client.js * A lightweight worker to handle SOAP requests via the 'soap' npm package. */const soap = require('soap');
// buffer for stdinlet inputData = '';
// 1. Read input from Python Hostprocess.stdin.on('data', chunk => { inputData += chunk;});
process.stdin.on('end', async () => { try { const requestPayload = JSON.parse(inputData); const { wsdl_url, method, args, proxies } = requestPayload;
const options = {};
// Configure Proxy if provided if (proxies && proxies.http) { options.proxy = proxies.http; }
// 2. Create SOAP Client const client = await soap.createClientAsync(wsdl_url, options);
if (!client[method]) { throw new Error(`Method '${method}' not found in WSDL.`); }
// 3. Execute Method // We wrap the callback-style library in a Promise const executeMethod = (methodName, args) => { return new Promise((resolve, reject) => { client[methodName](args, (err, result) => { if (err) return reject(err); resolve(result); }); }); };
const result = await executeMethod(method, args);
// 4. Output success to stdout console.log(JSON.stringify({ status: 'success', data: result }));
} catch (error) { // 5. Output error to stdout (captured by Python) console.log(JSON.stringify({ status: 'error', message: error.message, stack: error.stack })); process.exit(1); }});2. The Server: server.py
Section titled “2. The Server: server.py”This FastMCP server defines the tools available to CrewAI. When called, it spawns the Node.js worker to perform the actual network request.
"""server.pyPython MCP Host that delegates SOAP calls to a Node.js subprocess."""from fastmcp import FastMCPimport subprocessimport jsonimport os
# Define the MCP Servermcp = FastMCP("PolyglotSOAPBridge")
@mcp.tool()def call_soap_endpoint(wsdl_url: str, operation: str, arguments: dict) -> str: """ Calls a legacy SOAP endpoint using a Node.js worker.
Args: wsdl_url: The full URL to the WSDL (e.g., http://soap.server.com?wsdl). operation: The case-sensitive name of the SOAP operation to call. arguments: A dictionary of parameters matching the WSDL schema. """
# Configuration payload for the Node.js worker payload = { "wsdl_url": wsdl_url, "method": operation, "args": arguments, # "proxies": { # "http": "http://user:[email protected]:22225", # "https": "http://user:[email protected]:22225" # } # For production, inject BrightData proxy URL here }
try: # Spawn Node.js process process = subprocess.Popen( ['node', 'soap_client.js'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True )
# Send payload to Node.js stdin stdout, stderr = process.communicate(input=json.dumps(payload))
if process.returncode != 0: return f"System Error in Node Worker: {stderr}"
# Parse Node.js output try: response = json.loads(stdout) if response.get("status") == "error": return f"SOAP Fault: {response.get('message')}" return json.dumps(response.get("data"), indent=2) except json.JSONDecodeError: return f"Invalid JSON from Worker: {stdout}"
except Exception as e: return f"Bridge Exception: {str(e)}"
if __name__ == '__main__': mcp.run(transport='sse', host='0.0.0.0', port=8000)3. Deployment: Dockerfile
Section titled “3. Deployment: Dockerfile”To run this polyglot architecture, we need a container environment that supports both Python and Node.js.
# Start with a lightweight Python imageFROM python:3.11-slim
# 1. Install Node.js and NPM# We use apt-get to pull the standard nodejs packageRUN apt-get update && \ apt-get install -y nodejs npm && \ rm -rf /var/lib/apt/lists/*
# 2. Set working directoryWORKDIR /app
# 3. Install Python dependencies# 'fastmcp' handles the server, 'uvicorn' is required for SSE transportRUN pip install fastmcp uvicorn
# 4. Install Node.js dependencies# We initialize a temporary package.json and install 'soap'RUN npm init -y && npm install soap
# 5. Copy application codeCOPY server.py .COPY soap_client.js .
# 6. Expose the port for Railway/DockerEXPOSE 8000
# 7. Start the Python HostCMD ["python", "server.py"]4. Connecting CrewAI
Section titled “4. Connecting CrewAI”Once the Docker container is running (e.g., on localhost:8000), you can connect your CrewAI agent to it via the Model Context Protocol (MCP).
from crewai import Agent, Task, Crew
# Initialize the Agent with access to the SOAP Bridgelegacy_architect = Agent( role="Legacy Systems Engineer", goal="Retrieve customer data from the old SOAP CRM", backstory="You are an expert in WSDLs and XML protocols.", verbose=True, allow_delegation=False, # CONNECTIVITY: # This URL connects to the Docker container's SSE stream mcps=["http://localhost:8000/sse"])
soap_task = Task( description=( "Access the SOAP service at 'http://example.com/crm?wsdl'. " "Call the 'GetCustomerById' operation with id='CUST-9921'. " "Return the raw JSON response." ), agent=legacy_architect, expected_output="A JSON object containing customer details.")
crew = Crew( agents=[legacy_architect], tasks=[soap_task])
result = crew.kickoff()print(result)Why this pattern?
Section titled “Why this pattern?”- Isolation: The Node.js
soaplibrary handles XML quirks better than many Python alternatives. - Performance:
fastmcphandles the high-level agent communication, while Node.js handles the I/O bound network request efficiently. - Portability: Wrapping both in Docker ensures that system-level dependencies (like Node versions) don’t conflict with your AI Agent’s Python environment.
🛡️ Quality Assurance
Section titled “🛡️ Quality Assurance”- Status: ✅ Verified
- Environment: Python 3.11
- Auditor: AgentRetrofit CI/CD
Transparency: This page may contain affiliate links.