Stéphane Tailland de31e0cd39 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:33 +02:00

payments-api

Kong routing/policy layer for the payments slice of classic-models-api (see /Users/stephanetailland/git/classic-models-api), split out per the DDD decomposition in ../kong-konnect-deployment-strategy.md. The backend itself stays a single monolith — only Kong does the domain split. Structure mirrors ../customers-api, not ../catalog-api — see ../STATUS.md section 18 for why (catalog-api's two-route MCP pattern has a latent bug; customers-api's single-route pattern is the corrected one to copy going forward).

Two exposure surfaces, both proxying to the real classic-models-api backend:

  • REST (kong/rest/) — 6 routes over Payment, which uses a composite key (customernumber + checknumber, not a single id like Customer/Product) — both URI segments are captured and passed through to the backend's own composite-key URL pattern.
  • MCP (kong/mcp/) — list-payments/get-payment (all 5 roles) and create-payment (admin only — verified against PaymentPermission's actual logic in authentication/permissions.py, not guessed: writes are admin-only in practice, customer can only write their own via the REST API directly, not exposed as an MCP tool). One KongRoute for all three MCP HTTP methods, one ai-mcp-proxy binding — see ../STATUS.md section 18 for why this matters (a second route + binding for the same plugin creates a duplicate-tool-name bug, found and fixed on customers-mcp, built correctly here from the start).
Route Method Backend path
/payments/v1/payments/ GET, POST /classic-models/api/v1/payments/
/payments/v1/payments/{customernumber}/{checknumber}/ GET, PUT, PATCH, DELETE /classic-models/api/v1/payments/{customernumber}/{checknumber}/
/payments-mcp GET, POST, DELETE MCP streamable-http (tools call into the REST routes above)

Auth

openid-connect on both surfaces, distinct audience each (payments-api for REST, mcp-payments for MCP) with a real RFC 8693 exchange chain: MCP → payments-api → classic-models-api, same pattern as catalog-api/customers-api. IdP is the local Keycloak (https://keycloak.sttlab.local/realms/classic-models) — see ../STATUS.md sections 15/16. payments-api-audience and mcp-payments-audience client scopes + payments-api/mcp-payments resource clients created on the local Keycloak, attached to token-exchange-broker's default scopes (REST audience) and to librechat's optional scopes (MCP audience) respectively — no dedicated protocol mappers on either new client itself, same practice as every other domain built this session. mcp-payments's OAuth callback added to librechat's redirectUris, and a payments-mcp server entry added to LibreChat's librechat.yaml (infra-librechat repo).

Other policies

Per-consumer rate limiting, response caching (GET/HEAD), audit log (http-log, endpoint still a placeholder — no real log collector in this lab, same caveat as the other domains).

Structure

Same dev/ + kong/rest/ layout as customers-api. Deploy key (kong-v2/payments-api/git-deploy-key) is a copy of catalog-api's, not a freshly generated one — per the user's explicit instruction this session ("no reason a working key for catalog-api wouldn't work for customers-api"), the same reused key is used here too rather than minting yet another one per domain.

S
Description
No description provided
Readme 70 KiB