Skip to content

Securing AI Agent access to legacy SOAP APIs (Python)

Securing AI Agent access to legacy SOAP APIs (Python)

Section titled “Securing AI Agent access to legacy SOAP APIs (Python)”

Connecting modern AI agents (CrewAI, LangGraph, OpenAI Operator) to legacy SOAP web services requires more than just parsing XML. It requires navigating the complex security layers that defined the “Service Oriented Architecture” (SOA) era—specifically WS-Security (WSSE) and strict firewall boundaries.

This guide provides a production-ready Model Context Protocol (MCP) server that acts as a secure gateway. It handles the heavy lifting of WSDL parsing, XML serialization, and authentication (WS-Security), exposing simple, clean tools to your AI agent.

Legacy SOAP endpoints are notoriously hostile to LLMs. They require verbose XML payloads and rigid headers. By placing an MCP server in the middle, we create a “Smart Proxy”:

  1. Agent sends a JSON request (e.g., get_user_balance(id="123")).
  2. MCP Server translates this into a SOAP Envelope with WS-Security headers.
  3. Legacy API returns an XML response.
  4. MCP Server parses the XML into a JSON dictionary for the Agent.

We use FastMCP for the agent interface and Zeep for the SOAP protocol handling. Zeep is the industry standard for Python SOAP clients, offering robust support for WSDL caching and WS-Security.

This server implements a dynamic SOAP client. It authenticates using UsernameToken (a common legacy standard) and is configured to route traffic through enterprise proxies if needed.

from fastmcp import FastMCP
from zeep import Client
from zeep.transports import Transport
from zeep.wsse.username import UsernameToken
from zeep.helpers import serialize_object
import requests
import json
import os
# Initialize the FastMCP server
mcp = FastMCP("SecureSOAPGateway")
@mcp.tool()
def query_soap_service(
wsdl_url: str,
operation: str,
username: str,
password: str,
args: dict = None
) -> str:
"""
Securely executes a SOAP operation against a legacy WSDL endpoint.
Args:
wsdl_url: The full URL to the WSDL file (e.g., http://legacy-erp.local/service?wsdl)
operation: The specific SOAP method to call (e.g., 'GetUserBalance')
username: WS-Security Username
password: WS-Security Password
args: Dictionary of arguments to pass to the SOAP method
Returns:
JSON string containing the serialized response.
"""
if args is None:
args = {}
# 1. Configure the Transport layer (Proxies & Sessions)
session = requests.Session()
# For production, inject BrightData proxy URL here
# proxies = {
# 'http': 'http://user:pass@brd.superproxy.io:22225',
# 'https': 'http://user:pass@brd.superproxy.io:22225',
# }
# session.proxies.update(proxies)
# Disable SSL verification if dealing with internal self-signed certs (Common in legacy)
# session.verify = False
transport = Transport(session=session)
# 2. Configure WS-Security
# Many legacy systems require the PasswordText or PasswordDigest authentication method.
wsse = UsernameToken(username, password)
try:
# 3. Initialize the Zeep Client
# strict=False allows for loose parsing of non-compliant legacy XML
client = Client(
wsdl=wsdl_url,
transport=transport,
wsse=wsse,
strict=False
)
# 4. Locate the operation dynamically
service_proxy = client.service
if not hasattr(service_proxy, operation):
return f"Error: Operation '{operation}' not found in WSDL."
# 5. Execute the call
# We unpack the dictionary args directly into the function
method = getattr(service_proxy, operation)
result = method(**args)
# 6. Serialize the complex zeep object to a standard Python dict/list structure
# Zeep returns custom objects that aren't JSON serializable by default
serialized_result = serialize_object(result)
return json.dumps(serialized_result, default=str)
except Exception as e:
return f"SOAP Error: {str(e)}"
if __name__ == "__main__":
mcp.run()

This Dockerfile ensures all system dependencies for XML parsing (libxml2, libxslt) are present and exposes the correct port for MCP communication.

# Use a lightweight Python base
FROM python:3.11-slim
# Install system dependencies required for lxml and zeep
RUN apt-get update && apt-get install -y \
libxml2-dev \
libxslt-dev \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Install Python libraries
# fastmcp: The Agent interface
# zeep: The SOAP client
# requests: For underlying HTTP transport
RUN pip install --no-cache-dir fastmcp zeep requests
# Copy the server code
COPY server.py .
# Expose port 8000 for Railway/Cloud compatibility
EXPOSE 8000
# Start the MCP server
CMD ["python", "server.py"]

Legacy systems rarely use modern OAuth. They rely on the WS-Security standard, which embeds authentication credentials inside the SOAP Header XML.

  • The Problem: Manually constructing XML headers is error-prone and invites injection attacks.
  • The Solution: The zeep.wsse.username.UsernameToken class automatically signs the header and handles nonce/timestamp generation if required by the server.

Enterprise SOAP endpoints often sit behind rigid IP allowlists.

  • BrightData / Residential Proxies: If your agent is running in the cloud (AWS/GCP), its IP will change. Using a static proxy service allows you to whitelist a single IP on the legacy firewall.
  • Implementation: The commented-out proxies block in server.py injects this configuration directly into the underlying requests.Session used by Zeep.

Legacy SOAP responses can be massive and deeply nested.

  • The serialize_object helper from Zeep converts these complex custom types into standard Python dictionaries and lists.
  • We use json.dumps(..., default=str) to safely handle date types (datetime.date, decimal.Decimal) that frequently break JSON serializers.
  1. Build the Image:

    Terminal window
    docker build -t soap-agent-gateway .
  2. Run the Container:

    Terminal window
    docker run -p 8000:8000 soap-agent-gateway
  3. Connect your Agent: Configure your MCP client (e.g., in Claude Desktop or your custom LangChain script) to connect to http://localhost:8000/sse. The agent will now see the query_soap_service tool and can autonomously figure out how to call methods defined in your WSDL.


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

Transparency: This page may contain affiliate links.