Skip to content

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.

  1. LangGraph Agent: Orchestrates the reporting workflow (e.g., “Analyze Q3 sales for Customer X”).
  2. MCP Server: A Python container that translates agent tool calls into SAP OData HTTP requests.
  3. SAP Gateway: The interface on the ECC server that exposes BAPIs (Business Application Programming Interfaces) as REST endpoints.

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 os
import requests
from fastmcp import FastMCP
from typing import List, Dict, Optional
# Initialize the FastMCP server
mcp = 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 ID
SAP_AUTH = (os.getenv("SAP_USER", "remote_agent"), os.getenv("SAP_PASSWORD", "password123"))
# Common headers for SAP OData JSON format
HEADERS = {
"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()

This Dockerfile is optimized for Railway/Cloud deployment. It includes standard Python libraries and exposes the required port.

# Use a lightweight Python base image
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install dependencies
# 'fastmcp' is the MCP server framework
# 'requests' handles HTTP OData calls to SAP
RUN pip install --no-cache-dir fastmcp requests
# Copy server code
COPY server.py .
# Environment variables should be injected at runtime, but we set defaults here
ENV PORT=8000
ENV SAP_HOST="https://sap-ecc-gateway.internal"
# EXPOSE 8000 is critical for Railway compatibility
EXPOSE 8000
# Run the MCP server
ENTRYPOINT ["python", "server.py"]

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.

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 ToolNode
from langchain_openai import ChatOpenAI
from 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 Model
llm = ChatOpenAI(model="gpt-4-turbo")
# Bind tools to the LLM
model_with_tools = llm.bind_tools(tools)
# Create the Tool Node
tool_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'

When integrating agents with SAP ECC, you will encounter specific challenges:

  • Leading Zeros: SAP Material numbers often require padding (e.g., input 123 must be sent as 000000000000000123). You may need to add a helper function in your server.py to zfill(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.

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

Transparency: This page may contain affiliate links.