Skip to content

v0.7.0-rc3

Pre-release
Pre-release

Choose a tag to compare

@Patryk-Stefanski Patryk-Stefanski released this 02 Jun 09:16
· 8 commits to release-0.7.0 since this release
f283029

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

New Contributors

Full Changelog: v0.6.1...v0.7.0-rc3