list-payments/get-payment (all 5 roles) and create-payment (admin only — matches PaymentPermission's actual write-access logic, not guessed). One KongRoute for GET/POST/DELETE, one ai-mcp-proxy binding — avoids the duplicate-tool-name bug found and fixed on customers-mcp (STATUS.md section 18), built correctly here instead of repeating the two-route mistake. Keycloak (local): mcp-payments-audience client scope + mcp-payments resource client, attached to librechat as an optional scope, no dedicated protocol mappers on the client itself.
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 overPayment, which uses a composite key (customernumber+checknumber, not a single id likeCustomer/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) andcreate-payment(admin only — verified againstPaymentPermission's actual logic inauthentication/permissions.py, not guessed: writes are admin-only in practice,customercan only write their own via the REST API directly, not exposed as an MCP tool). OneKongRoutefor all three MCP HTTP methods, oneai-mcp-proxybinding — see../STATUS.mdsection 18 for why this matters (a second route + binding for the same plugin creates a duplicate-tool-name bug, found and fixed oncustomers-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
kong-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.