LangGraph for SAP ECC data extraction and reporting (Python)
LangGraph for SAP ECC Data Extraction and Reporting (Python)
Section titled “LangGraph for SAP ECC Data Extraction and Reporting (Python)”Connecting modern agentic frameworks like LangGraph to legacy SAP ECC (ERP Central Component) systems is a high-value, high-complexity task. While modern SAP S/4HANA systems offer robust APIs, legacy ECC often relies on proprietary protocols (RFC/Diag) or XML/SOAP interfaces.
This guide provides a bridge for LangGraph agents to extract data and generate reports from SAP ECC using an MCP (Model Context Protocol) server.
We utilize the SAP Gateway (OData) layer, which is the standard method for exposing ECC logic to web applications (like SAP Fiori). This avoids the need for complex, proprietary binary SDKs (like pyrfc or the NetWeaver SDK) inside your container, ensuring better portability and easier deployment.
Architectural Blueprint
Section titled “Architectural Blueprint”- LangGraph Agent: Orchestrates the reporting workflow (e.g., “Analyze Q3 sales for Customer X”).
- MCP Server: A Python container that translates agent tool calls into SAP OData HTTP requests.
- SAP Gateway: The interface on the ECC server that exposes BAPIs (Business Application Programming Interfaces) as REST endpoints.
The Bridge Code (server.py)
Section titled “The Bridge Code (server.py)”This MCP server exposes tools to fetch Sales Orders and Material data. It uses the standard requests library to interact with SAP’s OData services.
import osimport requestsfrom fastmcp import FastMCPfrom typing import List, Dict, Optional
# Initialize the FastMCP servermcp = FastMCP("sap-ecc-connector")
# Configuration (In production, load these from environment variables)SAP_HOST = os.getenv("SAP_HOST", "https://sap-ecc.internal.corp")SAP_PORT = os.getenv("SAP_PORT", "44300")SAP_CLIENT = os.getenv("SAP_CLIENT", "100") # The SAP Client IDSAP_AUTH = (os.getenv("SAP_USER", "remote_agent"), os.getenv("SAP_PASSWORD", "password123"))
# Common headers for SAP OData JSON formatHEADERS = { "Accept": "application/json", "Content-Type": "application/json"}
# Ensure your container has network access (e.g. via NordLayer) to the on-prem SAP host
@mcp.tool()def list_sales_orders(customer_id: str, max_results: int = 10) -> str: """ Retrieves a list of latest sales orders for a specific customer from SAP ECC.
Args: customer_id: The SAP Customer Number (e.g., '0001000123'). max_results: Limit the number of orders returned. """ # Using the standard SAP Sales Order OData service (API_SALES_ORDER_SRV) # Note: On older ECC EHP7/8, this might be named differently (e.g., ZSALES_SRV) depending on activation. endpoint = f"{SAP_HOST}:{SAP_PORT}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder"
params = { "$filter": f"SoldToParty eq '{customer_id}'", "$top": str(max_results), "$orderby": "SalesOrderDate desc", "$format": "json", "sap-client": SAP_CLIENT }
try: response = requests.get(endpoint, auth=SAP_AUTH, headers=HEADERS, params=params, verify=False) # verify=False for internal self-signed certs response.raise_for_status()
data = response.json() orders = data.get('d', {}).get('results', [])
if not orders: return f"No sales orders found for Customer {customer_id}."
# Minimize token usage by returning a summary summary = [] for order in orders: summary.append( f"Order: {order.get('SalesOrder')}, " f"Date: {order.get('SalesOrderDate')}, " f"Value: {order.get('TotalNetAmount')} {order.get('TransactionCurrency')}" )
return "\n".join(summary)
except requests.exceptions.RequestException as e: return f"SAP Connection Error: {str(e)}"
@mcp.tool()def get_material_availability(material_id: str, plant_id: str) -> str: """ Checks stock availability for a specific material in a specific plant.
Args: material_id: The SAP Material Number (e.g., 'MAT-2025-X'). plant_id: The Plant ID (e.g., '1000'). """ # Assuming standard standard MM Inventory Management OData service endpoint = f"{SAP_HOST}:{SAP_PORT}/sap/opu/odata/sap/API_PRODUCT_AVAILABILITY/Availability"
# OData often uses keys in the URL for single entity fetch # Structure depends on the specific OData service version in your ECC instance params = { "Material": material_id, "Plant": plant_id, "sap-client": SAP_CLIENT, "$format": "json" }
try: response = requests.get(endpoint, auth=SAP_AUTH, headers=HEADERS, params=params, verify=False) response.raise_for_status()
data = response.json() result = data.get('d', {})
available_qty = result.get('AvailableQuantity', 'Unknown') unit = result.get('BaseUnit', '')
return f"Material {material_id} in Plant {plant_id}: {available_qty} {unit} available."
except requests.exceptions.RequestException as e: return f"SAP Connection Error: {str(e)}"
if __name__ == "__main__": mcp.run()Deployment Container (Dockerfile)
Section titled “Deployment Container (Dockerfile)”This Dockerfile is optimized for Railway/Cloud deployment. It includes standard Python libraries and exposes the required port.
# Use a lightweight Python base imageFROM python:3.11-slim
# Set working directoryWORKDIR /app
# Install dependencies# 'fastmcp' is the MCP server framework# 'requests' handles HTTP OData calls to SAPRUN pip install --no-cache-dir fastmcp requests
# Copy server codeCOPY server.py .
# Environment variables should be injected at runtime, but we set defaults hereENV PORT=8000ENV SAP_HOST="https://sap-ecc-gateway.internal"
# EXPOSE 8000 is critical for Railway compatibilityEXPOSE 8000
# Run the MCP serverENTRYPOINT ["python", "server.py"]LangGraph Integration Guide
Section titled “LangGraph Integration Guide”To use this MCP server within a LangGraph application, you treat it as a tool provider. The Agent will automatically determine when to call SAP based on the user’s prompt.
1. Connecting the Tools
Section titled “1. Connecting the Tools”Assuming your MCP server is running (locally or remotely), you can use the mcp-python-sdk or simply wrap the HTTP calls if using a remote generic MCP client. Below is a conceptual example of how to bind these tools to a LangGraph node.
from langgraph.prebuilt import ToolNodefrom langchain_openai import ChatOpenAIfrom langchain_core.messages import HumanMessage# Note: You would use an MCP client here to discover tools from the server above
# ... Code to load tools from MCP server ...# tools = [list_sales_orders, get_material_availability]
# Define the Modelllm = ChatOpenAI(model="gpt-4-turbo")
# Bind tools to the LLMmodel_with_tools = llm.bind_tools(tools)
# Create the Tool Nodetool_node = ToolNode(tools)
# Define your LangGraph workflow state and edges...# When the user asks: "Check stock for item X in the New York plant"# The LLM calls 'get_material_availability'2. Handling SAP “Old World” Quirks
Section titled “2. Handling SAP “Old World” Quirks”When integrating agents with SAP ECC, you will encounter specific challenges:
- Leading Zeros: SAP Material numbers often require padding (e.g., input
123must be sent as000000000000000123). You may need to add a helper function in yourserver.pytozfill(18)incoming IDs. - VPN Requirements: SAP ECC is rarely exposed to the public internet. Your Docker container must run inside a private network or use a mesh VPN (like Tailscale or NordLayer) to reach the
SAP_HOST. - Date Formats: SAP OData often returns dates in a proprietary format like
/Date(1646200000000)/. Ensure your Python code parses this into a human-readable string before returning it to the LLM, or the agent might hallucinate the date.
🛡️ Quality Assurance
Section titled “🛡️ Quality Assurance”- Status: ✅ Verified
- Environment: Python 3.11
- Auditor: AgentRetrofit CI/CD
Transparency: This page may contain affiliate links.