openapi-to-mcp
The openapi-to-mcp plugin enables the gateway to act as a bridge between OpenAPI specifications and MCP (Model Context Protocol) servers. With this plugin, you can expose your existing OpenAPI-based services through an MCP interface, making them accessible to AI models and clients.
The plugin works by converting your OpenAPI specification into the MCP format and serving it through an MCP server interface. Requests from AI clients are then proxied to your upstream services, with support for custom headers and two transport methods for streaming responses: streamable HTTP and Server-Sent Events (SSE), allowing flexible and reliable real-time communication.
The following diagram illustrates the interaction between the MCP client, API7 gateway, and an upstream OpenAPI service. The path and data are example values for demonstration.
Demo
The following example demonstrates how to enable MCP access to Petstore APIs, allowing AI models and clients to interact with the Petstore service. When configured correctly, the AI client should immediately see available Petstore tools; if tools aren't appearing, verify the OpenAPI specification URL is accessible and the gateway address is reachable from your AI client environment.
Examples
The examples below demonstrate how you can configure openapi-to-mcp plugin for different scenarios.
Enable MCP Access to Petstore APIs
The following example demonstrates how to expose the Petstore APIs through the MCP protocol, allowing AI models and clients to interact with the Petstore service.
- Admin API
- ADC
- Ingress Controller
Create a route with the openapi-to-mcp plugin:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "openapi-to-mcp-route",
"uri": "/mcp",
"methods": ["GET", "POST"],
"plugins": {
"openapi-to-mcp": {
"transport": "streamable_http",
"base_url": "https://petstore3.swagger.io/api/v3",
"headers": {
"Authorization": "special-key"
},
"openapi_url": "https://petstore3.swagger.io/api/v3/openapi.json"
}
}
}'
❶ Configure the route to allow GET and POST methods. The GET method enables the tool discovery and response streaming (SSE), while the POST method enables the execution and action capabilities (messages).
❷ Configure the transport method to be streamable_http (recommended for production).
❸ Configure the Petstore API address.
❹ Configure the Petstore API credential.
❺ Configure the Petstore OpenAPI document URL.
Create a route with the openapi-to-mcp plugin configured as such:
services:
- name: openapi-to-mcp-service
routes:
- name: openapi-to-mcp-route
uris:
- /mcp
methods:
- GET
- POST
plugins:
openapi-to-mcp:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
❶ Configure the route to allow GET and POST methods. The GET method enables the tool discovery and response streaming (SSE), while the POST method enables the execution and action capabilities (messages).
❷ Configure the transport method to be streamable_http (recommended for production).
❸ Configure the Petstore API address.
❹ Configure the Petstore API credential.
❺ Configure the Petstore OpenAPI document URL.
- Gateway API
- APISIX CRD
Create a route with the openapi-to-mcp plugin configured as such:
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: openapi-to-mcp-plugin-config
spec:
plugins:
- name: openapi-to-mcp
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /mcp
method: GET
- path:
type: Exact
value: /mcp
method: POST
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: openapi-to-mcp-plugin-config
Apply the configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
❶ Configure the transport method to be streamable_http (recommended for production).
❷ Configure the Petstore API address.
❸ Configure the Petstore API credential.
❹ Configure the Petstore OpenAPI document URL.
❺ Configure the route to allow GET and POST methods. The GET method enables the tool discovery and response streaming (SSE), while the POST method enables the execution and action capabilities (messages).
Create a route with the openapi-to-mcp plugin configured as such:
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
ingressClassName: apisix
http:
- name: openapi-to-mcp-route
match:
paths:
- /mcp
methods:
- GET
- POST
plugins:
- name: openapi-to-mcp
enable: true
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
❶ Configure the route to allow GET and POST methods. The GET method enables the tool discovery and response streaming (SSE), while the POST method enables the execution and action capabilities (messages).
❷ Configure the transport method to be streamable_http (recommended for production).
❸ Configure the Petstore API address.
❹ Configure the Petstore API credential.
❺ Configure the Petstore OpenAPI document URL.
In your AI client, such as Cursor, update the MCP settings with your API7 Gateway address and append the previously created route path. For instance:
{
"mcpServers": {
"api7-petstore-mcp": {
"url": "http://123.123.123.123:9080/mcp"
}
}
}
If the configuration is successful, you should see the available tools (external functions or services exposed to AI clients through MCP).
You can now interact with API7 Enterprise directly from the chat window of your AI client. For example, try asking: “How many pets are there in the petstore?”

Configure Authentication for MCP Routes
The following example demonstrates how to expose Petstore APIs through the MCP protocol when the route is protected by an authentication method such as key-auth.
- Admin API
- ADC
- Ingress Controller
Create a consumer johndoe:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "johndoe"
}'
Configure the key-auth credential for johndoe:
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
Create a route with the openapi-to-mcp and key-auth plugins:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "openapi-to-mcp-route",
"uri": "/mcp",
"methods": ["GET", "POST"],
"plugins": {
"openapi-to-mcp": {
"transport": "streamable_http",
"base_url": "https://petstore3.swagger.io/api/v3",
"headers": {
"Authorization": "special-key"
},
"openapi_url": "https://petstore3.swagger.io/api/v3/openapi.json"
},
"key-auth": {
"header": "apikey"
}
}
}'
Create a consumer and a route with the openapi-to-mcp and key-auth plugins configured as such:
consumers:
- name: johndoe
plugins:
key-auth:
key: john-key
services:
- name: openapi-to-mcp-service
routes:
- name: openapi-to-mcp-route
uris:
- /mcp
methods:
- GET
- POST
plugins:
key-auth:
header: apikey
openapi-to-mcp:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Create a consumer and a route with the openapi-to-mcp and key-auth plugins configured as such:
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: johndoe
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: john-key
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: openapi-to-mcp-plugin-config
spec:
plugins:
- name: key-auth
config:
header: apikey
- name: openapi-to-mcp
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /mcp
method: GET
- path:
type: Exact
value: /mcp
method: POST
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: openapi-to-mcp-plugin-config
Apply the configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
Create an ApisixConsumer and a route with the openapi-to-mcp and key-auth plugins configured as such:
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
name: johndoe
spec:
authParameter:
keyAuth:
value:
key: john-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
ingressClassName: apisix
http:
- name: openapi-to-mcp-route
match:
paths:
- /mcp
methods:
- GET
- POST
plugins:
- name: key-auth
enable: true
config:
header: apikey
- name: openapi-to-mcp
enable: true
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
When an MCP server requires authentication, you can specify headers in the mcp.json configuration. Refer to the documentation of your AI client to verify whether headers are supported.
If Headers Are Supported
For example, in Cursor you can update the MCP settings with your API7 Gateway address, append the previously created route path, and include the header required for key-auth:
{
"mcpServers": {
"api7-petstore-mcp": {
"url": "http://123.123.123.123:9080/mcp",
"headers": {
"apikey": "john-key"
}
}
}
}
The configured headers will be added to both GET and POST requests.
If the configuration is successful, you should see the available tools (external functions or services exposed to AI clients through MCP). You can then interact with Petstore directly from the chat window of your AI client.
If the authentication header is not configured in mcp.json, the AI client will be unable to load tools from the MCP server.
If Headers Are Not Supported
If your AI client does not support configuring headers in mcp.json, you can include the authentication credential in the MCP URL query, since key-auth supports obtaining credential from the URL query.
Update the key-auth configuration on the route as such:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/openapi-to-mcp-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"key-auth": {
"_meta": {
"filter": [
[
"request_method",
"==",
"GET"
]
]
},
"query": "apikey"
}
}
}'
# other config
# ...
services:
- name: openapi-to-mcp-service
routes:
- name: openapi-to-mcp-route
uris:
- /mcp
methods:
- GET
- POST
plugins:
key-auth:
_meta:
filter:
- - request_method
- "=="
- GET
query: apikey
openapi-to-mcp:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Update the PluginConfig:
# other configs
# ---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: openapi-to-mcp-plugin-config
spec:
plugins:
- name: key-auth
config:
_meta:
filter:
- - request_method
- "=="
- GET
query: apikey
- name: openapi-to-mcp
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the updated configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
Update the ApisixRoute:
# other configs
# ---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
ingressClassName: apisix
http:
- name: openapi-to-mcp-route
match:
paths:
- /mcp
methods:
- GET
- POST
plugins:
- name: key-auth
enable: true
config:
_meta:
filter:
- - request_method
- "=="
- GET
query: apikey
- name: openapi-to-mcp
enable: true
config:
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the updated configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
❶ Only apply the key-auth on GET requests. This is because the apikey configured in the query parameter is only sent with the GET request to the SSE endpoint. It is not included in the subsequent POST message requests. As a result, the message requests will be blocked by the key-auth plugin if the filter is not applied.
❷ Configure the plugin to obtain the authentication key from the query.
In your AI client, include the credential in the API7 Gateway address query parameter:
{
"mcpServers": {
"api7-petstore-mcp": {
"url": "http://123.123.123.123:9080/mcp?apikey=john-key"
}
}
}
If the configuration is successful, you should see the available tools (external functions or services exposed to AI clients through MCP). You can then interact with Petstore directly from the chat window of your AI client.
If the authentication credential is not configured in the MCP server URL query, the AI client will be unable to load tools from the MCP server.
Flatten Tool Schema Parameters
The following example demonstrates how flatten_parameters affects the structure of query and path parameters in the generated MCP tool input schema.
Complete the previous example to set up MCP access to the Petstore APIs. Although the configuration does not explicitly set flatten_parameters, the parameter defaults to false.
In your AI client, such as Cursor, inspect the tool input schema. You should see parameters nested under pathParameters and queryParameters:
{
"operations": {
...,
"getPetById": {
"method": "GET",
"path": "/pet/{petId}",
"pathParameters": {
"type": "object",
"required": ["petId"],
"properties": {
"petId": {
"type": "integer",
"description": "ID of pet to return"
}
},
"additionalProperties": false
}
},
"findPetsByStatus": {
"method": "GET",
"path": "/pet/findByStatus",
"queryParameters": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["available", "pending", "sold"],
"description": "Status values that need to be considered for filter",
"default": "available"
}
},
"additionalProperties": false
}
}
}
}
Update the plugin to flatten query and path parameters:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes/openapi-to-mcp-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"openapi-to-mcp": {
"flatten_parameters": true
}
}
}'
services:
- name: openapi-to-mcp-service
routes:
- name: openapi-to-mcp-route
uris:
- /mcp
methods:
- GET
- POST
plugins:
openapi-to-mcp:
flatten_parameters: true
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Update the PluginConfig:
# other configs
# ---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: openapi-to-mcp-plugin-config
spec:
plugins:
- name: openapi-to-mcp
config:
flatten_parameters: true
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the updated configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
Update the ApisixRoute:
# other configs
# ---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: openapi-to-mcp-route
spec:
ingressClassName: apisix
http:
- name: openapi-to-mcp-route
match:
paths:
- /mcp
methods:
- GET
- POST
plugins:
- name: openapi-to-mcp
enable: true
config:
flatten_parameters: true
transport: streamable_http
base_url: "https://petstore3.swagger.io/api/v3"
headers:
Authorization: "special-key"
openapi_url: "https://petstore3.swagger.io/api/v3/openapi.json"
Apply the updated configuration to your cluster:
kubectl apply -f openapi-to-mcp-ic.yaml
In your AI client, such as Cursor, inspect the tool input schema. You should see that parameters like status are no longer nested under pathParameters or queryParameters:
{
"operations": {
...,
"getPetById": {
"parameters": {
"type": "object",
"required": ["petId"],
"properties": {
"petId": {
"type": "integer",
"description": "ID of pet to return"
}
},
"additionalProperties": false
}
},
"findPetsByStatus": {
"parameters": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["available", "pending", "sold"],
"description": "Status values that need to be considered for filter",
"default": "available"
}
},
"additionalProperties": false
}
}
}
}
Customize MCP Tool Annotations
MCP tool annotations are available from API7 Enterprise version 3.9.7.
The following example demonstrates how to add MCP tool annotations to OpenAPI operations exposed by the openapi-to-mcp plugin.
Without these annotations, AI clients only receive the generated tool name, description, and input schema. They cannot reliably tell whether a tool is read-only, destructive, or idempotent, which makes it harder to rank tools correctly and use them safely.
This is implemented in the bundled OpenAPI-to-MCP converter in two ways:
- It infers default tool behavior from the HTTP method.
- It reads explicit operation-level configuration from the OpenAPI vendor extension
x-mcp-annotations.
When both are present, explicit x-mcp-annotations values override the inferred defaults.
Complete the previous example to expose an OpenAPI document through the openapi-to-mcp plugin, then add annotations to the OpenAPI operations:
The previous Petstore example uses a public OpenAPI document that you cannot edit directly. To apply x-mcp-annotations, host your own OpenAPI document and update the openapi_url field in the openapi-to-mcp plugin configuration to point to that hosted document.
paths:
/users/{id}:
get:
operationId: getUser
summary: Get user information
x-mcp-annotations:
title: Get User
readOnlyHint: true
openWorldHint: false
delete:
operationId: deleteUser
summary: Delete a user
x-mcp-annotations:
title: Delete User
destructiveHint: true
Supported annotation fields:
titlereadOnlyHintdestructiveHintidempotentHintopenWorldHint
If x-mcp-annotations is not configured, the converter still applies default inference rules:
GET,HEAD, andOPTIONSmap toreadOnlyHint: trueDELETEmaps todestructiveHint: trueandidempotentHint: truePUTmaps toidempotentHint: true
After updating the hosted OpenAPI document, ask your MCP client to list tools. The following snippet shows the result.tools portion of the tools/list response:
{
"tools": [
{
"name": "getUser",
"annotations": {
"title": "Get User",
"readOnlyHint": true,
"openWorldHint": false
}
},
{
"name": "deleteUser",
"annotations": {
"title": "Delete User",
"destructiveHint": true,
"idempotentHint": true
}
}
]
}
Notes:
- Only operation-level
x-mcp-annotationsis supported. - Invalid values and unsupported fields are ignored.
summaryanddescriptionstill control the generated tool description.titleis only read fromx-mcp-annotations.title.
Troubleshooting
To diagnose issues, check the openapi-to-mcp error log at /usr/local/openapi2mcp/error.log in your gateway container or pod. Note that this log is separate from the gateway’s error log.
Known Issues
-
The error
Cannot use 'in' operator to search for '$ref' in undefinedtypically occurs when an OpenAPI v2 document is used inopenapi_url. The plugin only supports OpenAPI v3 document inopenapi_url. -
The plugin has a known parsing issue when handling
oneOfschemas in OpenAPI v3 document retrieved fromopenapi_url. In this case, the MCP client will be stuck at tool loading.