Metadata-Version: 2.4
Name: spec2openapi
Version: 0.1.0
Summary: Convert legacy API specs (SOAP/WSDL, Swagger 2.0) into FastMCP-ready OpenAPI 3.x documents
Author-email: Seoyul Yoon <devops.reso@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/Seo-yul/spec2openapi
Project-URL: Repository, https://github.com/Seo-yul/spec2openapi
Project-URL: Issues, https://github.com/Seo-yul/spec2openapi/issues
Project-URL: Changelog, https://github.com/Seo-yul/spec2openapi/blob/main/CHANGELOG.md
Keywords: wsdl,soap,swagger,openapi,mcp,fastmcp,converter,legacy,api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Code Generators
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: zeep>=4.2
Requires-Dist: lxml>=4.9
Requires-Dist: PyYAML>=6.0
Provides-Extra: mcp
Requires-Dist: fastmcp>=3.0; extra == "mcp"
Requires-Dist: httpx>=0.27; extra == "mcp"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: fastmcp>=3.0; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: openapi-spec-validator>=0.7; extra == "dev"
Dynamic: license-file

# spec2openapi

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

[![CI](https://github.com/Seo-yul/spec2openapi/actions/workflows/ci.yml/badge.svg)](https://github.com/Seo-yul/spec2openapi/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/spec2openapi)](https://pypi.org/project/spec2openapi/)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-green.svg)](LICENSE)
[![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-Contributor%20Covenant-purple.svg)](CODE_OF_CONDUCT.md)

[한국어 문서 (Korean README)](README.ko.md)

---

MCP (Model Context Protocol) tooling such as [FastMCP](https://gofastmcp.com) 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 (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, `collectionFormat` → `style`/`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.
- **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

```bash
pip install spec2openapi          # converter only (zeep, lxml, PyYAML)
pip install "spec2openapi[mcp]"   # + reference MCP runtime (fastmcp, httpx)
```

## Quick start

### CLI

```bash
# 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
```

```text
$ 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

```python
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:

1. **Deterministic, documented defaults** — missing `consumes`/`produces` → `application/json`; missing `operationId` → `{method}_{path}`; missing `host` → relative server `/`; missing `schemes` → `https`. 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 outcome** — `spec2openapi 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

```bash
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](SECURITY.md) for the full notes and how to report vulnerabilities.

## Development

```bash
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 `$ref`s, deep nesting, large enums, cross-namespace name collisions, duplicate operation names across services, odd path characters, deep `allOf` chains). Generated samples live in [`examples/`](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](CONTRIBUTING.md). This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md); by participating you agree to uphold it. Security issues should be reported privately per [SECURITY.md](SECURITY.md).

## License

[Apache-2.0](LICENSE) © Seoyul Yoon
