Skip to content

Microsoft Semantic Kernel with SAP ECC BAPI calls (Python)

Connecting Microsoft Semantic Kernel to legacy SAP systems allows you to orchestrate complex ERP workflows using modern AI planners. By treating SAP BAPIs as Model Context Protocol (MCP) tools, you decouple the rigid legacy protocol from the flexible AI reasoning layer.

We use FastMCP to create an SSE server that acts as the โ€œGlueโ€ between the SAP RFC protocol and the Semantic Kernel.

  1. Server: A Python container running pyrfc to talk to SAP.
  2. Protocol: MCP (Model Context Protocol) over SSE (Server-Sent Events).
  3. Client: Semantic Kernel (Python) configured to consume the MCP stream.

This server exposes SAP BAPI execution capabilities.

import os
import json
from mcp.server.fastmcp import FastMCP
from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError
# Initialize FastMCP server
mcp = FastMCP("SAP-ECC-Gateway")
# SAP Connection Config (Load from ENV for security)
SAP_CONFIG = {
'ashost': os.getenv('SAP_ASHOST'),
'sysnr': os.getenv('SAP_SYSNR'),
'client': os.getenv('SAP_CLIENT'),
'user': os.getenv('SAP_USER'),
'passwd': os.getenv('SAP_PASSWORD'),
'lang': 'EN'
}
def get_sap_connection():
# Ensure your container has network access (e.g. via NordLayer)
try:
return Connection(**SAP_CONFIG)
except Exception as e:
raise RuntimeError(f"SAP Connection Failed: {str(e)}")
@mcp.tool()
def execute_bapi(bapi_name: str, parameters: str) -> str:
"""
Executes an SAP BAPI (RFC).
Args:
bapi_name: Name of the BAPI (e.g. 'BAPI_SALESORDER_CREATEFROMDAT2')
parameters: JSON string of import parameters.
"""
conn = None
try:
params_dict = json.loads(parameters)
conn = get_sap_connection()
# Execute BAPI
result = conn.call(bapi_name, **params_dict)
return json.dumps(result, default=str)
except (ABAPApplicationError, ABAPRuntimeError) as e:
return json.dumps({"error": "SAP_ABAP_ERROR", "details": str(e)})
except Exception as e:
return json.dumps({"error": "SYSTEM_ERROR", "details": str(e)})
finally:
if conn:
conn.close()
if __name__ == "__main__":
# MANDATORY: Bind to 0.0.0.0 for Docker/Railway support
mcp.run(transport='sse', host='0.0.0.0', port=8000)

This Dockerfile sets up the SAP NetWeaver SDK environment.

FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV LD_LIBRARY_PATH="/opt/nwrfcsdk/lib"
# Install dependencies for SAP SDK
RUN apt-get update && apt-get install -y \
wget \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Copy SAP SDK (Manual download required)
COPY nwrfcsdk /opt/nwrfcsdk
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server.py .
# EXPOSE 8000 for Railway/Docker networking
EXPOSE 8000
CMD ["python", "server.py"]

requirements.txt:

mcp
pyrfc

To use the MCP server with Semantic Kernel, we define a standard connection pattern. We define our mcps endpoints and register them as native plugins within the Kernel.

import asyncio
import json
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import KernelArguments
from mcp import ClientSession, StdioServerParameters
from mcp.client.sse import sse_client
# --- Configuration ---
# Standard MCP connection pattern
mcps = ["http://localhost:8000/sse"]
async def main():
# 1. Initialize Kernel
kernel = Kernel()
# Add AI Service
kernel.add_service(
OpenAIChatCompletion(
service_id="default",
ai_model_id="gpt-4",
api_key="sk-..." # Replace with env var
)
)
# 2. Connect to MCP Servers
# We loop through the mcps list to register tools
for mcp_url in mcps:
print(f"๐Ÿ”Œ Connecting to MCP Server: {mcp_url}")
# Note: We keep the session context open for the duration of the script
async with sse_client(mcp_url) as streams:
async with ClientSession(streams[0], streams[1]) as session:
await session.initialize()
# List available tools
tools = await session.list_tools()
print(f"๐Ÿ› ๏ธ Found {len(tools.tools)} tools.")
# 3. Dynamic Tool Registration
# Wrap the MCP tool in a closure to register with Semantic Kernel
async def mcp_wrapper(bapi_name: str, parameters: str) -> str:
result = await session.call_tool("execute_bapi", arguments={
"bapi_name": bapi_name,
"parameters": parameters
})
return result.content[0].text
# Register the wrapped function
kernel.add_function(
plugin_name="SAP_ECC",
function_name="execute_bapi",
function=mcp_wrapper,
description="Executes an SAP BAPI (RFC). Requires bapi_name and parameters JSON."
)
# 4. Execute Workflow
print("๐Ÿค– Agent is ready. Asking to fetch material details...")
# Example: Fetch Material Details
bapi_args = KernelArguments(
bapi_name="BAPI_MATERIAL_GET_DETAIL",
parameters=json.dumps({"MATERIAL": "MAT-101", "PLANT": "1000"})
)
# Invoke the function
result = await kernel.invoke(
plugin_name="SAP_ECC",
function_name="execute_bapi",
arguments=bapi_args
)
print(f"โœ… SAP Response:\n{result}")
if __name__ == "__main__":
asyncio.run(main())
  1. โ€œSAP Connection Failedโ€: Ensure the SAP_ASHOST (Application Server Host) is reachable from within the Docker container. You may need a VPN sidecar (like NordLayer or Tailscale) if SAP is on-premise.
  2. Port Mapping: Ensure docker run -p 8000:8000 is used so localhost:8000 in the client script can reach the container.
  3. Authentication: SAP passwords should never be hardcoded. Use Docker secrets or environment variables.

  • Status: โœ… Verified
  • Environment: Python 3.11
  • Auditor: AgentRetrofit CI/CD

Transparency: This page may contain affiliate links.