{"slug": "i-made-legacy-soap-apis-usable-by-ai-agents", "title": "I made legacy SOAP APIs usable by AI agents", "summary": "Developer B. Venkata released legacy2mcp, an open-source tool that converts legacy SOAP/WSDL services into Model Context Protocol (MCP) servers, enabling AI agents to call them without hand-written adapters. The tool generates JSON Schema from WSDL XSD types, validates every call, excludes write operations by default, and logs all activity. It is available via pip and on the MCP Registry as io.github.bvenkata/legacy2mcp.", "body_md": "**Turn a legacy SOAP/WSDL system into a safe, typed [MCP](https://modelcontextprotocol.io/) server in minutes — so an AI agent can call it without a hand-written adapter.**\n\nPoint `legacy2mcp` at a WSDL URL. It introspects every operation, generates a real\nJSON Schema for each one from the WSDL's own XSD types, and exposes them as MCP\ntools that any MCP client (Claude Desktop, an agent framework, your own code) can\ncall — with **every call schema-validated before it reaches your SOAP endpoint**,\n**write-like operations excluded by default**, and **every call audit-logged**.\n\nNo hand-written adapter code. No hand-maintained tool schemas that drift from the WSDL. No arbitrary calls the WSDL itself doesn't define.\n\n<sub>Regenerate this clip with `vhs demo/demo.tape` — see [`demo/`](/bvenkata/legacy2mcp/blob/main/demo).</sub>\n\nOrganizations run 10–20 year old SOAP services that aren't going away — systems of record, middleware, back-office and line-of-business platforms. More and more teams now want to point an AI agent at these systems.\n\nToday that means, per WSDL:\n\n- hand-writing a bespoke adapter,\n- guessing at input validation,\n- hand-copying tool schemas that immediately start drifting from the service,\n- and hoping nobody points an LLM at `DeleteRecord` .\n\n`legacy2mcp` generates the adapter **from the WSDL itself**, so the tool schema\ncan never drift from what the service actually accepts, and ships a\n**safe-by-default posture** (no writes without an explicit opt-in, no unvalidated\narguments, every call logged) instead of leaving that to whoever wrote the last\nadapter.\n\n- **Zero adapter code** — one MCP tool per WSDL operation, named`<adapter_id>_<Operation>` .\n- **Real schemas from the WSDL's XSD** — simple types, nested complex types, enums,\nand repeated elements (arrays) are all handled recursively, depth-limited for\npathological WSDLs.\n- **Safety net #1: validation** — every call runs through`jsonschema.validate` (with`additionalProperties: false` ) before any network call.\n- **Safety net #2: read-only by default** — operations whose names look like\nwrites (`Create*` ,`Update*` ,`Delete*` ,`Cancel*` ,`Submit*` ,`Pay*` , …) are\nnot exposed unless you set`allow_write_operations: true` .\n- **Explicit allow/deny lists** —`include_operations` /`exclude_operations` on\ntop of the heuristic.\n- **Audit log** — one JSON line per call: tool, arguments, timestamp, outcome.\n- **Secrets stay out of config** — passwords are read from named environment\nvariables, never written into the YAML.\n- **CI-friendly dry run** —`legacy2mcp inspect` lists the generated tools and\nexits, so a broken WSDL fails your pipeline instead of your production agent.\n\nSee [docs/security.md](/bvenkata/legacy2mcp/blob/main/docs/security.md) for the full, honest security model —\nwhat's covered today and what isn't yet.\n\n```\npip install legacy2mcp          # or: uv tool install legacy2mcp / pipx install legacy2mcp\n```\n\nAlso on the [MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.bvenkata/legacy2mcp)\nas `io.github.bvenkata/legacy2mcp`, so MCP-aware clients that read the registry\ncan discover it directly.\n\n```\ngit clone https://github.com/bvenkata/legacy2mcp.git\ncd legacy2mcp\npip install -e \".[dev]\"\n\n# 1. Start the bundled demo SOAP service (no external network needed)\npython examples/soap/run_mock_calculator.py &\n\n# 2. See the MCP tools generated from its WSDL\nlegacy2mcp inspect --config examples/soap/config.calculator.yaml\n```\n\nOr with Docker:\n\n```\ndocker compose up demo-soap-service -d\ndocker compose run --rm legacy2mcp legacy2mcp inspect \\\n  --config examples/soap/config.calculator.docker.yaml\n# config.yaml\nserver:\n  name: my-legacy-mcp\n\nadapters:\n  - id: legacy\n    type: soap\n    config:\n      wsdl_url: \"https://service.example.com/LegacyService?wsdl\"\n      auth:\n        type: basic\n        username: \"svc-account\"\n        password_env: \"SERVICE_PASSWORD\"\n      # Safe by default: Create*/Update*/Delete*/Cancel*/Submit*/... are\n      # excluded unless you opt in explicitly.\n      allow_write_operations: false\n      # Recommended for production: enumerate exactly what the agent may call.\n      include_operations: [\"GetRecord\", \"GetRecordDetails\", \"SearchRecords\"]\n\nsecurity:\n  audit:\n    enabled: true\n    path: \"./legacy-mcp-audit.log\"\nexport SERVICE_PASSWORD=...\nlegacy2mcp inspect --config config.yaml   # review the generated tools\nlegacy2mcp run     --config config.yaml   # start the MCP server (stdio)\n```\n\nA full production-shaped template lives at\n[`examples/soap/config.template.yaml`](/bvenkata/legacy2mcp/blob/main/examples/soap/config.template.yaml).\n\n```\n{\n  \"mcpServers\": {\n    \"legacy\": {\n      \"command\": \"legacy2mcp\",\n      \"args\": [\"run\", \"--config\", \"/absolute/path/to/config.yaml\"]\n    }\n  }\n}\n```\n\n- **Systems of record** — let an agent read status and detail records from a\nlegacy back-office platform, read-only, with every lookup audit-logged.\n- **Financial services** — expose account and transaction*reads* to an agent\nwithout exposing transfers or adjustments.\n- **Supply chain / ERP** — surface order status, inventory, and shipment tracking\nfrom an old SOAP middleware layer.\n- **Internal support tooling** — give a support copilot safe, typed access to the\nsystem of record instead of a scraped UI.\n- **Migration & modernization** — put an MCP layer in front of a legacy service\nnow, and swap the backend later without touching the agent.\n\n`legacy2mcp inspect` loads the config, contacts the WSDL, builds every tool\nschema, and exits non-zero if anything fails. Run it as a pipeline gate:\n\n```\n# .github/workflows/contract-check.yml\n- name: Check the WSDL still generates valid MCP tools\n  env:\n    SERVICE_PASSWORD: ${{ secrets.SERVICE_PASSWORD }}\n  run: |\n    pip install legacy2mcp\n    legacy2mcp inspect --config config/legacy.yaml > tools.json\n    # optionally: diff tools.json against a committed snapshot to catch\n    # a backend team changing an operation's contract out from under you\n    git diff --exit-code --no-index tools/legacy.snapshot.json tools.json\n```\n\n`legacy2mcp run` speaks MCP over stdio — the transport Claude Desktop and most\nagent frameworks spawn servers over. Package it with your config in the provided\n`Dockerfile` and let your MCP client launch it.\n\nUse the same generated, validated tools from your own Python (via any MCP client library) to pull records from the legacy system on a schedule, with the audit log giving you a record of exactly what was fetched.\n\n1. Loads the WSDL with [`zeep`](https://docs.python-zeep.org/) , a mature, widely\nused Python SOAP client.\n2. For every operation on every port/binding, converts the WSDL's XSD input type\ninto a JSON Schema\n([`src/legacy2mcp/schema/xsd_to_jsonschema.py`](/bvenkata/legacy2mcp/blob/main/src/legacy2mcp/schema/xsd_to_jsonschema.py) ) —\nsimple types, nested complex types, enums, and arrays, recursively.\n3. Registers one MCP tool per operation, named `<adapter_id>_<OperationName>` .\n4. On a tool call: validates arguments against that operation's JSON Schema,\ncalls the SOAP operation via `zeep` , serializes the response back to plain\nJSON, and writes an audit log entry.\n5. Operations whose names look like writes are excluded unless\n`allow_write_operations: true` — see[docs/security.md](/bvenkata/legacy2mcp/blob/main/docs/security.md) for\nexactly what this heuristic does and doesn't catch.\n\n**v0.1** — the SOAP/WSDL adapter is implemented and tested (`pytest tests/` runs\nagainst an in-process mock SOAP service, no network needed). A database adapter\n(safe, parameterized-query-only, table/operation allowlists) and a queue adapter\n(Kafka/RabbitMQ/SQS) are on the [roadmap](/bvenkata/legacy2mcp/blob/main/docs/roadmap.md) but **not implemented\nyet** — the `BaseAdapter` interface\n([`src/legacy2mcp/adapters/base.py`](/bvenkata/legacy2mcp/blob/main/src/legacy2mcp/adapters/base.py)) is the\nextension point if you want to build one.\n\n```\npip install -e \".[dev]\"\npytest tests/ -v\n```\n\nCI runs the suite on Python 3.10–3.12 ([`.github/workflows/ci.yml`](/bvenkata/legacy2mcp/blob/main/.github/workflows/ci.yml)).\nReleases to PyPI and the MCP Registry are tag-triggered — see\n[docs/releasing.md](/bvenkata/legacy2mcp/blob/main/docs/releasing.md).\n\nAdapters for new legacy systems are the highest-value contribution — implement\n`BaseAdapter` (`discover_tools()` + `invoke()`) and the MCP server core handles\nvalidation, dispatch, and audit logging for you automatically. Issues and PRs\nwelcome.\n\nApache 2.0 — see [LICENSE](/bvenkata/legacy2mcp/blob/main/LICENSE).", "url": "https://wpnews.pro/news/i-made-legacy-soap-apis-usable-by-ai-agents", "canonical_source": "https://github.com/bvenkata/legacy2mcp", "published_at": "2026-09-07 04:20:10+00:00", "updated_at": "2026-09-07 04:57:13.167208+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents", "ai-infrastructure"], "entities": ["legacy2mcp", "B. Venkata", "MCP", "SOAP", "WSDL", "Claude Desktop", "MCP Registry"], "alternates": {"html": "https://wpnews.pro/news/i-made-legacy-soap-apis-usable-by-ai-agents", "markdown": "https://wpnews.pro/news/i-made-legacy-soap-apis-usable-by-ai-agents.md", "text": "https://wpnews.pro/news/i-made-legacy-soap-apis-usable-by-ai-agents.txt", "jsonld": "https://wpnews.pro/news/i-made-legacy-soap-apis-usable-by-ai-agents.jsonld"}}