openapi-mcp-builder
README.md
# openapi-mcp-builder
An MCP server that turns any OpenAPI (or Swagger) spec URL into a hosted MCP
server on the **Agentic Tools Platform**, and hands the caller back the
ready-to-use MCP gateway URL.
Under the hood it drives the platform's experimental `/v1/openapi-servers/*`
endpoints:
| Tool | Endpoint / behavior |
| --------------------------------------- | ------------------------------------------------- |
| `analyze_openapi_spec_url` | Local: GET spec URL, summarize operations by tag |
| `pick_openapi_endpoints` | Local: list all operations + **MCP App UI** (inline picker in supporting clients) |
| `search_openapi_operations` | Local: keyword search over paths/tags/opIds |
| `validate_openapi_tool_filter` | Local: unknown keys, regex check for paths |
| `export_trimmed_openapi_spec` | Shrink (operation keys, tags, path, prefix, related) |
| `reupload_openapi_spec_text` | Reupload spec body without a public URL |
| `build_tool_filter_for_tags` | Build `{"include_tags":[...]}` for tool_filter |
| `create_mcp_from_openapi_url` | `POST /v1/openapi-servers` + SAS PUT + poll |
| `list_openapi_mcp_servers` | `GET /v1/openapi-servers` |
| `get_openapi_mcp_server` | `GET /v1/openapi-servers/{id}` |
| `update_openapi_mcp_server` | `PATCH /v1/openapi-servers/{id}` |
| `delete_openapi_mcp_server` | `DELETE /v1/openapi-servers/{id}` |
| `refresh_openapi_mcp_server` | `POST /v1/openapi-servers/{id}/refresh` |
| `list_openapi_mcp_server_tools` | `GET /v1/openapi-servers/{id}/tools` |
| `reupload_openapi_spec_from_url` | `PATCH ?reupload=true` + SAS PUT + poll |
## How the spec-URL workflow works
```
user -> MCP client (e.g. Agent Studio)
| tool call: create_mcp_from_openapi_url(spec_url, name, ...)
v
openapi-mcp-builder
1. GET spec_url # validate JSON / YAML
2. POST /v1/openapi-servers # metadata only -> spec_upload_url
3. PUT <spec_upload_url> # Azure Blob SAS, x-ms-blob-type: BlockBlob
4. GET /v1/openapi-servers/{id} (poll) # until parse_status terminal
5. return { gateway_url, tool_count, parse_status, ... }
```
The `gateway_url` in the final response is the MCP URL the agent connects to.
### Large specs (operation limits)
The executor enforces a maximum number of OpenAPI operations per server (e.g. 50). A
**`tool_filter` alone** may *not* help if the platform still counts all operations in the
uploaded file before the filter is applied. In that case you must use a **physically
smaller** spec (fewer path operations in the document):
1. Run **`export_trimmed_openapi_spec`** on the `spec_url` with **`include_operation_keys`**
(exact `GET /path` list), **`include_tags`**, and/or **`path_substrings`** (literal
substrings in the path, e.g. `dailyLog` for ProjectSight). This returns a trimmed
OpenAPI JSON string and `trimmed_operation_count`.
2. Call **`reupload_openapi_spec_text`** with that JSON as **`spec_text`** on the existing
server (no Gist or extra hosting required).
For filters that the platform *does* apply to the live parse, use **`tool_filter`**
(`include_tags`, **`include_paths` as regex** such as `.*[Dd]ailyLog.*` — *not* glob
patterns like `*daily*`, which are invalid regex). Use **`analyze_openapi_spec_url`**
per-tag counts and set **`PLATFORM_MAX_OPENAPI_OPERATIONS`** / **`MAX_TRIMMED_SPEC_EXPORT_BYTES`**
in `.env` for hints and export size.
Optional: set **`CREATE_PREFLIGHT_ENFORCE=true`** so **`create_mcp_from_openapi_url`**
stops before register when the downloaded spec is over the operation cap and you
did not pass **`tool_filter`** or **`acknowledge_openapi_operation_limit=true`**
(see `.env.example`).
### For agent authors (Studio / Cursor)
1. **Always** run **`analyze_openapi_spec_url`** first. If **`exceeds_platform_limit`**
is true, do not call **`create_mcp_from_openapi_url`** until you have a plan.
2. **`tool_filter`** controls which operations become MCP tools; it may **not** reduce
the operation count the executor sees in the **uploaded file**. To pass a hard cap,
use **`export_trimmed_openapi_spec`** (smaller document) + **`reupload_openapi_spec_text`**.
3. Use **`search_openapi_operations`** to map a user phrase (e.g. “daily log”) to real
paths and tags, or call **`pick_openapi_endpoints`** with the same **`spec_url`** when the
client supports **MCP Apps** (inline UI; see [MCP App picker](#mcp-app-inline-endpoint-picker)).
Alternatively use the standalone **[endpoint picker](apps/endpoint-picker/README.md)** (build and
serve at `/endpoint-picker/`; see that README) to
copy **`include_operation_keys`** for **`export_trimmed_openapi_spec`**. **`include_paths` in
`tool_filter` must be regex**, not globs; run **`validate_openapi_tool_filter`**
with **`strict=true`** when you need to fail on unknown keys or glob-like path patterns.
4. **`analyze_openapi_spec_url`** reports **`external_ref_*`** when the document uses
non-`#/` `$ref`s (file or URL). Bundle to a single file when possible. Invalid field
names (e.g. **`path_pattern`**) are a common failure mode — see
[docs/PLATFORM.md](docs/PLATFORM.md) for open questions to confirm with the platform team.
### MCP App (inline endpoint picker)
The tool **`pick_openapi_endpoints`** is registered with FastMCP **`AppConfig`** pointing at a
**`ui://`** HTML resource. MCP clients that implement the [MCP Apps / UI extension](https://github.com/modelcontextprotocol/ext-apps)
(for example recent **VS Code** or **Claude** builds) can render the picker **inside the chat**,
receive the operation list from the tool result, and proxy **`export_trimmed_openapi_spec`**
when the user runs export from the UI.
- **Build the bundle** (writes into `src/openapi_mcp_builder/static/endpoint_picker_mcp.html`):
```bash
cd apps/endpoint-picker-mcp
npm install
npm run build
```
From the repo root you can also run **`npm run build:endpoint-picker-mcp`** (see root `package.json`).
- **Agent Studio** must explicitly support MCP Apps and negotiate `io.modelcontextprotocol/ui`;
if it does not, **`pick_openapi_endpoints`** still returns the same JSON as a normal tool (no iframe).
- **Manual check:** connect this server in an MCP Apps–capable client, invoke **`pick_openapi_endpoints`**
with a public **`spec_url`**, confirm the table appears, select operations, and run export from the UI.
## Authentication (OAuth provider OBO)
When deployed inside **Agent Studio**, every tool call carries the
signed-in user's on-behalf-of TID token as `Authorization: Bearer <token>`.
`openapi-mcp-builder` extracts that header and forwards it to the Agentic AI
Platform, so every action (create, list, patch, delete) runs as the end user —
no static service credentials required.
Three modes are supported, evaluated per request:
1. **OBO passthrough** — use the caller's `Authorization` header (production).
2. **Static token** — `TOOLS_API_ACCESS_TOKEN` env var (local dev / stdio).
3. **Client credentials** — `TOOLS_API_CLIENT_ID` + `TOOLS_API_CLIENT_SECRET`
mint a token against `TOOLS_API_TOKEN_URL` (service-to-service).
The generated OpenAPI MCP server itself defaults to `auth_config.provider =
"passthrough"`, so the upstream REST API receives the same end-user token at
tool-invocation time unless you override it.
## Install
```bash
python -m venv .venv
# Windows PowerShell
.venv\Scripts\Activate.ps1
# macOS / Linux
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env # then edit
```
Requires Python 3.10+.
## Run
### Stdio (Claude Desktop, local MCP clients)
```bash
MCP_TRANSPORT=stdio openapi-mcp-builder
# or
MCP_TRANSPORT=stdio python -m openapi_mcp_builder
```
Example Claude Desktop / Cursor MCP config:
```json
{
"mcpServers": {
"openapi-mcp-builder": {
"command": "openapi-mcp-builder",
"env": {
"MCP_TRANSPORT": "stdio",
"TOOLS_API_ENV": "dev",
"TOOLS_API_ACCESS_TOKEN": "eyJhbGciOi..."
}
}
}
}
```
### HTTP (Agent Studio, remote MCP hosts) — default
```bash
openapi-mcp-builder # listens on 0.0.0.0:8754 with MCP_TRANSPORT=http
```
Agent Studio should be configured to send `Authorization: Bearer <OBO-token>`
on every MCP request. Leave `TOOLS_API_ACCESS_TOKEN` empty so the server
requires the OBO header.
### Environment selector
`TOOLS_API_ENV` picks the Tools API base URL:
| `TOOLS_API_ENV` | Base URL |
| ------------- | ------------------------------------- |
| `dev` (default) | `https://tools.dev.Build Flows-ai.com` |
| `stage` | `https://tools.stage.Build Flows-ai.com` |
| `prod` | `https://tools.ai.Build Flows.com` |
Set `TOOLS_API_TOOLS_API_BASE_URL` to override explicitly.
## Example call
```jsonc
// tool: create_mcp_from_openapi_url
{
"spec_url": "https://api.redocly.com/registry/bundle/hcss-64o/identity/v1/openapi.yaml?branch=main",
"name": "hcss-identity",
"description": "HCSS Identity API (get bearer tokens)",
"tags": ["hcss", "identity"],
"auth_provider": "passthrough"
}
```
Response:
```jsonc
{
"ok": true,
"id": "srv_01HQZ...",
"name": "hcss-identity",
"parse_status": "success",
"tool_count": 3,
"gateway_url": "https://tools.dev.Build Flows-ai.com/openapi/hcss-identity",
"mcp_server_url": "https://tools.dev.Build Flows-ai.com/openapi/hcss-identity",
"path": "/openapi/hcss-identity",
"spec_bytes": 48321,
"spec_content_type": "application/yaml",
"waited_seconds": 4.12
}
```
Point your agent at `mcp_server_url` and you're done.
## Project layout
```
openapi-mcp/
├── pyproject.toml
├── README.md
├── .env.example
├── src/openapi_mcp_builder/
│ ├── __init__.py
│ ├── __main__.py # CLI entrypoint / transport selection
│ ├── server.py # FastMCP tools
│ ├── workflow.py # download -> register -> upload -> poll
│ ├── client.py # Async HTTP client for the Tools API
│ ├── auth.py # OBO passthrough + fallbacks
│ ├── config.py # Pydantic Settings
│ ├── models.py # Request / response schemas
│ ├── operation_key.py # Canonical operation_key for trim
│ ├── spec_external_refs.py
│ ├── spec_inspect.py # Per-tag / path operation summaries (tool_filter)
│ ├── spec_ref_prune.py # Prune components to $ref-closure after trim
│ ├── spec_trim.py # Shrink paths in a spec for reupload
│ ├── static_resources.py # MCP App HTML loader (ui:// endpoint picker)
│ ├── static/ # bundled endpoint_picker_mcp.html (built by Vite)
│ └── tool_filter_validate.py
├── docs/
│ └── PLATFORM.md # Open questions for the platform (op cap vs filter)
├── apps/
│ ├── endpoint-picker/ # static UI: paste spec → include_operation_keys JSON
│ └── endpoint-picker-mcp/ # Vite MCP App bundle for inline picker (npm run build)
└── tests/
├── conftest.py
├── test_auth.py
├── test_spec_inspect.py
├── test_spec_external_refs.py
├── test_spec_ref_prune.py
├── test_spec_trim.py
├── test_tool_filter_validate.py
├── test_endpoint_picker.py # pick_openapi_endpoints + MCP HTML smoke
└── test_workflow.py # respx-mocked end-to-end flow
```
## Tests
```bash
pytest
```
## License
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues