v0.7.0-rc3
Pre-releaseGHSA-g53w-w6mj-hrpp — Hairpin backend-init request bypass (Critical)
The router previously authenticated its hairpin initialize request with a
static shared string (router-key header), defaulting to the literal value
secret-api-key when MCP_ROUTER_API_KEY was unset. In controller-managed
deployments the value was a SHA-256 of the MCPGatewayExtension UID, exposed
on pod.spec.containers[].command (--mcp-router-key=…) and readable by
anyone with get on the resource.
A request carrying any header value matching the static key, plus an
attacker-chosen mcp-init-host header, could rewrite the upstream
:authority and route to any backend listener registered with the
gateway — bypassing the broker capability filter and the JWT session model.
This release replaces the static check with a short-lived JWT (30s,
aud=mcp-router, purpose=backend-init, host-bound, jti) signed by the
existing HMAC session signing key. The token is generated by the router on
each backend init and validated when the hairpin request re-enters the
gateway. The HTTPRoute managed by the controller now also includes a
RequestHeaderModifier filter that strips mcp-init-host and router-key
from requests as defense-in-depth, so neither header value can reach an
upstream MCP server.
Migration:
The --mcp-router-key CLI flag and MCP_ROUTER_API_KEY environment
variable have been removed. The controller no longer emits this flag
and strips it from existing deployments on the next reconcile.
JWT_SESSION_SIGNING_KEY has been renamed to GATEWAY_SIGNING_KEY.
This key is now used for both JWT session signing and session cache
encryption key derivation. The old environment variable is still
accepted as a fallback but is deprecated. The broker/router panics on
startup if neither is set. In controller-managed deployments this is
already generated and injected automatically via
env.valueFrom.secretKeyRef.
The --session-signing-key CLI flag is now a deprecated alias for the
new --gateway-signing-key flag.
JWTManager.Validate now enforces iss=mcp-gateway, aud=mcp-gateway,
and alg=HS256. Tokens signed with other algorithms or with different
issuer/audience claims are rejected.
The RouterAPIKey field on internal/config.MCPServersConfig is removed
(Go API breaking change for any out-of-tree consumers).
No client-facing API changes; users do not need to update manifests.
Breaking Changes
toolPrefix renamed to prefix on MCPServerRegistration
The toolPrefix field on MCPServerRegistration is renamed to prefix. This field has always been a server-level namespace, not tool-specific, and the rename aligns the API with its actual semantics now that it applies to both tools and prompts.
Migration: Users must replace toolPrefix with prefix in their MCPServerRegistration manifests. Since the field has CEL immutability validation, existing resources must be deleted and recreated (not patched in-place). For bulk updates, a sed one-liner or yq edit on manifest files before kubectl apply is sufficient.
Update manifest files
sed -i 's/toolPrefix:/prefix:/g' my-mcpserverregistrations.yaml
Delete and recreate (in-place patching won't work due to immutability)
kubectl delete mcpsr --all -n
kubectl apply -f my-mcpserverregistrations.yaml
x-authorized-tools header replaced with x-mcp-authorized
The x-authorized-tools header and allowed-tools JWT claim are replaced with x-mcp-authorized and allowed-capabilities. This generalizes the authorization header to support tools, prompts, and resources in a single JWT.
Migration: AuthPolicy configurations that set the x-authorized-tools header with the allowed-tools claim must be updated to set x-mcp-authorized with allowed-capabilities. The claim value changes from map[string][]string (server to tool names) to map[string]map[string][]string (capability type to server to item names).
Old claim format:
{"allowed-tools": "{"server1":["greet"],"server2":["time"]}"}
New claim format:
{"allowed-capabilities": "{"tools":{"server1":["greet"],"server2":["time"]}}"}
--enforce-tool-filtering CLI flag renamed to --enforce-capability-filtering
The broker flag controlling strict enforcement of the authorization header is renamed. Deployments that set this flag must update their configuration.
Keycloak roles require tool: prefix
Keycloak client roles representing MCP tools must now be prefixed with tool: (e.g. greet becomes tool:greet). The Rego policy in the AuthPolicy strips this prefix when building the capabilities map. This convention supports future capability types (prompt:, resource:).
Migration: Rename existing Keycloak client roles to include the tool: prefix. This is a one-time change in the Keycloak admin console or via the Keycloak API. The tools/call AuthPolicy CEL expression must also be updated to prepend tool: when checking roles:
Old CEL:
request.headers['x-mcp-toolname'] in ...resource_access[...].roles
New CEL:
('tool:' + request.headers['x-mcp-toolname']) in ...resource_access[...].roles
Config secret YAML key changed
The serialized server config in Kubernetes secrets changes the toolPrefix YAML key to prefix. The controller rewrites config on reconciliation, so this self-heals after the first reconcile cycle. No manual action required unless external tooling parses the config secret directly.
Broker /status endpoint JSON key changed
The ServerValidationStatus JSON response from the broker /status endpoint changes the toolPrefix key to prefix. Monitoring or automation that parses this endpoint must update accordingly.
prefix field now enforces character validation
The prefix field on MCPServerRegistration now only accepts lowercase letters (a-z), digits (0-9), and underscores (_). The value must start with a letter or digit. This is enforced at the CRD schema level.
Existing MCPServerRegistration resources with non-conforming prefixes continue to function but cannot be updated. To update such resources, delete and recreate them with a conforming prefix.
New Features
Enable/disable MCPServerRegistration
MCPServerRegistration resources now support a state field (Enabled or Disabled, default Enabled). Setting state: Disabled tells the broker to disconnect from the upstream server and remove all its tools and prompts from the gateway. The registration is preserved and can be re-enabled at any time by setting the field back to Enabled, restoring the server's capabilities without recreating any resources.
The status condition reason reflects the current state: Ready, NotReady, or Disabled.
Prompt federation
The gateway now federates MCP prompts from upstream servers alongside tools. Prompts are prefixed with the same prefix value as tools and are accessible via prompts/list and prompts/get through the gateway. Authorization filtering supports prompts via the "prompts" key in the allowed-capabilities JWT claim. Virtual servers can scope prompts via the spec.prompts field on MCPVirtualServer.
OAuth protected resource configuration via CRD
The oauthProtectedResource field on MCPGatewayExtension replaces the previous approach of manually setting OAUTH_* environment variables on the broker-router deployment. When set, the controller injects OAUTH_RESOURCE_NAME, OAUTH_RESOURCE, OAUTH_AUTHORIZATION_SERVERS, OAUTH_BEARER_METHODS_SUPPORTED, and OAUTH_SCOPES_SUPPORTED env vars automatically. When removed, the env vars are cleaned up on the next reconcile.
Only authorizationServers is required; resourceName defaults to "MCP Server", resource defaults to https:///mcp, bearerMethodsSupported defaults to ["header"], and scopesSupported defaults to ["basic"].
spec:
oauthProtectedResource:
authorizationServers:
- "https://keycloak.example.com/realms/mcp"
scopesSupported:
- "basic"
- "groups"
Migration: Replace any kubectl set env deployment/mcp-gateway commands that set OAUTH_* variables with a kubectl patch mcpgatewayextension using the oauthProtectedResource field. The controller will manage the env vars from that point forward. See the authentication guide for the updated workflow.
Note: Existing OAUTH_* environment variables set directly on the deployment will be removed on the next controller reconcile after upgrading. To preserve your settings you must set the values via the oauthProtectedResource field before or immediately after upgrading.
Generalized authorization header (x-mcp-authorized)
The authorization layer now supports a capability-typed JWT structure. The allowed-capabilities claim contains a nested map keyed by capability type (tools, prompts, resources), enabling per-capability authorization in a single header. This prepares the gateway for prompt and resource federation.
Go type change: The JWT parsing function returns map[string]map[string][]string. Each filter handler receives its slice via the appropriate key (capabilities["tools"], capabilities["prompts"]). The filterToolsByServerMap function signature is unchanged.
Enforcement semantics: A missing capability key (e.g. no "prompts" key in the JWT) means the JWT makes no assertion about that capability — behavior depends on the --enforce-capability-filtering flag. An empty map ("prompts": {}) explicitly denies all items of that capability type.
What's Changed
- fix: align OIDC server expected audience with Keycloak client ID by @trepel in #705
- fix: broker service selecting controller pods due to shared helm labels by @jasonmadigan in #704
- Add RC test matrix GitHub issue template by @david-martin in #700
- Update quick-start script default version by @david-martin in #707
- only deploy everything server for local dev by @jasonmadigan in #702
- Add RC test matrix phase to release process by @david-martin in #711
- generate JWT session signing key via controller-managed secret, instead of hardcoded default by @jasonmadigan in #715
- update quick start guide by @Patryk-Stefanski in #730
- Deploy test MCP servers as part of auth-example-setup by @trepel in #727
- Authentication review by @Patryk-Stefanski in #734
- fixes: tool revocation guide by @jasonmadigan in #738
- Fix broken verification and resource names in external MCP server guide by @jasonmadigan in #736
- rename mcp-controller Deployment to mcp-gateway-controller by @trepel in #729
- fix gevals workflow and add failure detection to it by @trepel in #724
- fix: escape ginkgo focus regex, use gcr mirror for redis, and make ki… by @trepel in #723
- docs: update authorization guide by @Patryk-Stefanski in #747
- docs: update scaling guide by @Patryk-Stefanski in #752
- fix configure-mcp-gateway-listener-and-router guide by @jasonmadigan in #751
- fixes: docs updates for otel guide by @jasonmadigan in #749
- fix: some small tweaks to virtual mcp servers guide after verification by @jasonmadigan in #746
- 409 fix inspect server targets by @jasonmadigan in #701
- docs: isolated-gateway-deployment guide has confusing Kind/NodePort section by @jasonmadigan in #744
- fixes for the register mcp servers guide by @jasonmadigan in #733
- draft: add security architecture doc by @maleck13 in #713
- doc-verifcation skill - suggested fixes by @Patryk-Stefanski in #757
- rc2 isolated gateway deployment guide review by @Patryk-Stefanski in #770
- Review of rc2 guides by @jasonmadigan in #769
- review of revocation,authz,auth guides for 0.6.0-rc2 by @Patryk-Stefanski in #767
- make e2e test hostnames and gateway URLs configurable via env vars by @maleck13 in #765
- Fix/remove links to developer content by @david-martin in #777
- fix(ci): update mcpchecker verify command to result verify by @bentito in #784
- add additional handler for client requesting /.well-known/oauth-prote… by @maleck13 in #780
- pin mcp-inspector in make targets by @jasonmadigan in #781
- Fix CI image mismatch and remove stale docs by @jasonmadigan in #774
- Claude oauth access by @maleck13 in #782
- Fix issuerUrl scheme to https across docs by @david-martin in #799
- fix(oidc-server): handle JWT aud claim as string or array by @trepel in #793
- Broker performance test harness + some fixes by @jasonmadigan in #797
- move to streamed mode for the ext_proc body by @maleck13 in #802
- Olm dependency Kuadrant by @maleck13 in #764
- CLAUDE.md some improvements and removal of unneeded content by @maleck13 in #801
- codify perf patterns & some small claude.md updates by @jasonmadigan in #808
- fix controller integration tests and add CI workflow by @maleck13 in #810
- prompt federation design doc by @Patryk-Stefanski in #796
- fixed the link of getting started by @malladinagarjuna2 in #803
- docs: document dev environment Make targets by @k1chik in #823
- Update vision to include non goal on transforming tools by @david-martin in #819
- fix : Add an image_tag parameter for custom naming in BuildImage action by @LAKSHJAIN14 in #814
- exp: claude release command by @jasonmadigan in #795
- refactor allowed-tools to allowedCapabilities by @Patryk-Stefanski in #821
- broker: surface viper errors and document inotify requirement by @k1chik in #837
- upgrade Keycloak to 26.4 and update proxy configuration by @trepel in #874
- Tool prefix rename by @Patryk-Stefanski in #842
- use MCPGatewayExtension for trusted headers key configuration by @trepel in #877
- add latest lib for mcp client that fixes #813 by @maleck13 in #884
- Fix: concurrent map race in session cache by @Aman-Cool in #872
- use maps.Clone in copy-on-write mutations by @Aman-Cool in #888
- Makefile: print MCP Gateway URL after local-env-setup by @shiavm006 in #858
- add URL elicitation design doc for per-user credentials by @maleck13 in #800
- Bump go to 1.25.9 by @david-martin in #891
- fix up linting by @Patryk-Stefanski in #895
- Add tool discovery and virtual server v2 proposal docs by @maleck13 in #902
- fix: run spell check on docs-only PRs by @david-martin in #845
- fix: use context.Background() in sessionCloser to prevent Redis sessi… by @Aman-Cool in #911
- feat: add support for productized MCP Gateway installation by @trepel in #887
- feat(broker): add /readyz endpoint and wire readinessProbe by @k1chik in #848
- fix(broker): remove nested RLock in ValidateAllServers by @k1chik in #921
- feat(broker): add /healthz endpoint and readiness initialDelaySeconds by @Aman-Cool in #932
- docs: rewrite tool filtering guide and deduplicate revocation setup by @Vasudev2401 in #806
- deps(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp from 0.16.0 to 0.19.0 by @dependabot[bot] in #739
- broker: guard gatewayServer nil dereference in MCPManager by @mugiwaraluffy56 in #886
- deps(deps): bump github.com/envoyproxy/go-control-plane/envoy from 1.36.0 to 1.37.0 in the envoy group across 1 directory by @dependabot[bot] in #698
- fix: serialize concurrent manage calls in MCPManager by @Aman-Cool in #936
- feat: add envoy-admin-forward make target by @DCchoudhury15 in #815
- chore(ci): improve cspell integration and fix typos by @vibhor-5 in #915
- fix: eliminate data race between LoadConfig and broker OnConfigChange by @Aman-Cool in #922
- Add design for Auditing by @david-martin in #914
- chore(ci): add cspell words for GHSA advisory by @david-martin in #962
- Refactor/manager channel event loop by @maleck13 in #943
- Adding prompt federation by @Patryk-Stefanski in #892
- Remove unnecessary span.IsRecording() guard guidance by @david-martin in #969
- chore(ci): remove gevals integration test workflow by @david-martin in #972
- fix(router): use https scheme for hair-pinning when listener is HTTPS by @Bhuvanesh66 in #942
- docs: add mcps listener to the gateway setup guide by @mugiwaraluffy56 in #855
- lint: enable golangci-lint for e2e tests by @shiavm006 in #841
- fix defer func in happy path tests to unblock CI tests by @Patryk-Stefanski in #978
- fix : Adding a informational get endpoint and catch all route by @LAKSHJAIN14 in #832
- feat: implement exponential backoff for MCP health checks by @vibhor-5 in #948
- fix(router): prevent duplicate backend sessions on concurrent tool calls by @Aman-Cool in #976
- e2e: add concurrent session init test by @Aman-Cool in #990
- refactor(upstream): decouple backoff logic from status updates by @vibhor-5 in #982
- docs: add prompt federation user guide by @vibhor-5 in #973
- chore: enable CodeRabbit auto-labeling for review effort and risk by @david-martin in #991
- Url elicitation combined by @maleck13 in #931
- ci: remove redundant DCO workflow by @david-martin in #980
- Adopt RFC 0018 release branch naming (release-X.Y) by @thomasmaas in #977
- feat: add OTel tracing and context-aware logging to broker and router by @Patryk-Stefanski in #955
- feat: implement tool discovery (CRD metadata, discover_tools, select_tools) by @jasonmadigan in #944
- invalidate cached user token on 401 from upstream by @maleck13 in #1015
- test: add remaining e2e tests for prompt federation by @Patryk-Stefanski in #998
- docs: clarify Kuadrant namespace when installed via OLM by @Aditya7880900936 in #839
- docs: update upstream docs for prompt federation by @Patryk-Stefanski in #1022
- fix: hold mcpLock.RLock in findServerByName to prevent concurrent map panic by @Aman-Cool in #1032
- fix: correct broken ToC anchor links in flows.md by @vishnudathks in #1028
- PR Title: docs: remove repo-specific setup steps from kubernetes MCP server guide by @Aryanburnwal05 in #1016
- refactor(broker): rename jwtSigningKeyFlag to gatewaySigningKeyFlag by @vibhor-5 in #971
- fix(session): set Redis TTL on session entries to match JWT expiry by @Aman-Cool in #1037
- fix: complete jwtSigningKeyFlag rename missed in #971 by @david-martin in #1036
- docs: document GATEWAY_SIGNING_KEY rename in release notes by @david-martin in #1035
- add the enable/disable support for mcp by @malladinagarjuna2 in #871
- docs: add design spec for custom TLS configuration (#659) by @Patryk-Stefanski in #1008
- feat: add CRD validation for MCPServerRegistration prefix field by @Patryk-Stefanski in #1026
- test: enable x-mcp-authorized JWT header e2e test by @trepel in #1021
- Add auditing guide for MCP tool call logging by @david-martin in #1023
- feat: move oauth protected resource config to MCPGatewayExtension spec by @Patryk-Stefanski in #1041
- make e2e tests configurable for OpenShift deployments by @trepel in #963
- refactor: slim CLAUDE.md, move checklists to .claude/rules/ by @maleck13 in #1034
- feat: add k6 performance benchmarking with CI regression gating by @jasonmadigan in #1049
- 579 mcpsr tags by @Patryk-Stefanski in #1024
- user-specific tool list design and implementation by @maleck13 in #1019
- fix: add explicit runAsUser to k6 benchmark job by @jasonmadigan in #1055
- fix: add rich HTML performance report to CI benchmark by @jasonmadigan in #1056
- fix: add git config to benchmark gh-pages publish step by @jasonmadigan in #1057
- ci: add test-tls-server to test server image build workflow by @trepel in #1050
- feat: add HTTPS e2e tests and CI workflow for TLS backends by @Patryk-Stefanski in #1054
- test: refactor trusted headers setup into reusable helper by @trepel in #1048
- Update version to 0.7.0-rc1 by @Patryk-Stefanski in #1061
- chore: clean up of main and removal of dead comments by @maleck13 in #1060
- Merge main into release-0.7.0 by @Patryk-Stefanski in #1062
- docs: add design spec for gateway-level CA certificate bundle by @vibhor-5 in #1046
- fix issue with configure-redis add user specific server to test-images by @maleck13 in #1069
- Merge main into release-0.7.0 rc3 by @Patryk-Stefanski in #1070
New Contributors
- @malladinagarjuna2 made their first contribution in #803
- @k1chik made their first contribution in #823
- @LAKSHJAIN14 made their first contribution in #814
- @Aman-Cool made their first contribution in #872
- @shiavm006 made their first contribution in #858
- @Vasudev2401 made their first contribution in #806
- @mugiwaraluffy56 made their first contribution in #886
- @DCchoudhury15 made their first contribution in #815
- @vibhor-5 made their first contribution in #915
- @Bhuvanesh66 made their first contribution in #942
- @thomasmaas made their first contribution in #977
- @Aditya7880900936 made their first contribution in #839
- @vishnudathks made their first contribution in #1028
- @Aryanburnwal05 made their first contribution in #1016
Full Changelog: v0.6.1...v0.7.0-rc3