Convert legacy API specs (SOAP/WSDL, Swagger 2.0) into FastMCP-ready OpenAPI 3.x documents
Project description
spec2openapi
Convert legacy API specifications — SOAP/WSDL and Swagger 2.0 — into FastMCP-ready OpenAPI 3.x documents.
MCP (Model Context Protocol) tooling such as FastMCP can turn an OpenAPI 3.x document into an MCP server automatically — but enterprises are full of services described only by WSDL or Swagger 2.0. spec2openapi closes that gap:
WSDL ─────────┐
├──(spec2openapi)──> OpenAPI 3.x (+ x-soap extensions) ──> FastMCP.from_openapi() ──> MCP tools
Swagger 2.0 ──┘
The output is a single, ordinary OpenAPI document. It is designed for a fixed-runtime deployment model: build one FastMCP-based container image, then swap the spec (e.g. a Kubernetes ConfigMap) to mass-produce MCP servers without rebuilding anything.
Features
- WSDL → OpenAPI 3.0/3.1 — document/literal and rpc/literal bindings, SOAP 1.1/1.2, nested complex types, arrays, attributes,
nillable, inheritance (flattenedcomplexContentextensions),simpleContent(text value + attributes),choice(members become optional +x-soap-choice), default values, recursive types, multi-service/multi-port WSDLs with automatic dedup. - XSD facets & docs carried into tool schemas — enumerations,
pattern, length and numeric bounds,fractionDigits(→multipleOf), andxsd:annotationdocumentation are extracted (including fromxsd:import-ed schemas) so LLMs see well-described, well-constrained tool arguments. x-soapcontract — SOAPAction, SOAP version, endpoint, wrapper element QNames,soap:headerparts and declared faults are embedded as vendor extensions; OpenAPIxmlannotations carry everything a call layer needs to serialize JSON ↔ literal XML.- Swagger 2.0 → OpenAPI 3.x upgrade — full mechanical mapping (servers, requestBody, formData/multipart, parameter schema wrapping,
collectionFormat→style/explode,$refrewriting, security schemes,type: file,x-nullable, discriminator). Every assumption made for missing information is recorded inx-s2o.assumptions; untranslatable constructs are preserved asx-extensions and listed inx-s2o.lossy. - FastMCP compatibility, guaranteed and verifiable — operationIds are generated in FastMCP's tool-name alphabet (
[A-Za-z0-9_], unique, ≤64 chars) so tool name == operationId.spec2openapi validateproves it: static checks,openapi-spec-validator, and a realFastMCP.from_openapi()round-trip listing the resulting tools. - Reference MCP runtime (optional) —
pip install "spec2openapi[mcp]"adds a verified SOAP bridge (custom httpx transport) + FastMCP glue, a fixed Dockerfile, and Kubernetes examples. SOAP faults map to MCP tool errors; plain REST specs are served by the same runtime.
Installation
pip install spec2openapi # converter only (zeep, lxml, PyYAML)
pip install "spec2openapi[mcp]" # + reference MCP runtime (fastmcp, httpx)
Quick start
CLI
# See what a WSDL contains (operations, headers, faults, style)
spec2openapi inspect https://legacy-host/OrderService?wsdl
# WSDL -> OpenAPI
spec2openapi convert https://legacy-host/OrderService?wsdl -o orders.openapi.yaml
# Swagger 2.0 -> OpenAPI 3.x (assumptions reported on stderr)
spec2openapi upgrade swagger2.json -o service.openapi.yaml
# Prove the spec converts cleanly into MCP tools
spec2openapi validate orders.openapi.yaml
# Reference MCP runtime (requires the [mcp] extra)
spec2openapi serve orders.openapi.yaml --transport http --port 8000
$ spec2openapi validate orders.openapi.yaml
operations : 2
component schemas : 3
openapi-spec-validator: OK
FastMCP round-trip: OK (2 tools)
- CreateOrder(customer, items, note)
- GetOrder(orderId)
OK: spec is FastMCP-convertible
Library
import spec2openapi
# WSDL -> OpenAPI dict
spec = spec2openapi.convert_wsdl("https://legacy-host/OrderService?wsdl")
# Swagger 2.0 -> OpenAPI dict
legacy = spec2openapi.load_spec("swagger2.json")
spec = spec2openapi.convert_swagger(legacy, openapi_version="3.1")
print(spec2openapi.dump_spec(spec)) # YAML text
# Optional [mcp] extra: run it as an MCP server right away
mcp = spec2openapi.from_openapi_spec(spec)
mcp.run(transport="http", host="0.0.0.0", port=8000)
How SOAP calls work (the x-soap contract)
The generated paths (/operations/...) are not real REST endpoints — a SOAP translation layer must build the actual call. Everything it needs ships inside the spec:
Field (paths.*.post.x-soap) |
Meaning |
|---|---|
operation / service / port |
WSDL names |
soapAction, soapVersion, style |
"1.1"/"1.2", document/rpc |
endpoint |
soap:address (override at runtime) |
input / output |
wrapper element QNames |
headers[] |
soap:header parts with schema refs |
faults[] |
declared faults with schema refs |
Serialization rules (schema xml annotations): xml.name/xml.namespace (absent namespace = unqualified), xml.attribute: true, xml.x-text: true (simpleContent text), arrays repeat the element, and property order = XSD sequence order (do not alphabetize the document). x-soap-choice lists mutually exclusive property groups.
The [mcp] extra contains a verified implementation of this contract (src/spec2openapi/bridge.py) — use it directly or as the reference for your own runtime.
Handling missing information (Swagger 2.0)
Upgrading is favorable: OpenAPI 3.x is a superset of Swagger 2.0, so almost nothing must be invented. Where documents are genuinely underspecified, a three-tier policy applies:
- Deterministic, documented defaults — missing
consumes/produces→application/json; missingoperationId→{method}_{path}; missinghost→ relative server/; missingschemes→https. All recorded inx-s2o.assumptions. - Preserve, never drop — constructs with no OpenAPI 3 equivalent (e.g.
collectionFormat: tsv) are kept asx-extensions and listed inx-s2o.lossy. - Verify the outcome —
spec2openapi validateruns the actual FastMCP round-trip; assumptions never block tool generation because tools only need paths and schemas.
Kubernetes: one image, many MCP servers
docker build -t spec2openapi:0.1.0 .
spec2openapi convert <wsdl> -o openapi.yaml
kubectl create configmap my-mcp-spec --from-file=openapi.yaml
kubectl apply -f k8s/example.yaml # Deployment mounts /config/openapi.yaml
Only the ConfigMap changes per service; credentials live in a Secret (SPEC2OPENAPI_ENDPOINT, SPEC2OPENAPI_AUTH = basic|wsse, SPEC2OPENAPI_USERNAME/PASSWORD, SPEC2OPENAPI_TIMEOUT, SPEC2OPENAPI_VERIFY, SPEC2OPENAPI_TRUST_ENV). The MCP endpoint is http://<service>:8000/mcp (streamable HTTP).
Limitations
rpc/encoded (skipped and recorded in x-soap.skippedOperations), MTOM/attachments, WS-Policy/WS-Addressing, and substitution groups are not supported. WS-Security support in the reference runtime is UsernameToken (PasswordText).
Security
All XML parsing disables DTD loading, entity resolution, and parser-level network access. When converting WSDLs from untrusted sources, add --forbid-external (CLI) or forbid_external=True (API) to refuse fetching remote wsdl:/xsd: imports (SSRF mitigation; local relative imports still work). See SECURITY.md for the full notes and how to report vulnerabilities.
Development
git clone https://github.com/Seo-yul/spec2openapi.git
cd spec2openapi
pip install -e ".[dev]"
python -m pytest tests/
The suite (70 tests) covers conversion units, the Swagger upgrader, envelope (de)serialization, end-to-end MCP-tool-call → mock-SOAP-server round-trips (rpc, simpleContent, choice, recursive trees, unqualified forms), FastMCP round-trips for every fixture × OpenAPI 3.0/3.1, and stress patterns (circular $refs, deep nesting, large enums, cross-namespace name collisions, duplicate operation names across services, odd path characters, deep allOf chains). Generated samples live in examples/.
Project layout
src/spec2openapi/
parser.py WSDL parsing (zeep) + raw XSD scraping (facets/docs)
schema.py XSD -> JSON Schema (xml annotations, choice, simpleContent)
openapi.py OpenAPI 3.0/3.1 assembly + x-soap extensions
swagger.py Swagger 2.0 -> OpenAPI 3.x upgrader (x-s2o report)
convert.py core public API
cli.py convert / upgrade / inspect / validate / serve
bridge.py [mcp] SOAP bridge (httpx transport)
server.py [mcp] FastMCP glue
Contributing
Contributions are welcome — see CONTRIBUTING.md. This project follows the Contributor Covenant Code of Conduct; by participating you agree to uphold it. Security issues should be reported privately per SECURITY.md.
License
Apache-2.0 © Seoyul Yoon
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spec2openapi-0.1.0.tar.gz.
File metadata
- Download URL: spec2openapi-0.1.0.tar.gz
- Upload date:
- Size: 47.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c7fd3d8654651eaae3daa82683b883b1b41469f9d8991d553c449b57e464bd9
|
|
| MD5 |
0020c5f9ee23b375ec7f31e6a4abe46b
|
|
| BLAKE2b-256 |
3e6d832305d2a59f011ee1b14c33f758870504490758134501f0795ec0634e3d
|
File details
Details for the file spec2openapi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: spec2openapi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 39.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af1f23c65950a09a0f7d884745591963b2a3a547800b19d650e751b9d768e3a0
|
|
| MD5 |
e54402006cee2408a51d24a0f3659ed4
|
|
| BLAKE2b-256 |
3e4dcdcf6ce05e11a7af2192ca6aaa25ed39b52526a4407820ca0e98139772e8
|