Conversation
…penAPI documents
Three fixes to the contract the plugin documents, found by driving it with the
official MCP SDK client.
Defaults declared in the document were applied after the arguments had been
validated, so a parameter or a body property that is both `required` and has a
`default` was rejected as missing and the call never reached the API. They are
now filled in before validation, and an argument the client sent is never
replaced.
With the SSE transport, `${...}` in `base_url` or in `headers` was resolved
again on every message POST. That request carries only the session id -- the
endpoint handed to the client is `<path>?sessionId=<uuid>` -- so a header such
as `Bearer ${http_x_api_token}` resolved to `Bearer ` and a variable base_url
failed to resolve at all. The values are now resolved once, on the request that
opens the stream, stored with the session and used for every message on it.
Any JSON or YAML document parsed, so a route pointed at the wrong URL -- an
error page, an index document, a spec that failed to render -- served an empty
tool list and looked healthy. The route's own document must now declare an
`openapi` or `swagger` version and a `paths` object; a document pulled in by an
external `$ref` is a fragment and is still accepted as before.
Adds tests for each, and documents the default handling, the SSE resolution
point and the query serialization the plugin performs.
…tent When the document describes an operation's success response as a JSON object, the generated tool now advertises that schema as `outputSchema`, and a call whose answer satisfies it returns the API body as `structuredContent` with the same body as the text block. The response is taken from `200`, then `201`, then any other explicit `2xx`, then `2XX`; only an `application/json` object schema with properties qualifies, so an array, a `default` response and an unmerged composition advertise nothing. An answer that cannot satisfy the schema -- a status outside `2xx`, a body that is not a JSON object, or one that does not validate -- is returned as an error result carrying the usual envelope. MCP requires a tool that declares an `outputSchema` to produce structured content unless the result is an error, so returning the envelope on its own would leave a client that enforces the schema with a protocol error instead of a readable failure. The body is validated the way the client will validate it: the MCP SDKs check structured content with a validator that does not assert `format`, so asserting it here would reject bodies the client would have accepted.
…y point
`base_url` accepts variables, so a route can be written whose upstream host
comes from the request -- `http://${http_x_backend}` is a documented
configuration. Nothing constrained what that resolved to, which makes the
gateway a usable relay to any host reachable from it.
`allowed_hosts` takes exact host names and `*.example.com` wildcards matching
one or more leading labels. When it is set, the host the `base_url` resolves to
is checked before the document is fetched and before the API is called, and a
`base_url` that is not an `http` or `https` URL is rejected as well; both
answer 400. Neither the URL nor the host appears in the rejection or in the log
line, because a resolved `base_url` can carry values taken from the request.
Leaving the attribute unset keeps the previous behaviour.
YAML reads an unquoted version as a number: `swagger: 2.0` and `openapi: 3.1` both come back as one, and only a version with two dots such as `3.0.0` is a string. The document check accepted a string alone, so it turned away the form Swagger 2.0 documents are usually written in. JSON documents were never affected, since JSON quotes the value. The check now takes either, and the loader tests cover all four shapes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent changes to
openapi-to-mcp, one commit each, both found by driving the plugin with the official MCP SDK client and comparing the result against what the OpenAPI document promises. They are unrelated and can be evaluated separately.Based on #13956; the first commit shown here is that PR's. Please review this one after it merges, at which point this branch is rebased onto
master.1.
outputSchemaandstructuredContent(commitfeat(openapi-to-mcp): advertise outputSchema and return structuredContent)An operation whose success response is described as a JSON object currently tells an MCP client nothing about the shape of what it will get back, and the answer always arrives as an opaque text block.
The generated tool now advertises that schema as
outputSchema, and a call whose answer satisfies it returns the API body asstructuredContent, with the same body as the text block.Which response the schema comes from, most specific first:
200,201, any other explicit2xx, then2XX. Only anapplication/jsonschema that is an object with properties qualifies — an array, adefaultresponse, a baretype: objectand a composition the document leaves unmerged (allOf) advertise nothing, since a client could not bind to them.The error path deserves attention in review. MCP requires a tool that declares an
outputSchemato return structured content unless the result is an error, so an answer that cannot satisfy the schema — a status outside2xx, a body that is not a JSON object, or one that does not validate — is returned as an error result carrying the usual{status, statusText, headers, data}envelope. Returning that envelope on its own would leave an SDK client withMCP error -32600: Tool ... has an output schema but did not return structured contentinstead of a readable failure; the behaviour here is deliberately the compliant one. Tools that advertise nooutputSchemaare untouched and keep returning the envelope whatever the status.The body is validated the way the client will validate it — the MCP SDKs check structured content with a validator that does not assert
format, so asserting it here would reject bodies the client would have accepted.Tests: response selection and the cases that qualify or do not, in
t/plugin/openapi-to-mcp-tools-generator.t; end to end, that only the qualifying operations advertise a schema, that the advertised schema is the declared one, that a matching answer comes back as structured content and that a 404 comes back as an error result.2.
allowed_hosts(commitfeat(openapi-to-mcp): add allowed_hosts to restrict where base_url may point)base_urlaccepts variables, andhttp://${http_x_backend}is a documented configuration. Nothing constrained what it resolved to, so such a route makes the gateway a usable relay to any host it can reach.The new
allowed_hostsattribute takes exact host names and*.example.comwildcards that match one or more leading labels. When it is set, the host is checked before the document is fetched and before the API is called; abase_urlthat is not anhttporhttpsURL is rejected too. Both answer 400, and neither the URL nor the host appears in the response or in the log line, because a resolvedbase_urlcan carry values taken from the request. Leaving the attribute unset keeps today's behaviour.Tests: a host outside the list rejected, an exact entry served, a wildcard entry matching a sub-domain but not the bare domain, a non-http URL rejected, and the schema rejecting an empty list and an entry that is a URL rather than a host.
Both changes are documented in
docs/en/latest/plugins/openapi-to-mcp.mdand the Chinese page.