github agentic-community/mcp-gateway-registry 1.36.0
1.36.0 - Pooled mcpgw Connections, Calling Tools Found by Search, and an Opt-In OBO Token Cache

2 hours ago

Release 1.36.0 - Pooled mcpgw Connections, Calling Tools Found by Search, and an Opt-In OBO Token Cache

November 2026


Upgrading from 1.35.0

This section covers everything you need to know to upgrade from 1.35.0 to 1.36.0.

Breaking Changes

NGINX_DISABLE_API_AUTH_REQUEST=true takes effect again. The flag had done nothing since the nginx templates drifted from the text the generator searched for, so nginx kept auth_request on /api/ whatever the setting (#1964). It now removes auth_request from the /api/ location, and the registry then accepts only the browser session cookie there: Bearer tokens, M2M tokens and static API keys get 401. If you set the flag to true in extra_env/registry.env, .env, registry_extra_env or extraEnv, remove it unless you want cookie-only /api/. Podman's default changes from true to false to match every other surface, so a Podman install that never set the flag keeps the behavior it had in practice (#1985).

The Helm charts reserve more environment variable names. The auth-server chart now reserves the OBO cache, secret-store and OpenBao variables, the registry chart reserves the three OBO cache variables, and the mcpgw chart reserves the four EGRESS_HTTP_POOL_* variables (#1796, #1798). A release that sets any of them through extraEnv fails to render. Move the value to the chart's own setting, listed under New Environment Variables below.

Other upgrade notes

The registry and auth-server egress pools now use the configured keep-alive expiry. url_guard passed its pool limits to httpx next to a custom transport, which httpx ignores, so both pools ran on the httpx default of 5 seconds instead of the configured EGRESS_HTTP_POOL_KEEPALIVE_EXPIRY_SECONDS (30 by default). Idle connections now stay open for 30 seconds. Keep the value below the shortest idle timeout of any load balancer between the gateway and its upstreams, or set it to 0 to turn keep-alive reuse off. The Helm charts used to replace a pool setting of 0 with the default, and now keep it (#1796).

mcpgw hides the reason behind a blocked outbound request. When mcpgw's egress guard refuses a destination, the tool now returns "Outbound request blocked by the mcpgw egress guard. See the mcpgw logs for details." It used to return the error text, which included internal hostnames and resolved addresses. mcpgw logs the reason once at WARNING (#1982).

Per-server PRM scopes change on providers other than Entra. The Protected Resource Metadata served for obo_exchange servers, and for virtual servers backed by them, now advertises the same scopes as the gateway-wide document: openid email profile offline_access, or MCP_ADVERTISED_SCOPES when set. It used to advertise the Entra-style <resource>/user_impersonation, which Keycloak refused with invalid_scope, so no IDE could sign in to a Keycloak obo_exchange server. Entra is unchanged (#1972).

Keycloak OBO checks that the exchanged token belongs to the same user. The auth server refuses an exchanged token whose sub differs from the user's token, or a user token with no sub, and it checks the JWKS URL before sending the token request. On Entra, the audit log records unsupported_grant_type and access_denied as exchange_failed again, as it did before 1.35.0. On Docker Compose, Keycloak 25 needs admin-fine-grained-authz added to KC_FEATURES before any OBO exchange succeeds. See the Keycloak section of docs/design/egress-auth-design.md (#1957, #1983).

The registry returns text skill assets as text again. Since 1.35.0 every skill asset came back base64-encoded, and mcpgw's get_skill_content returned only metadata for templates, schemas and config files unless the caller passed include_binary=True. An asset that decodes as strict UTF-8 with no NUL bytes now comes back in content; anything else stays base64. Every resource response includes size_bytes, and mcpgw adds content_type for text resources (#1963, #1973).

The first ECS apply redeploys three services. The registry and auth-server task definitions gain the OBO cache settings, and the mcpgw task definition gains the four pool settings, so the first terraform apply after upgrading redeploys all three even with the cache off.

New Environment Variables

Variable Default Description
EGRESS_OBO_CACHE_ENABLED false Cache exchanged OBO tokens in the per-user SecretStore, so repeated calls by one user to one audience reuse a valid token. Read by the registry and the auth server.
EGRESS_OBO_CACHE_MAX_TTL_SECONDS 300 Longest time a cached token is reused, applied below the token's own expiry. This bounds how long a revocation takes to apply on cache hits.
EGRESS_OBO_CACHE_EXPIRY_SKEW_SECONDS 30 Do not reuse a cached token this close to its expiry. Must be less than the maximum TTL.
AUTH_SERVER_OPENBAO_TOKEN empty Docker Compose only. The OpenBao token the auth server uses for the cache. Leave empty unless the cache is on. Compose has no namespace scoping, so this token can read every stored egress credential.

mcpgw now reads the existing EGRESS_HTTP_POOL_MAX_CONNECTIONS, EGRESS_HTTP_POOL_MAX_KEEPALIVE, EGRESS_HTTP_POOL_KEEPALIVE_EXPIRY_SECONDS and EGRESS_HTTP_POOL_CONNECT_RETRIES. Set them to the same values on the registry, the auth server and mcpgw.

New Helm values:

Value Default Description
registry.egressAuth.oboCacheEnabled, auth-server.egressAuth.oboCacheEnabled "false" Turn the OBO cache on. Set both.
*.egressAuth.oboCacheMaxTtlSeconds, *.egressAuth.oboCacheExpirySkewSeconds "300", "30" Cache reuse window, in both charts.
auth-server.egressAuth.credentialEncryptionKey empty Encrypts cached OBO tokens. Set it to the registry's egressAuth.credentialEncryptionKey.
auth-server.egressAuth.secretStoreBackend, .secretsManager.*, .openbao.* openbao, OpenBao role auth-server-obo-cache Secret-store settings for the cache. They must match the registry chart. The stack's openbao-init job creates the scoped role.
mcpgw.app.httpPoolMaxConnections, .httpPoolMaxKeepalive, .httpPoolKeepaliveExpirySeconds, .httpPoolConnectRetries "100", "20", "30", "1" mcpgw's pool settings. Keep them in step with the registry and auth-server charts.

New Terraform variables: egress_obo_cache_enabled (false), egress_obo_cache_max_ttl_seconds (300) and egress_obo_cache_expiry_skew_seconds (30).

Upgrade Instructions

Docker Compose

cd mcp-gateway-registry
git pull origin main
git checkout 1.36.0

# Remove NGINX_DISABLE_API_AUTH_REQUEST=true from extra_env/registry.env
# (or .env on Podman) unless you want cookie-only /api/.
# Review new env vars in .env.example, then rebuild and restart:
./build_and_run.sh

Kubernetes / Helm (EKS)

cd mcp-gateway-registry
git pull origin main
git checkout 1.36.0

# REQUIRED: the auth-server, mcpgw, registry and stack charts changed
cd charts/mcp-gateway-registry-stack
helm dependency build
helm dependency update

# Move any newly reserved names out of extraEnv (see Breaking Changes),
# then upgrade:
helm upgrade mcp-gateway . -f your-values.yaml

Terraform / ECS

cd mcp-gateway-registry
git pull origin main
git checkout 1.36.0

# Three new optional variables. The first apply redeploys the registry,
# auth-server and mcpgw services.
cd terraform/aws-ecs
terraform plan
terraform apply

Major Features

mcpgw reuses its connections

mcpgw opened and closed a new HTTP client for every registry API call and every Keycloak M2M token request, so each tool call paid for a new TCP connection. It now keeps one pooled client for the life of the process, shared by all seven registry calls, the client_credentials token request and the JWKS fetch.

  • mcpgw cannot import the registry's URL guard, so it has its own copy. It resolves, checks and pins every request to an address before it takes a pooled connection. It allows only the hosts in REGISTRY_BASE_URL and KEYCLOAK_INTERNAL_URL, and refuses cloud metadata, link-local, multicast and reserved addresses. Tests keep the copy in step with url_guard.
  • When a name resolves to several addresses, mcpgw tries the one that last connected first. Before, every request tried an unreachable first answer, such as ::1 for localhost with an IPv4-only listener, and dropped the warm connection.
  • mcpgw runs one token refresh at a time, so ten concurrent calls on an empty cache make one token request.
  • The same change fixes the registry and auth-server pools, which had ignored their configured keep-alive (see Other upgrade notes).

See docs/egress-http-client-pooling.md.

PR #1796, PR #1982

Agents can call tools they find with search_registry

An agent that found a server with search_registry used to be told only to add it to its MCP client, which usually means restarting the session. Agents now have two more ways to call a tool they found, and mcpgw still has no tool that calls other tools on the agent's behalf.

  • Coding agents that can run shell commands get curl steps in mcpgw's instructions and in the search_registry description. The token comes from REGISTRY_TOKEN, and the agent is told to ask the user to export it rather than look for one in files.
  • Applications built on LangGraph, Strands or the Claude Agent SDK can use agents/discovered_tool_client.py, which searches the registry and calls the tool through the gateway while the application keeps the token. It sends the token only to the registry's own origin and refuses an endpoint_url on any other scheme, host or port, since a registrant controls that value.
  • Both paths call the gateway as the user, so the gateway checks the user's own grants on every call.

See docs/ai-registry-tools.md.

PR #1976

Opt-in cache for exchanged OBO tokens

By default the auth server runs a fresh on-behalf-of exchange against the IdP for every request to an obo_exchange server. With EGRESS_OBO_CACHE_ENABLED=true, it stores each exchanged token in the per-user SecretStore and reuses it for the same user and audience until it nears expiry or reaches EGRESS_OBO_CACHE_MAX_TTL_SECONDS. It works with Entra and Keycloak.

  • Each entry belongs to one user ID, IdP, audience and set of scopes. Entries sit in a reserved namespace that the connections API and the credential vend cannot read.
  • The auth server uses the cache only when /validate verified the request's bearer token with the IdP. Requests with a session cookie, a gateway-minted token or no stable user ID run the stateless exchange.
  • Concurrent requests across replicas wait for one exchange instead of each running their own, and any cache error falls back to the stateless exchange.
  • The auth server gets only the access it needs: a scoped IAM policy on ECS, and a scoped OpenBao role and policy on Helm, limited to the cache namespace.
  • A cache hit skips the IdP's re-check of conditional access and revocation for the length of the reuse window. Leave the cache off where that matters.

See docs/egress-obo-cache.md.

PR #1798


What's New

Security

  • Keycloak OBO refuses an exchanged token issued for a different user, and checks the realm JWKS URL before it sends the gateway's client secret (#1957)
  • A new test shows that a JWKS URL pointing at a private address or a cloud metadata address stops the exchange before any token request (#1983)
  • mcpgw's blocked-egress errors no longer include internal hostnames or addresses (#1982)
  • The discovered-tool helper sends the registry token only to the registry's own origin (#1976)

Egress and OBO

  • Opt-in cache for exchanged OBO tokens on every deployment surface (#1798)
  • Keycloak-specific OBO error mappings apply only on Keycloak, and Entra keeps its own (#1957)
  • The design doc records that Keycloak 25 needs admin-fine-grained-authz for token exchange (#1983)

mcpgw

  • One pooled client for registry and token traffic, with its own egress guard (#1796)
  • The instructions and search_registry description explain how to call a tool found by search (#1976)
  • get_skill_content returns content_type for text resources (#1973)

Skills

  • Semantic search returns each skill's real health_status, last_checked_time and skill_md_raw_url (#1966)
  • Text assets come back as text, with size_bytes on every resource (#1963)

Deployment

  • Podman defaults NGINX_DISABLE_API_AUTH_REQUEST to false (#1985)
  • The pool settings reach mcpgw on Docker Compose, Helm and Terraform (#1796)
  • The telemetry collector's DocumentDB final snapshot name stays the same across plans (#1954)

Documentation

  • The parameter reference lists the Helm secret-generation values added in 1.35.0 (#1969)
  • The writing skill and scripts/prose-scan.py catch more phrasing that reads badly aloud (#1975)

Bug Fixes

  • Setting NGINX_DISABLE_API_AUTH_REQUEST=true changes the nginx config again (#1964)
  • IDE sign-in to Keycloak obo_exchange servers no longer fails with invalid_scope (#1972)
  • Skills in semantic search no longer report health_status: unknown (#1966)
  • Inline (AgentCore) skills are no longer marked unhealthy when the registry's own route answers a HEAD request with 405 (#1966)
  • Text skill assets are no longer returned only as base64 (#1963)
  • The registry and auth-server egress pools use the configured keep-alive expiry instead of 5 seconds (#1796)
  • The Helm charts keep a pool setting of 0 instead of replacing it with the default (#1796)
  • terraform plan on the telemetry collector no longer reports a change to the DocumentDB final snapshot name every run (#1954)

Closed Issues

Issue Title Closed By
#1971 fix(oauth): per-server PRM advertises Entra-only user_impersonation scope on Keycloak, so IDE login to obo_exchange servers fails with invalid_scope PR #1972
#1962 NGINX_DISABLE_API_AUTH_REQUEST has no effect: its /api/ block no longer matches the nginx templates PR #1964
#1952 Skill health_status is not returned by semantic search / search_registry (always "unknown"), so clients can't skip unhealthy skills PR #1966
#1947 terraform/telemetry-collector: timestamp() leaves final_snapshot_identifier unknown during plans PR #1954
#1785 Reuse long-lived HTTPX client for mcpgw to Registry calls PR #1796
#1666 Egress OBO: optional caching of exchanged tokens in the configured SecretStore PR #1798
#1662 Keycloak startupProbe disabled by default causes CrashLoopBackOff on fresh installs (bug) manual

Pull Requests Included

PR Title
#1985 fix(podman): default NGINX_DISABLE_API_AUTH_REQUEST to false
#1983 fix(egress): follow-ups to #1957 (subject-token comments, JWKS IP test, Keycloak 25 prerequisite)
#1982 fix(mcpgw): follow-ups to #1796 (refresh-lock test, generic egress error, doc cleanup)
#1976 feat(mcpgw): let agents call tools they find with search_registry, without an invoke tool
#1975 docs(writing): add read-aloud tells and scan for the ones a regex can catch
#1973 fix(skills): return content_type for text resources and log asset shape
#1972 fix(oauth): advertise IdP-valid scopes in per-server PRM on non-Entra providers (#1971)
#1969 docs: add the Helm secret-generation values from #1784 to the parameter reference
#1966 fix(skills): return real health status in semantic search (#1952)
#1964 fix(nginx): make NGINX_DISABLE_API_AUTH_REQUEST drop /api/ auth_request again
#1963 fix(skills): return UTF-8 text assets in content again
#1957 refactor(egress): Keycloak OBO follow-ups from the #1678 review
#1955 chore(deps): bump zod from 3.25.76 to 4.6.6 in /cli in the npm_and_yarn group across 1 directory
#1954 fix(terraform): keep telemetry final snapshot name stable across plans
#1798 feat(egress): optional OBO exchanged-token cache (issue #1666)
#1796 feat(mcpgw): pool the registry and M2M token egress on one long-lived client

Security Dependency Updates

Package Previous Updated Scope
zod 3.25.76 4.6.6 cli/ (#1955)

Contributors

Thank you to all contributors for this release:


Support


Full Changelog: 1.35.0...1.36.0

Don't miss a new mcp-gateway-registry release

NewReleases is sending notifications on new releases.