Release 1.33.0 - Virtual-Server Credential Brokering and Root-Endpoint Routing
October 2026
Upgrading from 1.32.1
This section covers everything you need to know to upgrade from 1.32.1 to 1.33.0.
Breaking Changes
Every connected egress account must be reconnected once.
Egress credential binding is now tighter. A vaulted credential used to be bound to a destination origin (https://api.example.com); it is now bound to the exact outbound URL (https://api.example.com/mcp), and the set of approved destinations is captured when consent begins rather than re-read when the provider redirects back. The second part closes a window in which a server version added while the user was away at the provider could inherit an approval the user never saw.
The consequence is a one-time reconnect. Credentials written before this release carry the origin-only form, which no longer matches the exact URL, so the vend treats them as a miss and falls back to its normal re-consent path. There is no migration step, and nothing is lost:
- An
oauth_userserver returns the usual connect prompt. The user approves once and the credential is rebound. - A
patserver returns the "submit a PAT" instruction. The user resubmits it once on the Connected Accounts page. - A designated discovery identity must also reconnect. Until it does, its server reports unhealthy discovery, which is expected rather than an outage.
This affects any deployment with EGRESS_AUTH_ENABLED=true, not only those using virtual servers. Operators should expect connect prompts across the fleet on the first calls after the upgrade, and should tell users to expect one.
Further detail, including how to tell this apart from a real binding failure, is in the egress credential vault guide.
Other upgrade notes
Internal nginx location names change for some virtual-server backends. Internal names are now derived from an injective encoding of the registered path, so two registrations can no longer collide on one internal location. Any backend whose path contains -, . or _ gets a new internal name. Each live client session re-initializes those backends once, stateful backends receive a fresh Mcp-Session-Id, and the previous session records age out through their TTL index. Registered virtual servers, tool mappings and client-facing URLs are unchanged.
A virtual server now advertises a per-server protected-resource document on Microsoft Entra ID. This is what makes an MCP client able to complete the ingress login for a /virtual/<id> path at all on Entra. Operators running Entra should add each enabled virtual server's resource URL to the gateway application's identifierUris; GET /api/egress/obo-identifier-uris now returns virtual servers alongside registered ones, so it gives the exact set. Keycloak and Cognito deployments are unaffected and stay on the gateway-wide document.
Helm upgrades no longer rotate the Keycloak client secrets. The keycloak-configure Job asked Keycloak for a fresh web and M2M client secret on every run. Pods read Kubernetes Secrets only at startup, so an upgrade left the running auth server holding a credential that no longer existed and browser OAuth code exchange failed with a 401, while a direct password-grant check against the newly written Secret still passed. The Job now reads the existing credentials and synchronizes the Secret without rotating. If you had a habit of re-running a credential fetch script after every upgrade, you can drop it. Deliberate rotation is now a separate, coordinated action rather than a side effect of upgrading.
Permission grants written with a leading slash now take effect. The server access check compared a request path with its slashes stripped against grants stored exactly as written, so a grant recorded as /my-server never matched. Grants are now compared in canonical form. On an upgraded deployment some groups will gain access they were already supposed to have, and any duplicate entries added as a workaround (my-server alongside /my-server) become redundant and can be removed.
A Keycloak realm needs its dynamic-registration policies reapplied, and already-registered clients reviewed. Two limits now apply to a client that registers itself through RFC 7591 Dynamic Client Registration: it cannot request a client scope that confers gateway access, and it cannot define its own protocol mappers. Both are realm policy, so they take effect when the realm configuration is reapplied, not when the images roll. On Helm that happens automatically the next time the keycloak-configure Job runs; on Docker Compose and ECS, run bash keycloak/setup/upgrade-realm-for-dcr.sh, which reports the resulting allowlist so you can check it.
Reapplying the policies does not revoke a scope a client was already granted, because Keycloak records the grant on the client at registration. After the upgrade, review the realm's dynamically registered clients, which are the ones with a UUID-format client ID, and remove any that hold a scope ending in /read or /execute, or named mcp-registry-admin or registry-admins. bash keycloak/setup/cleanup-stale-dcr-clients.sh --dry-run lists the candidates; it skips any client with an active session, so a client currently in use needs to be removed by hand. The MCP client re-registers on its next connection and the new record is subject to the policies.
Discovery on a virtual server now costs more internal requests. Each tools/list authorizes every mapped backing server and checks its per-tool block state, so a virtual server fronting several backends issues several internal requests per discovery call where 1.32.1 issued roughly one. Measured on a seven-backend virtual server in steady state: 12 GET /validate to auth-server and 7 block checks to the registry, 19 in total. These are internal hops rather than inbound traffic, so they do not consume the edge limit_req, but they do add auth-server and registry load per discovery call and count against a backing server's own rate limit. Size accordingly if you run virtual servers with many backends.
An A2A agent in reverse-proxy mode now needs a declared interface that actually matches its backend. Registration and updates are rejected when supportedInterfaces declares no JSONRPC or HTTP+JSON binding at the agent's registered backend URL. An agent already registered before this release with a mismatched interface is not touched retroactively, but its live agent-card fetch through the gateway, and its advertised interfaces on read, were already inaccurate before this release and will now show fewer interfaces, or a 502 on the live card fetch, rather than the earlier misleading result. Re-register the agent with a correct interface, or disable reverse proxy for it, to clear this.
New Environment Variables
No new environment variables in this release.
Upgrade Instructions
Docker Compose
cd mcp-gateway-registry
git pull origin main
git checkout 1.33.0
# No new environment variables in this release, but review .env.example
# against your .env if you are skipping intermediate versions.
./build_and_run.shKubernetes / Helm (EKS)
cd mcp-gateway-registry
git pull origin main
git checkout 1.33.0
# REQUIRED: the release updates the image tags in every chart's values.yaml,
# and the packaged subchart .tgz files are not in git, so a plain
# `helm upgrade` would deploy the previous release's images.
cd charts/mcp-gateway-registry-stack
helm dependency build
helm dependency update
# Update values.yaml if needed, then upgrade:
helm upgrade mcp-gateway . -f your-values.yamlTerraform / ECS
cd mcp-gateway-registry
git pull origin main
git checkout 1.33.0
# The release updates the image defaults in variables.tf; review the plan
# before applying.
cd terraform/aws-ecs
terraform plan
terraform applyMajor Features
Virtual servers can front egress-brokered backing servers
A virtual MCP server aggregates tools from several registered servers behind one endpoint. Until now it could not front a backing server that needs a per-user credential: the router dispatched the backend call straight to the registered upstream with the Authorization header cleared, so the credential vend and inject step was skipped and the backing server received a token-less request. Virtual servers and egress auth were effectively mutually exclusive.
The backend hop now goes through the same vend and inject step a direct call uses, keyed on the resolved backing path so each backend receives its own credential. The caller must hold a grant on both the virtual server and the backing server, checked against the exact rewritten request. The caller's own gateway token is still never forwarded to a registrant-controlled backend; the backend sees only the credential brokered for it.
Two backing servers with different credential modes can now sit behind one virtual server and each gets its own vend, which was the original motivation: splitting a self-hosted Atlassian server into separate Jira and Confluence registrations and aggregating them.
The same change also lets a virtual server front a stateless Streamable HTTP backend. The router previously assumed every backend returns a session ID on initialize; one that returns none produced a lookup that missed on every request. A successful session-less initialize is now recorded as its own state.
A root-endpoint server is reachable at the URL it is advertised under
A server registered with append_mcp_path: false serves MCP at its path itself, and that bare path is what the gateway publishes as the server's protected-resource document and Connect URL. Only the trailing-slash prefix location was rendered, so a POST to the advertised bare path received a 301 redirect to the slash form, which MCP clients refuse to follow on a POST.
The bare path now has its own exact-match location carrying the same authorization, rate limiting and header handling as the prefix form, and /validate accepts the registered path with no trailing slash. A sibling path that merely shares the prefix is still refused.
What's New
Security and Authorization
- The
keycloak-configureJob reuses the existing OAuth client secrets instead of rotating them on every upgrade, no longer prints bootstrap passwords to its own log, and passes Secret data over stdin rather than onkubectlarguments (#1869) - Server access grants are compared in canonical form, so a grant stored with a leading slash is honoured (#1869)
- A2A Pull Card fetches from the registrant's backend rather than the advertised gateway route, and refuses a card that points back at the gateway (#1869)
- Resource-scoped tokens and search visibility bind to canonical asset paths (#1869)
- Egress credentials bind to the exact outbound URL, and the approved destination set is captured when consent begins rather than re-read at the callback (#1825)
- A virtual server's backing grant is checked separately from the virtual grant, on the exact rewritten request, so a tool present in a backend map but absent from the virtual tool list is no longer callable (#1825)
- Internal nginx location names use an injective encoding, so two registered paths can no longer collide on one internal location (#1825)
- A token's
groupsclaim is validated as a JSON array of strings by one shared validator across every identity provider, rather than each provider coercing other shapes. A claim arriving as a bare string was wrapped into a single-element list, which turned the string into a group membership (#1884) - A dynamically registered client can no longer request a client scope that confers gateway access, nor define its own protocol mappers, which decide what a claim contains (#1884)
- The set of scopes withheld from dynamic registration is derived from the list that creates them rather than from a naming convention. Two scopes created only on the Helm surface,
mcp-registry-adminandregistry-admins, are named without the separator the convention relied on and were requestable there (#1885) - The agent tool loop pins its destination rather than building it from a model-supplied parameter (#1863)
Virtual Servers
- A backing server's per-tool block is enforced consistently across the stored catalogue and the runtime, including for a tool pinned to a backend version, which previously failed rather than running (#1869)
- Backend session state distinguishes a stateful session ID from a successful session-less initialize, and is isolated by owner and pinned version (#1825)
- Discovery across backends classifies each one consistently, so one backend's outage or revoked credential no longer blanks its healthy siblings (#1825)
- A virtual server advertises a per-server protected-resource document where the identity provider requires one, and appears in
GET /api/egress/obo-identifier-uris(#1825)
A2A
- Agent cards gain the A2A 1.0
supportedInterfaces,securityRequirements, andsignaturesfields, threaded through register, update (including the pull-card diff), and the batch item processor (#1866) - A2A reverse-proxy registration and updates are rejected when no declared
JSONRPCorHTTP+JSONinterface actually matches the registered backend URL, and only matching interfaces are projected onto the gateway on read (#1887) - The live agent-card fetch through the gateway runs as a content handler against a new internal-only nginx location instead of a body filter, so an unroutable live card now returns a genuine 502 instead of a misleading 200, and the internal fetch strips caller-supplied identity headers before reaching the backend (#1887)
Search
- A search result's
endpoint_urlis built from the server's ownappend_mcp_pathsetting instead of always appending/mcp(#1814)
Identity Provider
GET /api/management/iam/groupsno longer fails the whole page when the identity provider's own group listing errors; it logs a warning and falls back to the groups already tracked in the registry's scope documents (#1883)
Reliability
- An unavailable server version is reported as
503rather than409, which invited a retry the caller could not act on (#1868) - One malformed server record can no longer abort an entire nginx render; the affected server is skipped and logged (#1868)
- The AWS credential check is bounded so it cannot delay registry startup (#1863)
MCP_PROXY_TIMEOUTis wired into the prebuilt Compose file (#1820)
Custom Entities
is_enabledis writable on create and update, so a record can be switched off. A disabled record leaves semantic search, and a disabled proxied record also loses its gateway route (#1858)
Registration
POST /api/servers/registerincludes metadata in the registration gate payload (#1872)
Developer Experience
- The test suite drops its per-session database instead of leaving one behind per run. These had accumulated to the point where MongoDB refused connections with "Too many open files" (#1871)
- The release retag workflow watches the branch prefix the image-tag PR actually uses, so it no longer skips silently (#1857)
- A rejected virtual-server version pin logs which linkage property failed, and the version-document id format has one definition instead of seven (#1879)
Documentation
api/openapi.jsoncarries the custom-entityis_enabledfield, and the custom-entities guide documents the proxy-route effect of disabling a record (#1877)- The agents guide corrects the comment above the server-path guard (#1863)
POST /api/servers/registeris documented as returning a disabled server by default (#1886)
Bug Fixes
- A2A reverse-proxy registration could accept a declared interface that did not match the gateway's backend route, and a live card fetch returned a misleading 200 instead of failing when no interface was actually routable (#1887)
- Search's
endpoint_urlignored a server'sappend_mcp_pathsetting and always appended/mcp(#1814) GET /api/management/iam/groupsreturned a 502 for the whole page when the identity provider's group listing failed, discarding the groups already tracked locally (#1883)- A
groupsclaim arriving as a bare string was coerced into a one-element list, making the string a group membership (#1884) - Two admin client scopes on the Helm surface were requestable by a dynamically registered client (#1885)
- A Helm upgrade rotated the Keycloak client secrets, breaking browser login while health checks stayed green (#1869)
- Permission grants stored with a leading slash matched nothing (#1869)
- A2A Pull Card fetched the agent card from the gateway's own route (#1869)
- A virtual-server tool pinned to a backend version returned an error instead of running (#1869)
- Virtual-server tool calls reached backing servers without their per-user credential (#1825)
- A root-endpoint server answered a
POSTto its advertised URL with a 301 (#1831, #1870) /validaterefused a registered path with no trailing slash (#1870)- An unavailable server version returned
409 Conflict(#1868) - The egress callback error page did not render when the provider rejected the code exchange (#1825)
is_enabledwas silently dropped from custom-entity create and update request bodies (#1858)- The test suite leaked one MongoDB database per session, and one per worker under xdist (#1871)
MCP_PROXY_TIMEOUTwas ignored by the prebuilt Compose file (#1820)- Metadata was dropped from the registration gate payload on server registration (#1872)
Closed Issues
| Issue | Title | Closed By |
|---|---|---|
| #1867 | Follow-up to #1825: release-note the egress credential reconnect, plus three small code items | PR #1877 and this release note |
| #1815 | Feature request / bug: IAM > Groups page returns 502 entirely when the IdP group listing fails, discarding locally-tracked groups that were fetched successfully | PR #1883 |
| #1558 | search endpoint_url ignores append_mcp_path and always appends /mcp | PR #1814 |
| #1221 | Add ATR as a portable rule-pack scanner backend alongside Cisco AI Defense | manual |
| #1862 | agents/agent.py: invoke_mcp_tool builds its destination from a model-supplied parameter and attaches the bearer token | PR #1863 |
| #1859 | Metadata not included in registration gate payload for POST /api/servers/register | PR #1872 |
| #1607 | feat: route virtual-server backend calls through the egress hop so egress-brokered servers can be aggregated | PR #1825 |
Pull Requests Included
| PR | Title |
|---|---|
| #1887 | fix(a2a): advertise only routable agent interfaces through the gateway |
| #1886 | docs(registry): document that POST /api/servers/register comes back disabled |
| #1885 | fix(keycloak): withhold admin scope names from the DCR allowlist |
| #1884 | fix(providers): normalize group claims and fix dcr mapping |
| #1883 | fix(registry): keep scope groups when the IdP group listing fails |
| #1880 | docs: refresh the 1.33.0 notes for the latest merges |
| #1879 | fix(registry): name the rejected version linkage, and centralize the version id |
| #1878 | docs: add the 1.33.0 release notes draft |
| #1877 | docs(api): refresh openapi.json for is_enabled, and document the proxy-route effect |
| #1874 | chore(deps): bump the actions group in /.github/workflows with 2 updates |
| #1872 | fix(registry): include metadata in registration gate payload |
| #1871 | fix(tests): drop the per-session test database instead of leaking one per run |
| #1870 | fix(auth): accept a root-endpoint server's bare registered path at /validate |
| #1869 | fix(registry): repair live auth, virtual routing, and agent card flows |
| #1868 | fix(egress,registry): report unavailable versions as 503 and guard the nginx render |
| #1866 | feat(agent-card): add A2A 1.0 supported_interfaces, security_requirements, signatures |
| #1865 | chore(deps): weekly lockfile update (2026-10-12) |
| #1863 | fix(agents,registry): pin the agent tool destination and report unused AWS credentials |
| #1858 | fix(custom-entities): make is_enabled writable on create and update |
| #1857 | fix(ci,docs): align the retag branch prefix with the PR it watches for |
| #1851 | chore(deps): bump litellm from 1.93.0 to 1.93.2 in the uv group across 1 directory |
| #1831 | fix(nginx): serve a root-endpoint server at its bare path |
| #1825 | fix(virtual): broker per-user credentials for backing MCP servers |
| #1820 | fix(compose): wire MCP_PROXY_TIMEOUT into the prebuilt compose file |
| #1814 | fix(search): build endpoint_url with the server's append_mcp_path |
Security Dependency Updates
| Package | Previous | Updated | Scope |
|---|---|---|---|
| litellm | 1.93.0 | 1.93.2 | uv group, 1 directory |
| GitHub Actions | varies | varies | actions group, 2 updates |
Contributors
Thank you to all contributors for this release:
- Amit Arora (@aarora79)
- omrishiv (@omrishiv)
- Gonçalo Faustino (@go-faustino)
- tankyhsu (@tankyhsu)
- shichengsi (@scs001)
- NatalieRaoEg (@NatalieRaoEg)
- Rızgar Ozan (@RizgarOzan)
Support
Full Changelog: 1.32.1...1.33.0
What's Changed
- chore(deps): bump litellm from 1.93.0 to 1.93.2 in the uv group across 1 directory by @dependabot[bot] in #1851
- fix(agents,registry): pin the agent tool destination and report unused AWS credentials by @aarora79 in #1863
- fix(compose): wire MCP_PROXY_TIMEOUT into the prebuilt compose file by @scs001 in #1820
- fix(virtual): broker per-user credentials for backing MCP servers by @omrishiv in #1825
- fix(egress,registry): report unavailable versions as 503 and guard the nginx render by @aarora79 in #1868
- fix(nginx): serve a root-endpoint server at its bare path by @go-faustino in #1831
- fix(auth): accept a root-endpoint server's bare registered path at /validate by @aarora79 in #1870
- fix(tests): drop the per-session test database instead of leaking one per run by @aarora79 in #1871
- chore(deps): bump the actions group in /.github/workflows with 2 updates by @dependabot[bot] in #1874
- chore(deps): weekly lockfile update (2026-10-12) by @github-actions[bot] in #1865
- fix(custom-entities): make is_enabled writable on create and update by @go-faustino in #1858
- docs(api): refresh openapi.json for is_enabled, and document the proxy-route effect by @aarora79 in #1877
- fix(registry): include metadata in registration gate payload by @tankyhsu in #1872
- docs: add the 1.33.0 release notes draft by @aarora79 in #1878
- fix(registry): repair live auth, virtual routing, and agent card flows by @omrishiv in #1869
- fix(registry): name the rejected version linkage, and centralize the version id by @aarora79 in #1879
- docs: refresh the 1.33.0 notes for the latest merges by @aarora79 in #1880
- fix(providers): normalize group claims and fix dcr mapping by @omrishiv in #1884
- fix(keycloak): withhold admin scope names from the DCR allowlist by @aarora79 in #1885
- feat(agent-card): add A2A 1.0 supported_interfaces, security_requirem… by @NatalieRaoEg in #1866
- fix(search): build endpoint_url with the server's append_mcp_path by @go-faustino in #1814
- docs(registry): document that POST /api/servers/register comes back disabled by @aarora79 in #1886
- fix(a2a): advertise only routable agent interfaces through the gateway by @omrishiv in #1887
- fix(registry): keep scope groups when the IdP group listing fails by @RizgarOzan in #1883
- chore: update image tags to 1.33.0 by @github-actions[bot] in #1888
New Contributors
- @scs001 made their first contribution in #1820
- @tankyhsu made their first contribution in #1872
- @NatalieRaoEg made their first contribution in #1866
- @RizgarOzan made their first contribution in #1883
Full Changelog: 1.32.1...1.33.0