From 92f0a272a5dd3be8153c3b9b35e986c4239d7497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tailland?= Date: Fri, 28 Aug 2026 10:36:47 +0200 Subject: [PATCH] docs: correct the audience table and refresh the blockers - Two audience scopes per MCP server, not one: Kong's own openid-connect plugin re-exchanges the token, so it must also name kong-token-exchange-broker. Measured on catalog. - Drop the resolved Konnect-not-persisting blocker; the cause was a dead Konnect org, not Konnect refusing writes. - Drop the resolved kong-token-exchange-broker secret mismatch; its client_credentials returns 200. - Record the real blocker: PydanticAI's MCP client cannot open a session against ai-mcp-proxy. - Document the realm's override-on-start import and ${KC_SECRET_*} placeholders, and the a2a-sdk 1.x serving requirements. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 60 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8b8017a..de42de7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,13 +95,21 @@ access token five minutes. MCP servers, one per domain, served by Kong: -| Domain | URL | Audience scope | +| Domain | URL | Audience scopes to request | |---|---|---| -| catalog | `https://catalog-dev.sttlab.local/catalog-mcp` | `mcp-catalog-audience` | -| customers | `https://customers-dev.sttlab.local/customers-mcp` | `mcp-customers-audience` | -| payments | `https://payments-dev.sttlab.local/payments-mcp` | `mcp-payments-audience` | -| sales | `https://sales-dev.sttlab.local/sales-mcp` | `mcp-sales-audience` | -| workforce | `https://workforce-dev.sttlab.local/workforce-mcp` | `mcp-workforce-audience` | +| catalog | `https://catalog-dev.sttlab.local/catalog-mcp` | `mcp-catalog-audience kong-token-exchange-broker-audience` | +| customers | `https://customers-dev.sttlab.local/customers-mcp` | `mcp-customers-audience kong-token-exchange-broker-audience` | +| payments | `https://payments-dev.sttlab.local/payments-mcp` | `mcp-payments-audience kong-token-exchange-broker-audience` | +| sales | `https://sales-dev.sttlab.local/sales-mcp` | `mcp-sales-audience kong-token-exchange-broker-audience` | +| workforce | `https://workforce-dev.sttlab.local/workforce-mcp` | `mcp-workforce-audience kong-token-exchange-broker-audience` | + +**Two scopes per server, not one.** The gateway's own `openid-connect` plugin +exchanges the token again before reaching the backend, so the token must also +name `kong-token-exchange-broker`. Without it the call fails with a bare `401` +and the real cause appears only in the gateway log — *invalid status code +received from the token endpoint (403)*. Measured on catalog: with both scopes +the MCP server answers `200`, with `mcp-catalog-audience` alone it answers +`401`. LibreChat has always requested both. LLM inference goes through the Kong AI gateway, never direct to a provider: @@ -153,12 +161,32 @@ The realm actually deployed is `infra-keycloak/k8s/keycloak/configmap-realm.yaml — note that `kong-v2/classic-models-realm.yaml` is an older partial copy and is **not** authoritative. -## Known blockers +**The realm file is re-imported with `--override true` on every pod start.** An +`import-realm` initContainer replays it before Keycloak serves traffic, so the +file wins over the database and nothing created through the admin console +survives a restart. Add clients here, never in the console. -**Konnect is not persisting configuration.** The operator loops on the same -objects without converging and the dataplanes stay behind the control plane, so -no new Kong config reaches the gateways — including any new agent audience. -Resolve this before expecting an end-to-end run. +**Client secrets and user passwords in that file are `${KC_SECRET_*}` / +`${KC_USER_*}` placeholders**, substituted from the `keycloak-realm-secrets` +Secret (Vault + ExternalSecret). Never commit a literal secret back into it. + +## Serving A2A + +The `a2a-sdk` 1.x server rejects a request that does not carry the protocol +version header: + +``` +A2A-Version: 1.0 +``` + +`DefaultRequestHandler` speaks the older 0.3 shape; use +`DefaultRequestHandlerV2`, and mount both `create_agent_card_routes(...)` and +`create_jsonrpc_routes(handler, "/", enable_v0_3_compat=True)`. An executor must +enqueue the `Task` before any status update, or the client blocks waiting for a +task it never saw. `AgentInterface` names the field `protocol_binding`, not +`transport`. + +## Known blockers **No `employeeNumber` in tokens.** Sales territory scoping (a rep sees their territory, a manager sees all) resolves through @@ -166,8 +194,14 @@ territory, a manager sees all) resolves through identity, so the API cannot filter server-side. The realm's users (`admin-user`, `pm-user`, …) are role placeholders, not real employees. -**The `kong-token-exchange-broker` secret does not match the realm** — its -`client_credentials` returns `unauthorized_client`. +**PydanticAI's MCP client cannot open a session against Kong's +`ai-mcp-proxy`.** It fails with `Failed to initialize server session` and +swallows the cause. Everything else was eliminated: identity, TLS, DNS and the +token are all fine, and the three handshake steps — `initialize` (200 with a +session id), the SSE `GET` (200), `notifications/initialized` (202) — all +succeed with curl using the very token the agent minted. This blocks the +Catalogue agent end to end, and with it the Kong AI gateway path, which has +therefore never been exercised. ## Conventions