Skip to main content

spec2openapi

Convert legacy API specifications — SOAP/WSDL and Swagger 2.0 — into FastMCP-ready OpenAPI 3.x documents.

CI PyPI Python License: Apache-2.0 Code of Conduct

한국어 문서 (Korean README)


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 two inputs produce two kinds of output — this distinction matters:

  • Swagger 2.0 → a plain, standard OpenAPI 3.x document. The paths are the real REST endpoints. Any OpenAPI-driven runtime (FastMCP, or your own httpx-based server) serves it with zero runtime changes — just point it at the converted spec.
  • WSDL → OpenAPI 3.x + an x-soap contract. The generated /operations/... paths are not real REST endpoints; each tool call must be serialized to a SOAP envelope, sent to the SOAP endpoint, and the XML response parsed back to JSON. That logic is not part of a standard OpenAPI runtime — it lives in the SOAP bridge shipped in the [mcp] extra. Serving a SOAP-converted spec with a plain OpenAPI/httpx runtime will POST JSON to the SOAP endpoint and fail every call.

So spec2openapi is a converter for Swagger 2.0, and a converter + runtime contract (with a reference bridge) for SOAP. See How SOAP calls work below.

The fixed-runtime deployment model — build one image, swap the spec via a Kubernetes ConfigMap to mass-produce MCP servers — applies to both, as long as the image includes the [mcp] extra when serving SOAP specs.

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 (flattened complexContent extensions), 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), and xsd:annotation documentation are extracted (including from xsd:import-ed schemas) so LLMs see well-described, well-constrained tool arguments.
  • x-soap contract — SOAPAction, SOAP version, endpoint, wrapper element QNames, soap:header parts and declared faults are embedded as vendor extensions; OpenAPI xml annotations 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, collectionFormatstyle/explode, $ref rewriting, security schemes, type: file, x-nullable, discriminator). Every assumption made for missing information is recorded in x-s2o.assumptions; untranslatable constructs are preserved as x- extensions and listed in x-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 validate proves it: static checks, openapi-spec-validator, and a real FastMCP.from_openapi() round-trip listing the resulting tools.
  • SOAP bridge — required to serve SOAP specspip install "spec2openapi[mcp]" adds the bridge (custom httpx transport) that implements the x-soap contract, plus FastMCP glue, a fixed Dockerfile, and Kubernetes examples. SOAP faults map to MCP tool errors. Swagger-converted (pure REST) specs do not need this — any OpenAPI runtime serves them. Only SOAP-converted specs require the bridge at runtime.

Installation

pip install spec2openapi          # converter + CLI (zeep, lxml, PyYAML)
pip install "spec2openapi[mcp]"   # + SOAP bridge & runtime — required to serve SOAP specs

The core install is enough to convert any spec and to serve Swagger-converted (REST) specs from your own runtime. The [mcp] extra is required only to serve SOAP-converted specs (it provides the bridge that turns JSON tool calls into SOAP envelopes).

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 (via spec2openapi serve) or as the reference for your own runtime. There is no way to serve a SOAP-converted spec without an implementation of this contract; a standard OpenAPI runtime cannot do it.

Mixed SOAP + REST specs. The reference runtime routes all traffic through the SOAP bridge if any path carries x-soap, so REST operations in a mixed spec are not served correctly today. Keep SOAP and REST specs separate until this is addressed (tracking issue).

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:

  1. Deterministic, documented defaults — missing consumes/producesapplication/json; missing operationId{method}_{path}; missing host → relative server /; missing schemeshttps. All recorded in x-s2o.assumptions.
  2. Preserve, never drop — constructs with no OpenAPI 3 equivalent (e.g. collectionFormat: tsv) are kept as x- extensions and listed in x-s2o.lossy.
  3. Verify the outcomespec2openapi validate runs 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.2.1 .
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 (104 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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spec2openapi-0.2.1.tar.gz (61.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spec2openapi-0.2.1-py3-none-any.whl (46.2 kB view details)

Uploaded Python 3

File details

Details for the file spec2openapi-0.2.1.tar.gz.

File metadata

  • Download URL: spec2openapi-0.2.1.tar.gz
  • Upload date:
  • Size: 61.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for spec2openapi-0.2.1.tar.gz
Algorithm Hash digest
SHA256 2e25b204d67ebd8ceb825d0e5ccd705cd95d059d0a1925d224933d3e46d209c5
MD5 fc243d55ba111f0a723dd71ffdbaab9b
BLAKE2b-256 0601b143e2946646d077771535b8d7249ea9d5547ee9892969b5234874f7c7cf

See more details on using hashes here.

File details

Details for the file spec2openapi-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: spec2openapi-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 46.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for spec2openapi-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6b752e4f7e8cdcc2151182e9019a32967a62a13d40d35e5107124f61bb37a3f6
MD5 ab9abbdc4b2494aaeb2b7a36651ecc64
BLAKE2b-256 03c6b6acceb162c0d0ec3c449a6cec3e94dad4bcaaeec077ccb53c447b8d5e3c

See more details on using hashes here.

Release history Release notifications | RSS feed

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.2

2 files

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page