legacy2mcp 发布:60 秒把 SOAP 或 REST API 转成 MCP server
Legacy2mcp – Convert your SOAP or REST API to an MCP server in 60 seconds
legacy2mcp 发布,可将遗留 SOAP/WSDL 服务、数据库或 REST API 在 60 秒内转换为可用的 MCP server。
从遗留的 SOAP/WSDL 服务、数据库或 REST API 到可运行的 MCP 服务器,只需 60 秒。默认只读,每次调用都有审计日志。
注意
legacy2mcp 会内省遗留系统——WSDL 中的每个操作,或数据库的实时 schema——基于该内省出的 schema为每个工具构建真正的 JSON Schema,并将它们暴露为 MCP 工具——每次调用在到达你的 SOAP 端点或数据库之前都会经过 schema 校验,默认排除类似写入/写入操作,并且每次调用都有审计日志。无需手写适配器代码,无需手工维护 schema,LLM 也无法执行任意 SQL。
目录
- 安装
- 快速开始
- 工作原理
- 为什么不直接手写一个适配器?
- 指向你自己的 WSDL
- 指向你自己的数据库
- 指向你自己的 REST API
- 自动生成配置
- 在 Claude Desktop 中使用
- 已处理的内容
- 安全模型
- 使用场景
- 实际使用
- 配置参考
- 路线图
- 开发 · 贡献 · 许可证
安装
# Full install (recommended) pip install "legacy2mcp[ai]" # Or the identical, scan-only-ready core -- AI-assisted `legacy2mcp init` # needs no extra package either way, just an API key at runtime; `[ai]` # above installs nothing extra today, it's just a stable, familiar name pip install legacy2mcp # or: uv tool install legacy2mcp · pipx install legacy2mcp
也已作为 io.github.bvenkata/legacy2mcp 发布到 MCP Registry,因此支持注册表的 MCP 客户端可以直接发现它。
快速开始
无需 API key,无需克隆——针对一个公开演示 API:
pip install legacy2mcp legacy2mcp init --rest-url "https://petstore3.swagger.io/api/v3" --out config.yaml legacy2mcp inspect --config config.yaml # see the generated MCP tools
接下来,将它连接到你的 AI 客户端——参见 在 Claude Desktop 中使用。
你有自己的 WSDL、REST API 或代码库?legacy2mcp init(不带
参数)会引导你完成——参见
自动生成配置。更多
演示(本地 SOAP 服务、Docker、通过原始 URL 提供的 WSDL)列在
examples/ 中。
工作原理
flowchart LR
WSDL["WSDL / XSD"] --> GEN["legacy2mcp<br/>schema generation"]
DB["DB schema<br/>(allowlisted tables)"] --> GEN
GEN --> TOOLS["Typed MCP tools<br/>one per operation / table / query"]
AGENT["AI agent /<br/>MCP client"] -->|tool call| VAL{"schema<br/>validation"}
TOOLS -. defines .-> VAL
VAL -->|invalid args| REJ["rejected, never<br/>reaches SOAP/DB"]
VAL -->|valid and allowed| BACKEND["SOAP endpoint<br/>or database"]
BACKEND --> RESP["plain JSON<br/>back to the agent"]
VAL --> LOG[("audit log")]
加载中
SOAP:
- 使用
zeep加载 WSDL,这是一个成熟且广泛使用的 Python SOAP 客户端。 - 对于每个端口/绑定上的每个操作,将 XSD 输入类型转换为 JSON Schema(
schema/xsd_to_jsonschema.py)——简单类型、嵌套复杂类型、枚举和数组,递归处理,针对病态 WSDL 进行深度限制。 - 为每个操作注册一个 MCP 工具,命名为
<adapter_id>_<OperationName>。 - 在工具调用时:使用
jsonschema校验参数(schema 使用additionalProperties: false),通过zeep调用操作,将响应序列化为纯 JSON,并写入审计条目。
数据库:
- 使用对应方言的驱动(
psycopg/pyodbc/oracledb,延迟导入)连接,并将每个允许列表中的表的实时列类型(adapters/db_dialects.py)内省为 JSON Schema(schema/sql_to_jsonschema.py)。 - 为每个允许列表中的表和操作注册
select_<table>/insert_<table>,并为配置中的每个命名查询注册query_<name>。 - 在工具调用时:校验参数,使用一种方言中立的
:param占位符约定,从允许列表中的表/列构建 SQL(或运行配置提供的命名查询),执行它,并写入审计条目。LLM 永远不会看到或编写 SQL 文本。 - 名称看起来像写入的操作会被排除,除非
allow_write_operations: true。
为什么不直接手写一个适配器?
因为手写适配器是你系统 schema 的第二份副本,没人会记得去更新它。一旦某个 WSDL 操作的签名发生变化,或者某一列发生改变,你工具的 JSON Schema 就会悄无声息地偏离现实——而调用它的 LLM 会通过发送一些错误得无人察觉的参数,或者从 SOAP/DB 客户端深处收到一个令人困惑的运行时错误,才发现这一点。legacy2mcp 每次启动时都从实时系统构建 schema,因此发生漂移的调用会在验证阶段失败,在到达你的后端之前——而不是之后。
| 手写适配器(FastMCP / 原生 SDK) | 通用 OpenAPI 转 MCP 代理 | legacy2mcp | |
|---|---|---|---|
| Schema 来源 | 按工具手工编写 | OpenAPI 规范(如果存在的话) | 实时 WSDL / DB schema,或发现的 OpenAPI 规范 |
| 可能偏离真实系统 | 是——没有任何东西会重新检查它 | 仅当规范本身已过时 | 否——启动时从实时源重建 |
| 覆盖 SOAP / 数据库 | 你自己编写那部分客户端代码 | 否——仅支持 REST/OpenAPI | 是,外加 REST |
| 写操作默认隐藏 | 否——你得自己记得对每个操作设门禁 | 否——通常会暴露规范定义的一切 | 是——显式选择加入 allow_write_operations |
| 每次调用的审计日志 | 自己动手 | 通常没有 | 内置 |
| 到可运行服务器所需时间 | 每个系统数小时到数天 | 对于有规范的 REST API,只需几分钟 | 几分钟——legacy2mcp init |
将它指向你自己的 WSDL
# config.yaml server: name: my-legacy-mcp adapters: - id: legacy type: soap config: wsdl_url: "https://service.example.com/LegacyService?wsdl" auth: type: basic username: "svc-account" password_env: "SERVICE_PASSWORD" # value read from the environment, never the file allow_write_operations: false # Create*/Update*/Delete*/… stay hidden include_operations: ["GetRecord", "GetRecordDetails", "SearchRecords"] security: audit: enabled: true path: "./legacy-mcp-audit.log"
export SERVICE_PASSWORD=... legacy2mcp inspect --config config.yaml # review the generated tools legacy2mcp run --config config.yaml # start the MCP server (stdio)
一个带注释的生产形态模板位于 examples/soap/config.template.yaml。
将它指向你自己的数据库
pip install "legacy2mcp[db-postgres]" # or db-mssql / db-oracle
# config.yaml adapters: - id: orders type: db config: dialect: postgres # postgres | mssql | oracle dsn_env: "ORDERS_DB_DSN" # connection string, read from the environment allow_write_operations: false # insert_<table> / write: true queries stay hidden tables: - name: orders operations: [select] columns: [id, status, total, created_at] # column allowlist filterable_columns: [status] # equality filters on select_orders max_rows: 200 queries: - name: orders_by_status_since sql: "SELECT id, status, total FROM orders WHERE status = :status AND created_at >= :since" params: - { name: status, type: string, required: true } - { name: since, type: string, required: true }
export ORDERS_DB_DSN=... legacy2mcp inspect --config config.yaml # review the generated tools
列在启动时从实时 schema 内省得到,就像 SOAP 操作 schema 来自 WSDL 一样——没有任何需要手工维护的东西。从 LLM 到任意 SQL 永远没有通路:它只能对 tables: 下列出的表调用 select_<table> / insert_<table>,或者调用你在 queries: 下编写了 SQL 的 query_<name> 工具——LLM 只提供查询的 params 所声明的占位符值,绝不提供 SQL 文本。一个生产形态模板位于 examples/db/config.template.yaml。
将它指向你自己的 REST API
普通的 REST/JSON API 没有 WSDL 或实时数据库 schema 可供内省,因此 rest 适配器的端点列表是显式配置——手工编写,或者用 legacy2mcp init(下一节)生成一个起点。
# config.yaml adapters: - id: orders type: rest config: base_url: "https://api.example.com" auth: type: bearer token_env: "ORDERS_API_TOKEN" allow_write_operations: false endpoints: - name: get_order method: GET path: "/orders/{order_id}" params: - { name: order_id, location: path, type: integer, required: true } - name: create_order method: POST path: "/orders" write: true # stays hidden unless allow_write_operations: true params: - { name: total, location: body, type: number, required: true }
一个生产形态模板位于 examples/rest/config.template.yaml。
注意
非 GET 方法和 LLM 辅助分类都是启发式方法,并不能保证该端点实际上可以安全地无监督调用——在针对真实后端运行之前,请检查 write: 标志和 allow_write_operations,这与下面 SOAP 适配器的写动词启发式方法有同样的注意事项。
自动生成配置
legacy2mcp init 会询问要指向什么,然后为你写一个起始的 config.yaml:
legacy2mcp init
What do you want to point legacy2mcp at?
1) WSDL URL (SOAP service)
2) REST API URL (tries OpenAPI/Swagger discovery)
3) GitHub repo URL or a local path to its source code
Choice [1-3]:
| 选项 | 会发生什么 | 需要 LLM 密钥吗? |
|---|---|---|
| WSDL URL | 直接将 soap 适配器指向它——zeep 在 inspect/run 时无论如何都会内省实时 WSDL,因此无需预先扫描任何东西。 |
否 |
| REST API URL | 针对该 URL 尝试常见的 OpenAPI/Swagger 规范位置(/openapi.json、/swagger.json……),如果找到真实规范,就据此构建 rest 适配器的端点列表;否则生成一个空脚手架。 |
否 |
| GitHub 仓库 / 本地路径 | 克隆(或读取)该仓库,并静态扫描其 Python 源代码(仅 AST,绝不执行),查找 zeep.Client(...) 调用、SQLAlchemy/Flask-SQLAlchemy 模型,或 Flask/FastAPI 路由——相应地选择 soap/db/rest。 |
可选 |
每个工具也可通过标志 -- --wsdl-url、--rest-url、--github-url 或 --local-path 以非交互方式运行,用于脚本或 CI。只有 GitHub/源代码路径可以使用 LLM,且仅用于优化静态分析已经发现的内容(模糊的参数类型、读与写的分类)——绝不会转储你的仓库。它不需要额外依赖,只需通过 --llm-api-key-env 提供 OpenAI 兼容的 API 密钥,或者如果你省略它,则使用交互式隐藏提示(绝不会写入磁盘);传入 --scan-only 可直接跳过它并仅运行静态分析,与不输入密钥相同。
legacy2mcp init --github-url https://github.com/you/your-api --scan-only --out config.yaml
无论你选择哪条路径,在运行生成的文件之前都要先审查它——对于任何无法确信推断出的内容(未解析的 base_url、未验证的参数类型、哪些端点被归类为 write: true),它都会用 # TODO 注释标记出来,而且在你这样做之前,其中没有任何内容会针对真实后端进行验证:
legacy2mcp inspect --config config.yaml # review the generated tools legacy2mcp run --config config.yaml # start the MCP server
在将其指向任何真实环境之前,请参阅 docs/security.md,了解此启发式方法确切保证和不保证什么。
从 Claude Desktop(或任何 MCP 客户端)使用它
每个客户端都通过 stdio 启动 uvx legacy2mcp run --config <absolute path>。如果你使用 pip 或 pipx 安装,请用 legacy2mcp 代替 uvx legacy2mcp。
Claude Desktop(claude_desktop_config.json)
{
"mcpServers": {
"legacy2mcp": {
"command": "uvx",
"args": ["legacy2mcp", "run", "--config", "/absolute/path/to/config.yaml"]
}
}
}Claude Code
claude mcp add legacy2mcp -- uvx legacy2mcp run --config /absolute/path/to/config.yaml
Cursor(.cursor/mcp.json)
{
"mcpServers": {
"legacy2mcp": {
"command": "uvx",
"args": ["legacy2mcp", "run", "--config", "/absolute/path/to/config.yaml"]
}
}
}VS Code、传递密钥和故障排除:docs/clients.md。
已处理的内容
| 领域 | 覆盖范围 |
|---|---|
| 类型映射 | string / int / long / decimal / boolean / date / dateTime / base64Binary / … → JSON Schema 类型 + 格式 |
| 结构 | 嵌套复杂类型、重复元素 → 数组,xsd:enumeration → enum,递归深度受限 |
| 发现 | 每个服务 → 端口 → 绑定 → 操作;重复的工具名称在启动时被拒绝 |
| 调用 | 参数验证、zeep 调用、响应序列化为纯 JSON、单字段响应重新包装为命名结果 |
| 错误 | SOAP 故障和传输错误被捕获并作为干净的消息返回——不会向调用方暴露堆栈跟踪 |
| 认证 | HTTP 基本认证(用户名 + *_env 密码);持有者令牌(*_env);匿名 |
| 传输 | stdio(Claude Desktop 和大多数代理框架启动时使用的传输方式) |
| 数据库方言 | Postgres、SQL Server、Oracle——表允许列表(select/insert)配合实时模式内省,以及命名参数化查询 |
| REST | 来自配置或 legacy2mcp init 的显式端点允许列表(方法 + 路径 + 类型化参数)——没有可内省的实时模式,因此在运行之前这里没有任何内容经过验证 |
请参阅 docs/security.md 了解完整、诚实的安全模型——今天覆盖了什么,以及尚未覆盖什么。
安全模型
| 层 | 作用 |
|---|---|
| 模式验证 | 任何参数都必须针对该工具生成的模式通过 jsonschema.validate,才能到达 SOAP/DB 层。 |
| 默认只读 | SOAP:操作名称会与写动词前缀(Create、Update、Delete、Cancel、Void、Submit、Pay、…)进行匹配。DB:表/查询需显式选择加入,且 insert/标记为写的查询还需要额外确认。除非你设置 allow_write_operations: true,否则两者都不会暴露。 |
| 显式允许 / 拒绝 | SOAP:include_operations / exclude_operations。DB:表/列允许列表——没有“所有表”或任意 SQL 的等价物。 |
| 审计日志 | 每次调用一行 JSON——工具、参数、时间戳、结果、持续时间。 |
| 密钥卫生 | 凭据和数据库连接字符串来自命名的环境变量;YAML 保持可安全提交。 |
警告
SOAP 写操作过滤器是一种名称启发式规则,而非语义分析——一个名为 ProcessRecord 却会删除数据的操作不会被拦截。对于任何错误调用会造成实际后果的系统,请显式设置 include_operations,不要依赖该启发式规则。DB 适配器的允许列表在构造上就是显式的,但没有列级数据脱敏——允许列表中列的值始终按原样返回。MCP 服务器本身目前也还没有认证/授权——不要将服务器暴露给不受信任的调用方。详情见 docs/security.md。
用例
| 领域 | 形态 |
|---|---|
| 记录系统 | 智能体从遗留后台平台读取状态/详情记录,只读,每次查询都记录日志。 |
| 金融服务 | 暴露账户和交易的读取,而不暴露转账或调整。 |
| 供应链 / ERP | 从旧的 SOAP 中间件层呈现订单状态、库存、物流跟踪。 |
| 内部支持工具 | 支持副驾驶获得对记录系统的安全、类型化访问,而不是抓取 UI。 |
| 迁移与现代化 | 现在就在遗留服务前放置一个 MCP 层;以后更换后端而无需改动智能体。 |
实际使用
在 CI/CD 中——在 WSDL 漂移到达生产环境之前捕获它
legacy2mcp inspect 加载配置、连接 WSDL、构建每个 schema,如果任何环节失败则以非零状态退出:
- name: Check the WSDL still generates valid MCP tools env: SERVICE_PASSWORD: ${{ secrets.SERVICE_PASSWORD }} run: | pip install legacy2mcp legacy2mcp inspect --config config/legacy.yaml > tools.json git diff --exit-code --no-index tools/legacy.snapshot.json tools.json # optional: pin the contract
作为 sidecar / 长期运行的 MCP 服务器
legacy2mcp run 通过 stdio 使用 MCP 通信。使用提供的 Dockerfile 将其与你的配置打包,让你的 MCP 客户端启动它。
在数据管道中
通过任意 MCP 客户端库从你自己的代码中调用相同的、已生成并验证的工具,按计划拉取记录——审计日志会准确记录获取了什么。
配置参考
| 键 | 默认值 | 含义 |
|---|---|---|
server.name |
legacy2mcp |
向客户端报告的 MCP 服务器名称 |
server.transport |
stdio |
目前仅实现了 stdio |
adapters[].id |
— | 此适配器工具名称的前缀 |
adapters[].type |
— | soap、db 或 rest(已实现);queue 在路线图中 |
SOAP 适配器配置:
| 键 | 默认值 | 含义 |
|---|---|---|
adapters[].config.wsdl_url |
— | WSDL 位置(http(s)://…?wsdl 或文件路径) |
adapters[].config.auth |
{type: none} |
none 或 {type: basic, username, password_env} |
adapters[].config.allow_write_operations |
false |
暴露类似写操作的操作 |
adapters[].config.include_operations |
全部 | 操作名称的允许列表 |
adapters[].config.exclude_operations |
[] |
操作名称的拒绝列表 |
adapters[].config.timeout_seconds |
15 |
每次调用的 SOAP 超时 |
DB 适配器配置:
| 键 | 默认值 | 含义 |
|---|---|---|
adapters[].config.dialect |
— | postgres、mssql 或 oracle |
adapters[].config.dsn_env |
— | 保存连接字符串/DSN 的环境变量 |
adapters[].config.allow_write_operations |
false |
暴露 insert_<table> 和 write: true 查询 |
adapters[].config.timeout_seconds |
15 |
连接超时 |
adapters[].config.tables[].name / .schema |
— | 要加入允许列表的表(以及可选的 schema/owner) |
adapters[].config.tables[].operations |
[select] |
select 和/或 insert |
adapters[].config.tables[].columns |
所有已内省项 | 列允许列表 |
adapters[].config.tables[].filterable_columns |
[] |
可用作 select_<table> 上等值过滤器的列 |
adapters[].config.tables[].max_rows |
200 |
行数上限,在查询运行后强制执行 |
adapters[].config.queries[].name / .sql / .params |
— | 一个命名查询;sql 使用 :param_name 占位符 |
adapters[].config.queries[].write |
false |
将此查询置于 allow_write_operations 之后 |
REST 适配器配置:
| 键 | 默认值 | 含义 |
|---|---|---|
adapters[].config.base_url |
— | 所配置端点相对的基础 URL |
adapters[].config.auth |
{type: none} |
none、{type: basic, username, password_env} 或 {type: bearer, token_env} |
adapters[].config.allow_write_operations |
false |
暴露标记为 write: true 的端点 |
adapters[].config.timeout_seconds |
15 |
每次调用的 HTTP 超时 |
adapters[].config.endpoints[].name / .method / .path / .description |
— | 一个加入允许列表的端点;path 使用 {param} 占位符表示路径参数 |
adapters[].config.endpoints[].params[].name / .location / .type / .required |
— | location 为 path、query 或 body |
adapters[].config.endpoints[].write |
false |
将此端点置于 allow_write_operations 之后 |
安全配置:
| 键 | 默认值 | 含义 |
|---|---|---|
security.audit.enabled |
true |
写入审计日志 |
security.audit.path |
./legacy2mcp-audit.log |
审计日志位置 |
路线图
| 版本 | 范围 | 状态 |
|---|---|---|
| v0.1 | SOAP/WSDL 适配器、schema 生成、验证、默认只读、审计日志、run + inspect CLI、基本认证、stdio |
✅ 已发布 |
| v0.2 | 数据库适配器(Postgres / SQL Server / Oracle;表/操作允许列表 + 命名参数化查询,绝不使用任意 SQL) | ✅ 已发布 |
| v0.2.x | REST 适配器(显式端点允许列表);legacy2mcp init -- 扫描仓库(SOAP 客户端 / ORM 模型 / Flask/FastAPI 路由)并生成起始配置,可选使用兼容 OpenAI 的 LLM 辅助处理模糊部分 |
✅ 已发布 |
| v0.2.x | HTTP/SSE 传输、角色→工具授权、SOAP 的 OAuth2 | 计划中 |
| v0.3+ | 队列适配器(Kafka/RabbitMQ/SQS)、带审批门的工作流编排、OpenTelemetry 导出 | 想法 |
完整细节见 docs/roadmap.md。BaseAdapter 接口(discover_tools() + invoke())是扩展点——服务器核心为任何适配器处理验证、分发和审计。
开发
pip install -e ".[dev]"
pytest tests/ -v测试针对进程内模拟 SOAP 服务和进程内基于 SQLite 的
DB 适配器替身运行——无需网络,无需真实的 Postgres/SQL Server/Oracle。
DB 适配器的 Postgres/SQL Server/Oracle 方言(db_dialects.py)
其 SQL 转换逻辑由无驱动单元测试覆盖;连接每个的真实实例
不属于默认测试套件的一部分。
CI 在 Python 3.10–3.12 上运行测试套件(ci.yml)。发布到 PyPI 和 MCP Registry 由标签触发——见 docs/releasing.md。演示 GIF 使用 vhs demo/demo.tape(demo/)重新生成。
贡献
针对新遗留系统的适配器是最有价值的贡献——实现 BaseAdapter,核心会处理其余部分。欢迎提交 issue 和 PR。
许可证
来源:Hacker News · github.com