Commit Graph

5 Commits

Author SHA1 Message Date
Stéphane Tailland 865a7376e3 fix(mcp): translate OpenAPI 3.0 nullable into JSON Schema
The response schemas added yesterday made every paginated list tool fail. The
plugin's fields are documented as OpenAPI format and validated with a JSON
Schema validator, and `nullable: true` is not a JSON Schema keyword — so it is
ignored, `type: string` is enforced, and `previous: null` on the first page is
rejected as *null is not of type "string"*.

The failure lands after the backend has already done the work, surfaces to the
caller as a bare tool retry exhaustion, and appears nowhere in the gateway's own
logs. Found only because an orchestrating agent asked a question that paged.

The generator now rewrites `nullable: true` into `type: [T, "null"]` wherever it
inlines a schema, request bodies included. A 3.1 spec needs none of this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 12:19:31 +02:00
Stéphane Tailland 63d2d90107 refactor(mcp): generate the tool definitions from the spec
Produced by k8s-platform/tools/generate-mcp-tools.py: deck file openapi2mcp
does the conversion, and the wrapper re-injects the five things Kong's
generator cannot emit — per-tool ACLs, the MCP behavioural annotations, the
response schemas the plugin uses for structuredOutput, the real upstream host,
and JSON-only request bodies. Each is a documented gap; see
k8s-platform/MCP-TOOL-GENERATION.md.

tools-policy.yaml carries the ACLs and doubles as the allow-list: an operation
becomes a tool only if the policy names it. Kong would otherwise expose every
operation in the spec, DELETE included, and can express no authorization at all
from a bearer-token spec — silently, exit 0, no warning.

Same tools as before, same ACLs. What changed: query parameters are now
declared and callable, each tool carries its response schema so calls return
structured content instead of raw JSON, and request bodies are no longer
tripled across three identical content types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:48:38 +02:00
Stéphane Tailland 3b20b0a35b Revert stream-binding approach: merge GET into the single MCP route instead
The second ai-mcp-proxy binding (added to fix the GET/SSE 502 loop)
created a second runtime plugin instance with the same tool config,
which ai-mcp-proxy's own duplicate-name detection caught: all three
tools (list-customers/get-customer/create-customer) fell back to
auto-generated names across both instances. Confirmed via dataplane
pod logs right after applying the previous fix.

Real fix: one KongRoute for all three MCP HTTP methods (GET/POST/
DELETE) instead of two, one ai-mcp-proxy binding instead of two.
ai-mcp-proxy already tolerates multi-method routes (same "skips
method selection" notice the existing POST+DELETE route already
produces) — extending to GET is the same class of thing, not new
risk, and there is now exactly one plugin instance so no duplicate
tool names.
2026-08-23 15:39:27 +02:00
Stéphane Tailland 1d8922e1b5 Fix customers-mcp GET/SSE stream 502 loop: bind ai-mcp-proxy to the stream route too
The stream route (GET, mcp/routes.yaml) had no ai-mcp-proxy binding,
so GET requests fell through to the KongService's own upstream
target (REST_HOSTNAME, i.e. Kong's own external hostname) and
looped back into the Gateway, growing response headers each pass
until nginx rejected them (502, "upstream sent too big header").
Confirmed via customers dataplane pod logs while debugging a
LibreChat connection failure. catalog-mcp has the same structural
gap but happens to 405 instead of looping there — not touched here.
2026-08-23 15:25:47 +02:00
Stéphane Tailland a4967d13ee Addition of MCP server 2026-08-23 15:00:57 +02:00