{"slug": "show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server", "title": "Show HN: Enframe – Drop-in sidecar that wraps any OpenAPI spec to an MCP server", "summary": "Developer Erwin Kramer released Enframe, a drop-in sidecar container that wraps any OpenAPI spec into a Model Context Protocol (MCP) server with almost no configuration, inferring API key, OAuth 2.0 and OpenID Connect authentication from the spec. Enframe accepts Swagger 2.0 and OpenAPI 3.0 through 3.2 specs, emits tool schemas as JSON Schema 2020-12, and runs on the official C# SDK 2.x implementing MCP spec revision 2026-07-28 while negotiating down to 2025-11-25 and earlier. The project ships as the container image ghcr.io/erwinkramer/enframe, is configured through environment variables such as Enframe__ApiSpecSource and Enframe__ApiBaseUrl, and has been confirmed with clients including Pi, OpenCode v2 CLI, MCP Inspector and VS Code local mode using pre-registration authorization via Entra ID.", "body_md": "If you have a well-specified API with OpenAPI, **Enframe rewards you**: it takes almost no configuration to get an MCP server running, and even (mixed) authentication (API key, OAuth 2.0, OpenID Connect) is inferred.\n\nLet's wrap [DummyJSON](https://dummyjson.com/) into an MCP server and start it:\n\n```\npodman run -d --name enframe -p 8080:8080 \\\n  -e Enframe__ApiSpecSource=https://dummyjson.com/openapi.json \\\n  ghcr.io/erwinkramer/enframe:latest\n```\n\nConfirm it's up and healthy by going to the health endpoint: [http://127.0.0.1:8080/mcp/healthz](http://127.0.0.1:8080/mcp/healthz).\n\nNow integrate the live server with your favorite harness, for Pi the `mcp.json` can be as minimal as:\n\n```\n{\"mcpServers\":{\"dummy\":{\"url\":\"http://127.0.0.1:8080/mcp\"}}}\n```\n\nFinally, think of some creative way to reach it in a session, such as...:\n\nget all smartphones from dummy mcp\n\n- \n**OpenAPI:** accepts Swagger 2.0 and OpenAPI 3.0-[3.2](https://spec.openapis.org/oas/v3.2.0.html) specs, emits tool schemas as[JSON Schema 2020-12](https://json-schema.org/draft/2020-12) ; the dialect OpenAPI 3.1+ and MCP share.\n- \n**MCP:** official[C# SDK](https://github.com/modelcontextprotocol/csharp-sdk) 2.x — implements[spec revision `2026-07-28`](https://modelcontextprotocol.io/specification/latest) , negotiates down to`2025-11-25` and earlier.\n- \n**Clients:** any client conforming to the MCP spec, confirmed with following clients using[pre-registration authz](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#pre-registration) (inferred from OpenAPI spec) via[Entra ID](https://learn.microsoft.com/en-us/entra/agent-id/secure-mcp-server-with-entra-id) :Client Config, notes [Pi](https://pi.dev)[.pi/mcp.json](https://github.com/erwinkramer/enframe/blob/main/.pi/mcp.json)[OpenCode v2 CLI](https://opencode.ai/v2/docs/cli)[opencode.jsonc](https://github.com/erwinkramer/enframe/blob/main/opencode.jsonc)[MCP Inspector](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector)[mcp.json](https://github.com/erwinkramer/enframe/blob/main/mcp.json)[Local mode in VS Code](https://code.visualstudio.com/docs/agents/run/agent-harnesses#_local)[authorize VS Code in the app reg](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-mcp-server-vscode#authorize-visual-studio-code-in-the-app-registration)\n\n| Environment variable | Required | Description | \n|---|---|---|\n| `ASPNETCORE_HTTP_PORTS` | no | HTTP port to listen on; default `8080` | \n| `Enframe__ApiBaseUrl` | no | Base URL of the target API that generated tools call; defaults to the first URL in the spec's `servers` , with server variables substituted by their defaults (a relative URL is resolved against the spec's location) | \n| `Enframe__ApiKey` | conditional | API key sent for operations the spec protects with an apiKey scheme; required if the spec declares one | \n| `Enframe__ApiSpecSource` | yes | OpenAPI spec to wrap: an http(s) URL (fetched at startup) or a file path relative to the executable | \n| `Enframe__Authority` | no | Authorization server URL; defaults to the spec's `openIdConnectUrl` (`.well-known/openid-configuration` suffix stripped). Bearer-token auth for MCP clients is enabled when the spec declares an OAuth2 scheme and the authorization server is identifiable (this variable or the spec's`openIdConnectUrl` ) | \n| `Enframe__McpPath` | no | URL prefix the MCP endpoint is served under; default `/mcp` (leading/trailing slashes are normalized) | \n| `Enframe__Resource` | no | RFC 8707 resource identifier announced to MCP clients; defaults to the API [URI authority](https://www.rfc-editor.org/info/rfc3986/#section-3.2) (via`Enframe__ApiBaseUrl` ) + the MCP path (`Enframe__McpPath` ) | \n\nThe general OpenAPI mapping behavior is summarized as the MCP server [instructions](https://modelcontextprotocol.io/specification/2026-07-28/server/discover#data-types), via [ServerInstructions.md](https://github.com/erwinkramer/enframe/blob/main/src/Enframe.Mcp/ServerInstructions.md). Details about the mapping:\n\n| OpenAPI concept | MCP concept | \n|---|---|\n| Operation | One tool, named by its `operationId` (sanitized to MCP's allowed characters) | \n| `summary` /`description` | Tool `title` /`description` | \n| `parameters` (path, query, header) | `inputSchema` properties | \n| `requestBody` (first JSON content) | Required `inputSchema` property`body` | \n| 2xx response | Tool `outputSchema` (and`structuredContent` on success): wrapper object with`status` , declared response`headers` , and`body` (first JSON content) | \n| Non-2xx response | `isError: true` plus the raw response body as text; no structured content, since the`outputSchema` describes the success shape only | \n| `$ref` to`components/schemas` | Inlined into the tool's input/output schemas | \n| HTTP verb | Tool annotations: `GET` /`QUERY` → readOnly,`PUT` /`DELETE` → destructive, non-`POST` → idempotent | \n| `tags` | Tool `_meta.tags` | \n| `info.title` /`version` /`description` /`contact.url` | MCP server info: name, version, description, website URL | \n| `servers` (first URL, variables at defaults) | Base URL of the target API (default of `Enframe__ApiBaseUrl` ) | \n| `securitySchemes` OAuth2 scopes | MCP `scopes_supported` ; token audience derived from the scope's base | \n| `securitySchemes.openIdConnectUrl` | MCP authorization server ( `.well-known/openid-configuration` suffix stripped) | \n| `security` per operation | Call-time auth mode: bearer token forwarded, apiKey scheme → APIM key header, or none | \n\n1. Configure `127.0.0.1  localhost.guanchen.nl` or similar in the host file, so you'd have an easier time making your authorization server happy.\n2. Build Enframe from source and wrap a spec that supports auth you have control over, for instance:\n\n```\npodman build -t enframe .\npodman rm -f enframe\npodman run -d --name enframe -p 8080:8080 \\\n  -e Enframe__ApiKey=\"Lifetime Subscription\" \\\n  -e Enframe__ApiSpecSource=https://ca.guanchen.nl/v1/openapi/spec.json \\\n  -e Enframe__Resource=http://localhost.guanchen.nl:8080/mcp \\\n  enframe\n```\n\nNow, the MCP server will be live and listening at `http://localhost.guanchen.nl:8080/mcp`.\n\nWith the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) CLI we can test the behavior. The first run performs the interactive OAuth flow (browser + loopback callback); later runs reuse the cached token:\n\n```\nexport MCP_INSPECTOR_SECRET_STORE=file\nexport MCP_OAUTH_CALLBACK_URL=http://localhost:6276/oauth/callback\nnpx @modelcontextprotocol/inspector --cli --config ./mcp.json --method initialize\nnpx @modelcontextprotocol/inspector --cli --config ./mcp.json --method servers/show --server enframe\nnpx @modelcontextprotocol/inspector --cli --config ./mcp.json --method tools/list --format json --strict\nnpx @modelcontextprotocol/inspector --cli --config ./mcp.json --method tools/call --tool-name GetBankTeller\n```\n\nThis work is licensed under a\n[Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-nc-sa/4.0/).", "url": "https://wpnews.pro/news/show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server", "canonical_source": "https://github.com/erwinkramer/enframe", "published_at": "2026-10-28 20:35:03+00:00", "updated_at": "2026-10-28 20:43:00.078397+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "developer-tools", "ai-tools"], "entities": ["Enframe", "Erwin Kramer", "Model Context Protocol", "OpenAPI", "DummyJSON", "Entra ID", "OpenCode", "VS Code"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server", "markdown": "https://wpnews.pro/news/show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server.md", "text": "https://wpnews.pro/news/show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server.txt", "jsonld": "https://wpnews.pro/news/show-hn-enframe-drop-in-sidecar-that-wraps-any-openapi-spec-to-an-mcp-server.jsonld"}}