Releases: Kuadrant/mcp-gateway
Release list
v0.9.0
What's Changed
- scope out broker changes for 2026 support by @maleck13 in #1314
- Go lib update by @maleck13 in #1318
- Bump version to 0.8.0 by @david-martin in #1323
- Add an updated design for the router allowing it to use streamed body in some cases by @maleck13 in #1328
- fix(e2e): stabilize dual-protocol and version negotiation tests by @trepel in #1329
- fix: reject non-CA certificates in validateCACertPEM by @kkukadia in #1322
- docs: add A2A protocol support design document by @Aman-Cool in #1114
- deps(deps): bump the k8s-core group across 1 directory with 3 updates by @dependabot[bot] in #1306
- deps(deps): bump github.com/go-logr/logr from 1.4.3 to 1.4.4 by @dependabot[bot] in #1307
- deps(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.0 in the grpc group by @dependabot[bot] in #1340
- deps(deps): bump github.com/prometheus/client_golang from 1.23.2 to 1.24.1 by @dependabot[bot] in #1341
- deps(deps): bump istio.io/client-go from 1.29.0 to 1.30.3 by @dependabot[bot] in #1308
- fix: propagate upstream 4xx status codes during lazy init by @david-martin in #1332
- fix(otel): update sample Grafana dashboard and add Prometheus scrape config by @Patryk-Stefanski in #1330
- Protocol handler broker (ground work) by @maleck13 in #1325
- Protocol handler broker task 4 by @maleck13 in #1326
- feat: umbrella operator integration (kuadrant-operator managed mcp-gateway) by @Patryk-Stefanski in #1239
- e2e: add A2A test server by @Aman-Cool in #1200
- chore: pin GitHub Actions to commit SHAs using ratchet by @averevki in #1350
- task 5 completed for 2026 protocol broker by @maleck13 in #1346
- minor updates to arch docs by @maleck13 in #1359
- Design for integrating guard rails into the MCP Gateway by @maleck13 in #1302
- Add monthly workflow to update gh action references with ratchet by @averevki in #1358
- feat(router): structured audit log for tool calls by @Patryk-Stefanski in #1347
- ci: add ppc64le to image build platforms by @Patryk-Stefanski in #1361
- Clarify with links to envoy docs around the body mode decisions for the router by @maleck13 in #1364
- fix: handle SIGTERM so graceful shutdown runs in Kubernetes by @Aman-Cool in #1362
- feat(resources-federation): add upstream plumbing + broker federation by @kkukadia in #1310
- e2e: harden A2A test server request lifetime and task retention by @Aman-Cool in #1366
- Protocol handler broker t6 & t7 by @maleck13 in #1360
- ci: bump contributor-governance pin to include #7 fix by @david-martin in #1373
- fix(catalog): disable opm serve pprof to unblock multi-arch build by @david-martin in #1365
- deps(deps): bump github.com/redis/go-redis/v9 from 9.18.0 to 9.22.0 by @dependabot[bot] in #1355
- deps(deps): bump the otel group with 13 updates by @dependabot[bot] in #1354
- Update version to 0.9.0 by @david-martin in #1375
New Contributors
Full Changelog: v0.8.0...v0.9.0
v0.8.0
What's Changed
- update to security arch doc by @maleck13 in #1072
- fix stale docs: phantom fields, wrong secret refs, outdated detection logic, historical plumbing by @jasonmadigan in #1074
- feat: add review-with-plan skill for plan-aligned PR reviews by @maleck13 in #1059
- e2e: run only [Happy] tests on PRs, full suite nightly. Closes #960 by @manik3160 in #961
- add a maintainence prompt to help identify general improvements by @maleck13 in #1063
- docs: add kubelet Secret sync delay to troubleshooting guide by @jasonmadigan in #1094
- fix: deleted MCPVirtualServer resources persist in broker after config change by @ayuxsh009 in #985
- docs: enforce issue-first PR rule, replace AGENTS.md with symlink by @jasonmadigan in #1112
- fixes : #1089 (L3) by @aniketpandey05 in #1096
- ci: cut PR-gate e2e wall time via setup image caching and rollout-churn fixes by @jasonmadigan in #1121
- chore: add PR template with pre-review checklist by @david-martin in #1007
- fix(remote-github): validate AuthPolicy CRD and ensure namespace befo… by @Aditya7880900936 in #852
- docs: exempt bugfixes and security patches from issue-first rule by @jasonmadigan in #1126
- fix: rewrite elicitation IDs in sseRewriter.Flush() for final partial SSE lines by @Prachi194agrawal in #967
- removed the mcp lock from validateservertools by @malladinagarjuna2 in #1101
- fixes: #1089 (L7) by @aniketpandey05 in #1097
- test(e2e): cover prompts/list filtering via x-mcp-authorized JWT by @namansh70747 in #1118
- ci: pre-baked e2e node image and lighter PR-gate test scope by @jasonmadigan in #1122
- fix: clean up elicitation idmap entries on interrupted ext_proc streams by @shiavm006 in #941
- fix(session): bound JWTManager.Terminate cache deletion with a 5s timeout by @SarthakB11 in #924
- fix(broker): add transport timeouts to upstream HTTP client by @xenonnn4w in #1047
- Fix tls hairpin by @maleck13 in #1133
- docs: fix MCPServer typo in test_cases.md by @muskiteer in #1127
- collecting manual test cases for release by @trepel in #1025
- Update determineProtocol: incorrectly using listener protocol for all backend connections by @maleck13 in #1135
- per-server SNI for hairpin TLS connections by @maleck13 in #1156
- fix: align CI Kind cluster setup with pinned binary across all workflows by @jasonmadigan in #1154
- add some invariants to help agents avoid mistakes by @maleck13 in #1166
- Add PR focus workflow for contributor PR management by @david-martin in #1168
- Drop auto-draft conversion from PR focus workflow by @david-martin in #1173
- fix(broker): stop forwarding
Cookie/Proxy-Authorizationto user-specific upstreams by @PRAteek-singHWY in #1172 - Add s390x platform support in workflows by @Niha-2612 in #1157
- fix: correct Sever Server typo in method and variable names by @aniketpandey05 in #1175
- ci: add linkinator broken-link check for documentation by @Boomatang in #952
- fix(broker): use router-injected verified sub for elicitation identity binding by @namansh70747 in #1116
- test: add protocol version coverage (#363) by @namansh70747 in #1075
- update docs to call out single listener pattern for https by @maleck13 in #1163
- condition for jwt hs256 extending till 32 bytes by @malladinagarjuna2 in #1102
- Remove PR focus workflow by @david-martin in #1185
- fix missing params for upstream.MCPNamein filtered_tools_handler by @aniketpandey05 in #1180
- fix(charts): support advanced MCPGatewayExtension configuration by @rajatrsrivastav in #938
- fix(ci): add multi-stage builds to test server Dockerfiles by @david-martin in #1177
- docs: split external MCP server docs into guide and tutorial by @Aditya7880900936 in #835
- docs: add PR policy to CONTRIBUTING.md by @Patryk-Stefanski in #1193
- docs: add rule against working on untriaged issues by @Patryk-Stefanski in #1196
- deps(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.1 in the grpc group across 1 directory by @dependabot[bot] in #1003
- ci: add e2e compile check to test workflow by @jasonmadigan in #1183
- Gh 1100 by @maleck13 in #1149
- Post-release: bump version to 0.7.1, update release branch naming by @david-martin in #1161
- deps(deps): bump the otel group across 1 directory with 10 updates by @dependabot[bot] in #1071
- improve the token url elicitatoin demo by enabling authpolicies to be… by @maleck13 in #1064
- fix: use BUFFERED request body mode for reliable ClearRouteCache by @maleck13 in #1201
- remove controller->broker status polling for MCPServerRegistration by @jasonmadigan in #1187
- fix: update broken documentation links by @david-martin in #1202
- Remove static HTTPRoute duplication by @Aditya7880900936 in #975
- chore: add contributor governance workflow by @R-Lawton in #1210
- fix(broker-router): honor
--log-level=4(warn) instead of defaulting to debug by @PRAteek-singHWY in #1141 - docs: add contributor limits for issues and PRs by @Patryk-Stefanski in #1211
- fix: add /status route to gateway HTTPRoute and fix conformance CI poll by @jasonmadigan in #1214
- fix: remove dead HTTPRoute patch from mcp-system overlay by @aniketpandey05 in #1204
- write MCPVirtualServer config to all MCPGatewayExtension namespaces by @Patryk-Stefanski in #1212
- deps(deps): bump github.com/mark3labs/mcp-go from 0.53.0 to 0.55.1 by @dependabot[bot] in #1209
- deps(deps): bump istio.io/api from 1.29.0 to 1.30.2 by @dependabot[bot] in #1208
- deps(deps): bump the golang-x group across 1 directory with 2 updates by @dependabot[bot] in #1207
- added a test for the non-default path registration by @malladinagarjuna2 in #1179
- fix(mcp-router): make ExtProcServer.RoutingConfig swap race-free by @SarthakB11 in #925
- deps: bump k8s v0.36, controller-runtime v0.24.1, gateway-api v1.6.0 by @Patryk-Stefanski in #1216
- fix: expire elicited user tokens by @avinxshKD in #1151
- fix: use direct map lookup in GetVirtualServerByHeader by @aniketpandey05 in #1189
- E2e test improvements - Parallel execution by default by @maleck13 in #1225
- fix: derive internal hostname from gatewayClassName instead of hardcoding istio by @Patryk-Stefanski in #1241
- feat: add caCertBundleRef to MCPGatewayExtension by @david-martin in #1224
- feat: add spec.logLevel field to MCPGatewayExtension by @aniketpandey05 in #1153
- fix: add Serial decorator to CA Cert Bundle e2e test by @david-martin in #1246
- feat: migrate from mark3labs/mcp-go to official MCP Go SDK by @jasonmadigan in #1218
- Review and improve issue-triage workflow (#650) by @vibhor-5 in #906
- fix: correct actions/github-script SHA in issue-triage workflow by @Patryk-Ste...
v0.7.1
What's Changed
- Cherry-pick TLS hairpin fix and bump version to 0.7.1 by @Patryk-Stefanski in #1134
- Cherry-pick per-server SNI fix for 0.7.1 by @david-martin in #1159
Full Changelog: v0.7.0...v0.7.1
v0.7.0
GHSA-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 de...
v0.7.0-rc3
GHSA-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 depl...
v0.7.0-rc2
GHSA-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-keyCLI flag andMCP_ROUTER_API_KEYenvironment
variable have been removed. The controller no longer emits this flag
and strips it from existing deployments on the next reconcile. JWT_SESSION_SIGNING_KEYhas been renamed toGATEWAY_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-keyCLI flag is now a deprecated alias for the
new--gateway-signing-keyflag. JWTManager.Validatenow enforcesiss=mcp-gateway,aud=mcp-gateway,
andalg=HS256. Tokens signed with other algorithms or with different
issuer/audience claims are rejected.- The
RouterAPIKeyfield oninternal/config.MCPServersConfigis 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 <namespace>
kubectl apply -f my-mcpserverregistrations.yamlx-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://<publicHost>/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 theoauthProtectedResourcefield 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 https://github.c...
v0.6.1
What's Changed
- cherry-pick docs fixes to release-0.6.0 by @jasonmadigan in #779
Bug fixes
- #872 - Fix concurrent map race in session cache (AddSession/RemoveServerSession)
- #888 - Use maps.Clone in copy-on-write session cache mutations (follow-up to #872)
- #884 - Bump mcp-go lib (fixes #813 - re-initialise MCP sessions on failure)
- #793 - OIDC server: handle JWT aud claim as string or array
Performance
- #797 - Reduce GC pressure from tool map lookups, skip OTel span extraction when tracing disabled, fix hot-path logging mutex contention
- #802 - Move to streamed mode for ext_proc (removes dependency on Envoy buffer limits)
Infrastructure
- #837 - Surface viper config errors and document inotify requirement
- #764 - OLM Kuadrant dependency and make targets
- #874 - Upgrade Keycloak to 26.4
- #877 - JWT key Makefile fix
Full Changelog: v0.6.0...v0.6.1
v0.6.1-rc1
What's Changed
- cherry-pick docs fixes to release-0.6.0 by @jasonmadigan in #779
Full Changelog: v0.6.0...v0.6.1-rc1
v0.6.0
What's Changed
- Set gateway public host and internal mcp servers listener in gateway … by @david-martin in #515
- Fixes #485: adds Overview doc for MCP Gateway by @ShaunaDiaz in #508
- Add releasing instructions by @david-martin in #516
- Thread safety by @esnible in #521
- Remove dead code by @esnible in #522
- Contributes to #483: registering an MCP server by @ShaunaDiaz in #518
- Updates required if using external k8s cluster by @trepel in #526
- deps(deps): bump istio.io/client-go from 1.28.2 to 1.28.3 by @dependabot[bot] in #507
- Implement isolated MCP gateway install by @maleck13 in #524
- Allow for rc releases by @david-martin in #533
- Various updates to release process by @david-martin in #534
- fix: GET /mcp after authentication by @guicassolato in #536
- create the gateway-system ns by @maleck13 in #544
- add custom-response-server image build and improve Dockerfiles by @trepel in #545
- fix oauth client names by @guicassolato in #546
- Resolves #297: document integrating Vault for MCP Gateway by @ShaunaDiaz in #519
- Authorise test2_headers tool by default by @guicassolato in #548
- remove duplicate by @maleck13 in #550
- Pin conformance server to v0.1.9 by @trepel in #551
- Ensure empty array for json serialize when no tools by @david-martin in #547
- Migrate broken-server and custom-path-server images to kuadrant org by @trepel in #552
- Remove ACL auth example by @guicassolato in #549
- More unit tests by @esnible in #513
- Test cases for ext proc and protection by @esnible in #514
- add second gateway plus some small fixes to the helm template by @maleck13 in #538
- fix bug gh-555 by @maleck13 in #556
- Install updates by @maleck13 in #554
- fix: mcp inspector oauth redirect by @guicassolato in #557
- Add vault integration to TOC by @david-martin in #562
- Fixed broken links to MCP Server Registration guide by @trepel in #563
- set workload selector. Remove duplicate templates by @maleck13 in #564
- deps(deps): bump sigs.k8s.io/controller-runtime from 0.22.4 to 0.23.1 by @dependabot[bot] in #561
- Fix docs link by @david-martin in #569
- e2e test changes based on running against ocp and local setup by @maleck13 in #568
- Remove spaces causing helm install failure by @david-martin in #577
- BUGFIX: Resolve Gateway Hostname to IP for hostAliases patching by @bentito in #539
- use slog for consistent log formatting by @Patryk-Stefanski in #580
- deps(deps): bump github.com/golang-jwt/jwt/v5 from 5.3.0 to 5.3.1 by @dependabot[bot] in #584
- fix(make) Fixed race condition on Makefile. by @matzew in #588
- remove verbose full config log by @maleck13 in #591
- claude generated e2e on demand workflow by @maleck13 in #527
- draft of operator based deployment by @maleck13 in #570
- bug fix: filter out gateway specific metadata from tools by @maleck13 in #589
- add some more simplified flow diagrams restructure the doc a little by @maleck13 in #582
- deps(deps): bump k8s.io/api from 0.35.0 to 0.35.1 by @dependabot[bot] in #593
- deps(deps): bump github.com/onsi/gomega from 1.39.0 to 1.39.1 by @dependabot[bot] in #559
- Guard against malformed JSON by @esnible in #603
- Router otel tracing by @Patryk-Stefanski in #595
- Helm test by @maleck13 in #606
- Support 'OPTIONS /mcp' (used by MCP Inspector) by @esnible in #605
- Operator deploy impl by @maleck13 in #602
- Add automated integration tests using mcpchecker (gevals) by @bentito in #586
- Update release process for post release and fix 0.5.0 install steps by @david-martin in #618
- Update Notifications design doc to reflect the currrent state of the gateway by @david-martin in #612
- Adding authorization note to virtual mcp servers.md by @Patryk-Stefanski in #611
- Fix mcp-gateway deployment name by @david-martin in #620
- Operator deploy p2 by @maleck13 in #617
- Operator-based deployment with automatic HTTPRoute creation by @maleck13 in #619
- Fix nil pointer when no parent Accepted contition by @david-martin in #627
- Breakdown CLAUDE.md into relevant dirs by @Patryk-Stefanski in #610
- deps(deps): bump github.com/onsi/ginkgo/v2 from 2.27.4 to 2.28.1 by @dependabot[bot] in #558
- deps(deps): bump github.com/redis/go-redis/v9 from 9.14.0 to 9.18.0 by @dependabot[bot] in #594
- deps(deps): bump google.golang.org/grpc from 1.78.0 to 1.79.1 by @dependabot[bot] in #607
- deps(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc from 1.34.0 to 1.40.0 by @dependabot[bot] in #608
- deps(deps): bump istio.io/api from 1.28.2-0.20251205082437-fde1452f70bc to 1.29.0 by @dependabot[bot] in #609
- feat(elicitations): add idmap package to map request ids by @Cali0707 in #626
- Bump the go_modules group across 5 directories with 1 update by @dependabot[bot] in #621
- Minor follow ons from the httproute work by @maleck13 in #631
- MCPGatewayExtension spec by @maleck13 in #634
- Trusted header key pair by @maleck13 in #639
- Add coderabbit config by @david-martin in #641
- Add path matching/ignore rules for heavier workflows by @david-martin in #642
- Claude update by @maleck13 in #632
- OLM bundle generation and deployment by @Patryk-Stefanski in #637
- Add new tests and docs for redis integration & scaling by @david-martin in #635
- Update bundle and catalog to be built for amd64 & arm64 by @Patryk-Stefanski in #652
- Only compare managed flags when checking deployment updates by @maleck13 in #657
- bug: use mcpserverregistration in the remote-github sample by @jasonmadigan in #660
- Reword to be non specific to redis implementation by @david-martin in #656
- Make helm install script idempotent and fix Image pull errors by @david-martin in #658
- Update releasing.md by @Patryk-Stefanski in #655
- Add documentation guidelines (CLAUDE.md) by @david-martin in #636
- bug: fix OpenShift deploy script CatalogSource and CSV wait by @matzew in #664
- feat(elicitation): handle elicitation request and response flow by @Cali0707 in #643
- Add tools revocation guide by @david-martin in #654
- Redis secret configuration in the MCPGatewayExtension by @maleck13 in #661
- Reference latest released version on main always by @david-martin in https://github...
v0.6.0-rc2
What's Changed
- cherry-picks for v0.6.0-rc2 by @jasonmadigan in #726
- Cherry-picks for v0.6.0-rc2 by @jasonmadigan in #755
- Update version to 0.6.0-rc2 by @jasonmadigan in #758
Full Changelog: v0.6.0-rc1...v0.6.0-rc2