Repository navigation
test(mcp): end-to-end Kuadrant MCP Gateway authorization acceptance for the Responses agentic loop - #1833
Draft
leseb wants to merge 15 commits into
Draft
test(mcp): end-to-end Kuadrant MCP Gateway authorization acceptance for the Responses agentic loop#1833leseb wants to merge 15 commits into
leseb wants to merge 15 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Promote the proven spike into a committed harness that stands up the minimal no-full-Kubernetes real Kuadrant MCP Gateway stack (Envoy -> ext_proc router -> ext_authz Authorino on a bare etcd+kube-apiserver -> broker -> fake MCP backend, plus a static-JWKS IdP) on network kuadrant-accept and emits .stack-env for the acceptance driver. Engine-parametric (CONTAINER_ENGINE: podman/docker). Part of issue praxis-proxy#1673 acceptance (no Praxis product-code changes). Signed-off-by: Sébastien Han <seb@redhat.com>
Add examples/configs/openai/responses/mcp-kuadrant-gateway.yaml (Topology A: the per-user OAuth Bearer is the enforcement carrier; gateway Authorino mints the wristband and authorizes). Add a config-validity functional test that proves the example loads and its pipeline builds, and sync examples/README.md. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Add the Python acceptance driver (stack/scripted_model/praxis fixtures + raw-httpx Responses helpers) and prove item 1: userA (tool:get_forecast) discovers weather_get_forecast, the agentic loop dispatches it through the real gateway, and the backend executes exactly once. Promote the deterministic scripted model. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
…-dispatch Item 2: userB (no roles) sees weather_get_forecast filtered out of discovery, no dispatch. Item 3a: even when the model force-emits a call to a tool userB never discovered, Praxis does not dispatch it to the gateway (exec==0). Add an input-keyed force-call mode to the scripted model. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Add switch_authconfig.sh (applies an AuthConfig variant and waits for Authorino to reconcile). Item 3b: under the open-discovery policy userB discovers the tool, but the gateway's per-call authz denies the invocation (403 -> mcp_call.error) and the backend never executes — the 'discovery is not enforcement' proof. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Teach the scripted model to emit Responses SSE when the request is streaming, and add streaming equivalents for items 1/3a/3b. Each streaming denial asserts a valid terminal SSE lifecycle, the surfaced tool error, and zero backend executions. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Add `make test-kuadrant-acceptance` (build, stack up, run the Responses-loop matrix, tear down) and .github/workflows/kuadrant.yaml, a path-gated lane modeled on postgres.yaml (changes/merge_group gate; auto-registered by ci-status.yaml). Measured ~2min warm locally; budget ~8-12min cold on CI (30min timeout). Completes increment 1 (items 1-3 buffered+streaming). Part of issue praxis-proxy#1673. Signed-off-by: Sébastien Han <seb@redhat.com>
…gress Re-add the x-mcp-authorized courier slot (callout_credentials assertions + authorization_assertion on resolve+dispatch); every request now forwards both the per-user Bearer and the wristband. Add a two-stage Envoy Lua ingress observer (capture one-way djb2 digests before the router strips x-mcp-authorized, log them labeled by the router-set x-mcp-method) and item 7: the same Bearer and wristband digests reach both discovery and dispatch. No raw credentials are logged. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
4a: a tampered per-user Bearer is forwarded opaquely (reaches the gateway, confirmed at the ingress observer) and rejected there, surfaced as a 502 connector-resolve failure; the backend never executes. Missing bearer slot fails closed AT PRAXIS (no gateway dial). 4b documents that an invalid forwarded x-mcp-authorized is NOT an enforcement point in Topology A (Authorino mints its own) — explicitly uncovered. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Add a store-enabled praxis fixture (file-backed SQLite armed before dispatch) and item 5: require_approval:never still denies an unauthorized call, and an *approved* resume re-dials the gateway and is still denied (exec==0), buffered and streaming. Requires praxis-ai built with store-sqlite. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
…proval resume Add the revoked AuthConfig variant (denies userA+get_forecast at the per-call rule, discovery unchanged) and item 6: revoke between discovery and dispatch (6a) and between approval creation and resume (6b), buffered and streaming. Each confirms the Authorino reconcile took effect (polls the decision, never races a sleep) before asserting the denial + zero execution. Also fix a flaky --tamper that could emit a still-valid signature, and refresh the runtime note. Completes the issue praxis-proxy#1673 acceptance matrix. Signed-off-by: Sébastien Han <seb@redhat.com>
Address the fresh-reviewer Minor findings worth landing: move the item-6a streaming AuthConfig switch inside try so failures always restore 'filtered' (test isolation); poll the Envoy ingress log (_ingress_since) for items 4a/7 to avoid a log-flush race; strengthen item 3a to assert NO mcp_call item at all (proves client-side non-dispatch, not merely an unsuccessful call); and document the deliberate djb2 (not SHA-256) ingress fingerprint. Suite 18/18. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Wrap a long assert! per the pinned nightly rustfmt (nightly-2026-03-28) so the CI fmt check is clean. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
Rebase onto main: 'full' is now just 'standard' (post-praxis-proxy#1718 evolution), so the acceptance binary and the example test must enable openai-all explicitly. Build the binary with 'full,openai-all,store-sqlite', gate the config-validity test module on openai-agentic-loop + openai-mcp-tools (the config uses the agentic loop), and refresh the README note. Full 18-case suite green against rebased main. Part of issue praxis-proxy#1673 acceptance. Signed-off-by: Sébastien Han <seb@redhat.com>
leseb
force-pushed
the
leseb/mcp-gw-kuadrant-fix
branch
from
October 27, 2026 14:16
6765b1c to
97782d5
Compare
Signed-off-by: Sébastien Han <seb@redhat.com>
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.
Summary
End-to-end acceptance for issue #1673: stand up a real Kuadrant MCP Gateway and
prove per-user MCP tool authorization (discovery filtering + per-call authorization)
through Praxis's OpenAI Responses agentic loop.
This is acceptance of already-delivered courier behavior — there are no Praxis
product-code changes (
git diff --stattouches onlytests/,examples/, aMakefiletarget,examples/README.md, and a new CI workflow).The gateway runs as a minimal, no-full-Kubernetes container stack (proven feasible
by spike): static Envoy →
ext_proc(MCP router) →ext_authz(Authorino, backed by abare
etcd+kube-apiserver, no operator) → broker → fake MCP backend, plus astatic-JWKS IdP. Praxis's own
rmcpclient drives it unchanged.What's proven (18 cases, buffered + streaming, all green locally)
mcp_call, exec==0mcp_call.error, exec==0 (open-discovery variant forces the call to the gateway)x-mcp-authorizedrequire_approval:neverstill denied; an approved resume re-dials the gateway and is still deniedTopology A: the per-user OAuth Bearer is the enforcement carrier (gateway Authorino
validates it, mints the discovery wristband, authorizes each call); the
x-mcp-authorizedcourier is asserted separately for correctness.How to run
make test-kuadrant-acceptance # builds praxis-ai (full,store-sqlite), stacks up, runs the matrix, tears downNeeds a container engine (
CONTAINER_ENGINE, podman/docker),kubectl,uv,openssl.See
tests/integration/kuadrant/README.md.CI
New gated lane
.github/workflows/kuadrant.yaml, modeled onpostgres.yaml: path-filtered(only MCP-courier / harness / example-config changes trigger it), merge_group
changesgate, pinned image tags, auto-registered as a required check by
ci-status.yaml.Follow-ups (deferred, non-blocking)
kubectlsetup step to the lane (ubuntu-24.04 ships it today).trap-based teardown around the wholemaketarget for local robustness.setup-rustcomposite caches cargo (30-min timeout has headroom).Draft: opening for CI signal on the new lane.
Closes #1673