AlinsRan opened a new pull request, #13956:
URL: https://github.com/apache/apisix/pull/13956
Three defects in `openapi-to-mcp`, found by driving the plugin with the
official MCP SDK client (TypeScript, `@modelcontextprotocol/sdk`) over both
transports and comparing what the upstream API actually received. Each one
makes a call that a client is entitled to make fail or silently do the wrong
thing.
### 1. `default` is applied after validation, so a required member that has
one is rejected
An operation may declare a parameter `required: true` together with a
`default`; the same holds for a required request-body property. The generated
input schema reproduces both, `core.schema.check()` runs first and reports the
member as missing, and the call never reaches the API — even though the
document says what the value should be when the client does not send it.
Defaults are now filled into the arguments before validation, recursively,
and only into objects the caller actually sent, so an absent required container
is still an error rather than one this pass invents. An argument the client did
send is never replaced.
### 2. SSE: `${...}` in `base_url` and `headers` is resolved again on every
message
With `transport: sse`, the endpoint the server hands the client is
`<path>?sessionId=<uuid>`. Every subsequent message is a POST to that endpoint,
and it carries none of the request state the variables were read from.
Resolving them there produced empty values:
- `"Authorization": "Bearer ${http_x_api_token}"` reached the API as `Bearer
`.
- `base_url: "http://${http_x_backend}"` failed to resolve at all.
The resolved `base_url` and `headers` are now captured when the stream is
opened, stored with the session record in the `mcp-session` shared dict, and
used for every message on that session. A session created before this change
keeps the previous behaviour instead of failing.
### 3. A document that is not an OpenAPI document produces an empty tool list
Any JSON or YAML parses, so a route pointed at an error page, a JSON index
or a spec that failed to render came up as a healthy MCP server with zero
tools, and nothing anywhere said why.
The route's own document must now declare an `openapi` or a `swagger`
version and a `paths` object. The check is deliberately not in
`loader.parse()`: a document pulled in by an external `$ref` is normally a
fragment — components, a single schema — and has neither key, so it is still
accepted as before. The error surfaces through the `-32603` envelope both
transports already use.
### Tests
- `t/plugin/openapi-to-mcp-openapi-loader.t`: `validate()` accepts OpenAPI
and Swagger documents, rejects a document with no version and one with no
`paths`, and `parse()` still accepts a `$ref` fragment.
- `t/plugin/openapi-to-mcp.t`: a required query parameter and a required
body property, each with a default, may be omitted; an argument that is sent
wins over the default; a route pointed at a non-OpenAPI document reports an
error instead of an empty tool list.
- `t/plugin/openapi-to-mcp-e2e-sse.t` with a new driver: the header value
resolved when the stream opened is the one the API receives on a later message.
All four new behaviours were confirmed to fail on `master` before the change.
### Docs
`docs/en/latest/plugins/openapi-to-mcp.md` and the Chinese page document the
default handling, the point at which SSE resolves variables, and — since it is
easy to assume otherwise — the exact query-string form arrays take:
`tags=a&tags=b` for the `form`/exploded default and `tags=a,b` for `explode:
false`, never `tags[]=a&tags[]=b`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]