github agentic-community/mcp-gateway-registry 1.31.0
1.31.0 - Backend Identity, Tool-Level Security, and a Faster Hot Path

one hour ago

Release 1.31.0 - Backend Identity, Tool-Level Security, and a Faster Hot Path

September 2026


Upgrading from 1.30.0

This section covers everything you need to know to upgrade from 1.30.0 to 1.31.0.

Breaking Changes

This release carries more behaviour changes on already-shipped paths than 1.30.0 did. Most come from security fixes that started enforcing a rule the code already described, so a deployment can be relying on the gap without knowing it.

1. basic_header token endpoints no longer receive client_id in the request body. RFC 6749 §2.3.1 forbids more than one client authentication method per request, so sending the Basic header and the body parameter together was out of spec. Entra, Okta and Keycloak all accept the corrected form. This lands on the existing per-user egress path, not only the new discovery one, so any operator who selected custom_token_auth_style: basic_header is affected by a change that otherwise reads as a discovery feature (#1781).

2. The OBO egress target-audience and scope allowlist now runs on the live paths. The rules existed in a ServerInfo model validator that the request paths never construct, so the write path enforced 2 of 5 checks and the vend path enforced none. A stored OBO directive that does not conform now returns 403 at vend time instead of being exchanged. Check egress_oauth.scopes and target_audience on any server using egress_auth_mode: obo_exchange before upgrading (#1760).

3. Only allowlisted headers reach the external registration gate. Anything depending on a header being forwarded to that gate stops receiving it (#1761).

4. Read surfaces now apply the access check their sibling paths already did. Callers see less than before, which is the fix:

  • server.json export and virtual-server tool listing filter per caller (#1762).
  • Single-skill read and the skill catalog enforce the discovery gate (#1759).
  • Tools blocked by a security scan are hidden from every read projection, so a tools/list that used to show them no longer does (#1782).

5. The IdP bootstrap refuses default, weak and unset credentials. A PingFederate deployment must now supply PF_ADMIN_PASS, PINGFEDERATE_CLIENT_SECRET and the new PF_REGISTRY_ADMIN_PASSWORD from the environment, with no fallback. .env.example no longer ships PINGFEDERATE_CLIENT_SECRET=changeme, which was itself the first entry of the denylist and made cp .env.example .env a guaranteed bootstrap failure. Three Keycloak changes also reach existing realms for the first time, because all three provisioners previously treated an existing realm or a 409 as "nothing to do" (#1763):

  • The + wildcard webOrigins entry is gone. It expanded to the origin of every redirect URI, so CORS widened silently whenever a redirect was added.
  • fullScopeAllowed: false on mcp-gateway-web and mcp-gateway-m2m. Tokens no longer carry every role the user holds. Authorization here is group-driven, so nothing loses access.
  • bruteForceProtected is enabled with failureFactor: 5. The seeded admin can now be locked out after five failed logins.

6. Search results are ordered differently, and search_registry returns more of them. relevance_score was hardcoded to 1.0 for keyword-matched tools on two of four code paths, so a caller had nothing to rank on. Scores are graded now, tools scoring 0.0 are dropped rather than returned with a meaningless number, and MatchingToolResult.relevance_score is required. A new similarity_score carries the raw cosine, which is comparable across queries where relevance_score is not. search_registry also stops discarding the virtual servers it had already scored and counted against max_results (#1755).

7. DEDUP_SCORE_THRESHOLD is now optional, with a per-model default. Cosine scales are not comparable across embedding models, so a single number was wrong for every deployment that changed models. Leave it unset and the registry resolves it from EMBEDDINGS_MODEL_NAME: 0.45 for all-MiniLM-L6-v2, 0.85 for text-embedding-ada-002, and a deliberately conservative 0.6 for a model with no calibration. The previous flat default was 0.7.

Two traps here. The three DEDUP_* variables were absent from all three compose files, so on Docker a value in .env never reached the container and the registry ran on code defaults. They are wired now, which means a stale DEDUP_SCORE_THRESHOLD in your .env starts taking effect on this upgrade. And expect far fewer advisory matches: the check used to return suggestions on nearly every registration regardless of what was typed (#1721, #1744).

8. The MongoDB init script runs from the image and no longer destroys data. This carries its own mongodb-configure image tag bump, so the Helm dependency step below is not optional (#1757).

9. A blank method label no longer drops rows from the auth metric. X-Auth-Method was written with an or "" fallback, so the middleware read a present-but-empty header, its "unknown" default never fired, and Prometheus treated the blank value as absent. Those requests fell out of sum by (method). They now appear as method="unknown", so a dashboard that looked complete may show a new slice. The header contract is unchanged. The telemetry heartbeat schema_version goes "5" to "6" (#1754).

No environment variables were removed or renamed. ENTRA_LOGIN_BASE_URL is not new: it existed for the auth-server in 1.30.0, and this release wires it through to the registry container and the registry chart.

New Environment Variables

19 additions, all with safe defaults. A deployment that sets none of them behaves as 1.30.0 did, apart from the breaking changes above.

Variable Default Description
CIMD_PUBLISHER_ENABLED false Master switch for publishing the registry's own Client ID Metadata Document at GET /oauth/client-metadata.json. Ships dark.
CIMD_CLIENT_NAME AI Registry Tools client_name in the published document.
CIMD_REDIRECT_URIS (unset) Redirect URIs to publish.
CIMD_SCOPE (unset) Scopes to publish.
CIMD_CONTACTS (unset) Contact addresses to publish.
CIMD_LOGO_URI (unset) Logo URI to publish.
CIMD_CACHE_TTL 3600 Cache-Control: max-age on the published document.
EGRESS_HTTP_POOL_MAX_CONNECTIONS 100 Max total connections per shared egress client pool.
EGRESS_HTTP_POOL_MAX_KEEPALIVE 20 Max idle keepalive connections kept warm.
EGRESS_HTTP_POOL_KEEPALIVE_EXPIRY_SECONDS 30 Seconds an idle keepalive connection survives.
EGRESS_HTTP_POOL_CONNECT_RETRIES 1 Connection-establishment retries, applied only to idempotent hops.
VECTOR_SEARCH_EF_SEARCH 1000 HNSW search-queue size. Larger trades latency for recall.
VECTOR_SEARCH_OVERREQUEST 20 Multiplier on k so post-$search filters have candidates left to keep.
EMBEDDINGS_ENCODE_CONCURRENCY 2 Semaphore bound on concurrent model.encode() calls. Torch already parallelises inside one encode, so an unbounded executor makes every concurrent search slower.
SEARCH_LOG_QUERY_TEXT false Opt-in to logging the raw user search query. Off by default; thirteen log lines used to write it verbatim, one next to the username.
SECURITY_ALLOW_UNSAFE_SERVERS false Opt-in to per-tool blocking. When false, a failed scan disables the whole server as before.
OPENBAO_ROLE_ID (unset) AppRole role ID, now forwarded to the registry container on Compose.
OPENBAO_SECRET_ID (unset) AppRole secret ID (secret), now forwarded to the registry container on Compose.
PF_REGISTRY_ADMIN_PASSWORD (unset, required) PingFederate registry admin password (secret). No fallback; bootstrap fails closed without it.

13 of these are also available as Terraform variables in terraform/aws-ecs/variables.tf.

Upgrade Instructions

Docker Compose

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

# Review new env vars in .env.example and update your .env if needed.
# IMPORTANT: the three DEDUP_* variables now actually reach the container.
# Check .env for a stale DEDUP_SCORE_THRESHOLD before starting, or unset it
# to take the per-model default.
./build_and_run.sh

Kubernetes / Helm (EKS)

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

# REQUIRED: 24 files changed under charts/ in this release, and the
# mongodb-configure image tag moved. The packaged subchart .tgz files are
# gitignored and only repackage when you run these, so a plain
# `helm upgrade` would deploy the OLD subcharts.
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.yaml

Terraform / ECS

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

# 13 new variables are available in terraform/aws-ecs/variables.tf, all with
# safe defaults. Set them in your .tfvars only if you are enabling the CIMD
# publisher, tuning the egress HTTP pool, or opting into per-tool blocking.
cd terraform/aws-ecs
terraform plan
terraform apply

Major Features

Easier onboarding for an MCP server that demands OAuth before it answers

Onboarding a server that returns 401 on initialize meant storing a static bearer token on the record, which then served health checks, tool discovery and security scanning alike, with no way to mark it discovery-only. Those are all headless calls with no user behind them, so the registry had nothing else to present.

with_bearer(server_info) is now the single entry point, resolving at most one credential across three tiers. They are dispatched rather than chained, so "which credential won" stays answerable:

auth_scheme Condition Credential
oauth the server's own backend_oauth config client_credentials, works with the egress feature off
none oauth_discovery.enabled a per-user OAuth 2.1 identity, consented once by an owner or admin and borrowed from the vault
none egress_auth_mode: obo_exchange the gateway's app-only token, for servers whose runtime flow re-audiences a live user's JWT and so has no subject_token for a headless call

Tier 1 is terminal. If a machine grant is configured and its token endpoint has a transient error, the answer is "no credential", never "try a different principal", because falling through would quietly substitute a borrowed human token for a machine one. Tiers 2 and 3 bow out unless auth_scheme is none, so an explicitly stored static token is never shadowed.

Tier 2 is the novel part: a real person consents once and the registry borrows that person's vaulted token for its own background calls. That means a user's runtime credential and the identity the registry borrows share a principal, a provider and a server, so purpose became part of the vault address, and the separation is cryptographic rather than path-based. CredentialCodec._aad binds purpose into the AEAD associated data, so a ciphertext relocated between the egress and discovery addresses fails authentication in both directions. This closes an attack the codec's threat model already contemplated: a write-capable backend attacker copying a user's egress ciphertext to the discovery address so the registry borrows that user's credential. The segment is appended only for non-egress purposes, which keeps every pre-existing envelope byte-identical, so nothing needs migrating and nobody re-consents.

A borrowed identity lists tools and never calls one. That holds structurally rather than by convention: tools/call never enters the registry's Python at all, because nginx proxies it and forwards the caller's own Authorization header. Tests enforce it instead of a comment.

Provider support differs by tier, and this is the part to read before enabling it. Tier 2 borrows a credential from the egress vault, so it works with whatever OAuth provider the backend server uses, consented through the normal egress flow. Tier 3 mints a machine token against the gateway's own IdP, and AUTH_PROVIDER=entra is the only value implemented: every other value, including the shipped default cognito, plus keycloak, okta, auth0 and pingfederate, resolves nothing and the server reports unhealthy. So tier 3 needs Entra plus a manual app registration, and nothing in a default deployment reaches it.

Keycloak is refused outright on tier 3 rather than partially working, which is the more interesting decision. Two things are missing and only one is visible: the token request carries the gateway's client secret so it goes through the HTTPS-only credentialed profile, which refuses the in-cluster plain-HTTP KEYCLOAK_URL every shipped deployment uses. The dangerous one is that Keycloak binds the audience through a server-side mapper rather than a request scope, and neither the charts nor the realm bootstrap create that mapper. An operator who fixed only the HTTPS problem would mint a token audienced to whatever Keycloak defaults to, not target_audience, and send it to a third-party MCP server as a bearer. Nothing downstream re-checks the audience, so that failure would be silent and the token real.

Verified end to end against GitHub's hosted MCP server, a genuine OAuth 2.1 target that 401s on initialize and advertises resource_metadata: converting a server from a static bearer to a borrowed identity discovered 45 tools with no static credential stored.

PR #1781

Documentation: Authentication · OBO token exchange

A flagged tool is blocked on its own, instead of disabling the whole server

A security scan that flagged one tool disabled the entire server, so a single false positive cost every other tool on it. Per-tool blocking makes the unit of enforcement the tool.

Set SECURITY_ALLOW_UNSAFE_SERVERS=true and a failed scan blocks the flagged tools and leaves the server enabled. Leave it false and the previous behaviour stands. The gate is opt-in because silently keeping a scanned-unsafe server enabled is not a safe default.

Three properties are worth knowing:

  • A block precedes admin and wildcard grants. No scope, including an admin one, reaches a blocked tool.
  • Blocks reconcile on rescan, not only at registration. Without this, a tool blocked by a false positive stays blocked forever, because the only thing that could clear it never ran again.
  • Blocked tools are hidden from every read projection, so an agent cannot discover a compromised tool, try to call it, and be refused. Read projections fail open by design and tools/call still fails closed, so a projection bug cannot become an authorization bypass.

An operator-facing annotation shows blocked tools where a human needs to see them, with a per-tool toggle in the UI and a bearer API sibling at POST /api/servers/toggle-tool/{path}.

Known limitation: a server with every tool blocked stays enabled and serves nothing.

PR #1719 · PR #1779 · PR #1782 (closes #1623)

Search: honest scores, and 641ms down to 86ms

Two independent problems on the same surface.

Ranking. relevance_score came back as exactly 1.0 for a dozen or more tools in one result, so an agent picking the best two had nothing to rank on. A graded scorer had existed since May, but the MongoDB $map that built matching_tools stamped a literal 1.0 over it on two of four paths. DocumentDB supports $search: {vectorSearch: ...} and MongoDB CE does not, so DocumentDB hit the flat score on every query while a local Compose stack ran the correct Python scorer and looked fine, which is why this survived four months. The pipeline now selects candidates and Python grades them, so one scorer runs on every path and both backends.

Latency. search_registry spiked to ~1.4s against a typical 130-300ms, from three causes:

Before After
Vector + keyword stages ~628ms 76ms
Total in-app 641ms 86ms

model.encode() was synchronous and CPU-bound inside an async def, so every other request on the worker waited, including health checks and auth. It runs on a bounded thread now: a heartbeat coroutine's worst tick fell from 50.1ms to 11.0ms while ten encodes ran.

Five sequential vector queries became one. They were redundant: $search selects the nearest k documents before any $match runs, so $search (k=30) then $match entity_type=skill does not return the top 30 skills, it returns whichever of the global top 30 happen to be skills. Four pipelines at k=30 produced 30 candidates in total, never 30 each. The keyword query no longer waits for the vector query either: the gap between them collapsed to 0.4-1.7ms from 32-1042ms.

A third defect surfaced while measuring the second: because k is spent before filtering and nothing refilled what the filters discarded, the vector stage was returning 0-2 candidates out of a possible 120 on ECS. VECTOR_SEARCH_OVERREQUEST and VECTOR_SEARCH_EF_SEARCH now give the filters something to keep.

PR #1755 · PR #1769 (closes #1752, #1751)

Pooled HTTP clients on the egress hot path

Every egress call built a fresh httpx client, so each one paid a TCP and TLS handshake. Process-lifetime pooled clients replace them, with four tuning knobs.

The security property this rests on is that pooling cannot bypass the SSRF guard. _pin_request_async rewrites the connect host to the validated IP before pool checkout, so httpx keys the pool on the pinned IP rather than the hostname. Sharing is therefore rebind-safe: a second request to the same hostname cannot reuse a connection opened to a different address. Verified with two probes, four requests sharing one connection for a 61% improvement on the warm path, and the guard still refusing metadata, loopback and RFC-1918 addresses on the shared client.

One caveat recorded in the code: on connection reuse there is no handshake, so nothing re-checks that the certificate covers the second hostname. Cert scope is checked only for the hostname that opens the connection.

PR #1718 (closes #1773)

The registry publishes its own Client ID Metadata Document

GET /oauth/client-metadata.json serves the registry's CIMD, so an authorization server can identify the registry as an OAuth client by URL instead of a pre-registered client ID. HTTPS is enforced on the published client_id and one builder is pinned so the value cannot diverge between callers. Ships disabled behind CIMD_PUBLISHER_ENABLED=false.

PR #1711 (closes #992)

The auth-path traffic mix, per deployment and across the fleet

Every /validate call already recorded which of twelve auth paths it took, and three things stopped that from being usable. A blank label dropped rows from sum by (method). Only the operator of a single deployment could see the mix. And nothing said what a path costs.

All three are fixed. The telemetry heartbeat now carries auth_path_share_24h, auth_path_volume_bucket_24h and auth_path_window_hours, as shares rather than counts: raw per-path counts bound to a stable registry_id would turn an entity-count payload into a traffic meter, which is a new disclosure class. Largest-remainder rounding keeps the values summing to exactly 100, and all three go None together at zero volume. docs/OBSERVABILITY.md gains a cited per-path cost table, the queries, and a flush-health alert.

Two fixes ride along. The window reset became one conditional update_one whose filter is the concurrency control, which also repairs a pre-existing lost-update race on semantic_search_ctr where a find_one then $set could zero another writer's $inc. And mcpgw_registry_auth_path_flush_total makes a silently failing flush distinguishable from no traffic at all.

PR #1754 (closes #1753)


What's New

Security

  • Harden the IdP bootstrap against default, weak and unset credentials, and stop granting the Keycloak web client wildcard CORS origins or a wildcard post-logout allowlist (#1763)
  • Enforce per-caller tool visibility on server.json export and virtual-server tool listing (#1762)
  • Forward only allowlisted headers to the external registration gate (#1761)
  • Enforce the OBO egress target-audience and scope allowlist on the live write and vend paths, where it previously ran on a model validator nothing constructed (#1760)
  • Enforce the skill discovery gate on single-skill read and catalog surfaces (#1759)
  • Close two data exposures on the audit path and make degradation visible. Both predate #1756: audit records were already propagating to the application log stream, and X-Internal-Token was absent from the forwarded-header filters so the plain-MCP path leaked it (#1758)
  • Mask the readable username on the operator audit stream, which holds the same UPN as the already-redacted principal_name on an Entra v1.0 token (#1775)
  • Keep the security-scan credential out of argv, where /proc/<pid>/cmdline is world-readable, by re-attaching the flag inside the child from its environment (#1781)
  • Take the raw user search query out of thirteen log lines, behind an opt-in flag (#1755)
  • Apply per-tool security blocks to every read projection, so a blocked tool cannot be discovered and attempted (#1782)

Authentication and Authorization

  • OAuth 2.1 discovery identity and machine discovery for backend-authenticated MCP servers (#1781)
  • Record an identifiable user for Entra OBO calls in the audit log, instead of the opaque sub (#1756)
  • Narrow the audit identity filter to fields a stream can populate, and harden claim coercion so a blank claim or a signed anonymous cannot name a caller (#1774)
  • Publish a standalone Client ID Metadata Document (#1711)

Search and Discovery

  • Grade keyword-matched tools instead of scoring every one 1.0, and surface the raw cosine as similarity_score (#1755)
  • Offload embedding to a bounded thread and replace the per-type vector loop with one query (#1769)
  • Stop search_registry discarding virtual servers it had already scored against max_results (#1755)
  • Strip catalog boilerplate before embedding the duplicate-check query, refuse a query left with fewer than two distinct tokens, and report similarity_search_available: false when hits carry no similarity at all (#1744)
  • Filter duplicate advisories on cosine similarity rather than display rank, so the top hit of any search no longer clears the threshold by construction (#1721)

Performance

  • Pool shared HTTP clients on the egress hot path (#1718)
  • One conditional update_one for the metrics window reset, repairing a lost-update race on semantic_search_ctr (#1754)

Observability

  • Report the auth-path traffic mix per deployment and across the fleet, with a per-path cost table and a flush-health alert (#1754)
  • Coerce the method label at emission so a blank value stops dropping rows from sum by (method) (#1754)

Infrastructure

  • Run the MongoDB init script from the image, and stop it destroying data (#1757)
  • Stop indexing claim fields the audit API no longer searches, retiring four superseded index names (#1757)
  • Expose alb.ingress.kubernetes.io/ssl-policy and inboundCidrs as configurable chart values (#1720)
  • Forward OPENBAO_ROLE_ID and OPENBAO_SECRET_ID to the registry on Compose, which made OPENBAO_AUTH_METHOD=approle unusable there (#1772)
  • Wire ENTRA_LOGIN_BASE_URL through to the registry container and chart for sovereign-cloud and air-gapped deployments (#1781)

API and CLI

  • Per-tool toggle at POST /api/servers/toggle-tool/{path}, with CLI and client wiring (#1782)
  • GET/PUT /api/servers/{path}/oauth-config and GET/PUT/DELETE /api/servers/{path}/oauth-discovery, all owner-or-admin (#1781)

Documentation

  • Refresh api/openapi.json for 1.31.0, catching up the CIMD path and three audit paths that had gone stale (#1776)
  • Make the openapi.json refresh a checked step rather than a remembered one: a CLAUDE.md procedure with its four traps, plus a pr-review gate that blocks a PR adding a route without refreshing the spec (#1777)
  • State the scope limits, supported identity providers and operational constraints of backend discovery, and document the two-pass UI consent flow (#1781)
  • Document per-tool blocking across the four documents that describe that area (#1782)
  • Add the 54-second intro overview video to the demo video index, and refresh the published deck with the 1.30.0 any-resource slide (#1768)

Bug Fixes

  • Honour purpose in the Secrets Manager read-repair, so a repair cannot rewrite a credential to the wrong address space (#1781)
  • Revoke the borrowed discovery credential when a server is deleted or its designation is cleared, so a stored designation never becomes unremovable (#1781)
  • Stop search_registry tripping the credential-harvesting YARA rule on its own description (#1782)
  • Apply the per-tool block on rescan, not only at registration, so a false positive can be cleared (#1779)
  • Fail closed when a failed scan cannot block, rather than leaving a server enabled (#1779)
  • Add the missing entra_login_base_url entry to CONFIG_GROUPS, the only one of eight entra_* settings absent from the System Config UI (#1781)
  • Validate the vector tuning knobs and leave the HNSW queue headroom (#1769)
  • Stop a blank claim and a signed anonymous from naming a caller in the audit log (#1774)
  • Log the all-boilerplate duplicate-check skip, and repair the agent example configs (#1744)
  • Give keycloak/setup/init-keycloak.sh the 409-to-PUT client upsert the other provisioners already had, so realm changes reach an existing realm instead of reporting success and doing nothing (#1763)
  • Send sslRequired only when creating the Helm realm, so an operator who hardened it to external is not reverted on every helm upgrade (#1763)
  • Unbreak the discovery boundary tests, which walked into auth_server/.venv and failed on any developer machine while passing in CI, where .venv is gitignored (#1781)
  • Fix an unquoted CIMD_CLIENT_NAME in .env.example that broke build_and_run.sh via source .env, and add a CI test for that whole class (#1711)

Closed Issues

Issue Title Closed By
#1773 Reuse long-lived HTTPX AsyncClient instances on gateway hot paths to enable connection pooling PR #1718
#1771 OPENBAO_AUTH_METHOD=approle is unusable in Docker Compose: OPENBAO_ROLE_ID / OPENBAO_SECRET_ID never reach the registry PR #1772
#1767 docs: host the 70-second intro video for the AI Registry presentation deck PR #1768
#1753 feat(observability): report the auth-path traffic mix per deployment and across the fleet PR #1754
#1752 search_registry returns relevance_score: 1.0 for many/all keyword-matched tools instead of a graded score PR #1755
#1751 search_registry latency spikes up to ~1.4s under concurrent load due to blocking embedding calls and sequential queries PR #1769
#1696 Duplicate-check advisory produces false positives on names sharing a common catalog suffix (e.g. "-mcp-server") PR #1744
#1661 Expose alb.ingress.kubernetes.io/ssl-policy as a configurable value PR #1720
#1642 Audit Log displays Entra sub instead of identifiable user for OBO-authenticated MCP calls PR #1756
#1623 Feature request: Per-tool blocking and enable/disable toggles PR #1719
#1576 macos-setup skill Phase 15 cannot register the Cloudflare server: token filename never created manual
#992 [Phase 4] Publish CIMD (Client ID Metadata Document) for AI Registry Tools MCP client PR #1711
#930 Valid group-based admin scopes can still result in empty UI permissions manual
#536 Add auto-generated Configuration Reference endpoint and UI tab manual
#321 Local MCP for Databases - credentials manual
#168 Implement Credentials Manager Container for Automated Token Management manual

Pull Requests Included

PR Title
#1782 fix(security): apply per-tool blocks to every read projection
#1781 feat(auth): OAuth 2.1 discovery identity and machine discovery for backend-authenticated MCP servers
#1780 chore(deps): weekly lockfile update (2026-09-21)
#1779 fix(security): connect the per-tool auto-block to the scan path
#1778 chore(deps): bump the actions group in /.github/workflows with 4 updates
#1777 docs: make the openapi.json refresh a checked step, not a remembered one
#1776 docs(api): refresh openapi.json for 1.31.0
#1775 fix(audit): reduce the readable identity on the operator audit stream too
#1774 fix(audit): narrow the identity filter to fields a stream can populate, and harden claim coercion
#1772 fix(compose): forward OPENBAO_ROLE_ID and OPENBAO_SECRET_ID to the registry
#1769 fix(search): offload embedding and replace the per-type vector loop with one query
#1768 docs: add the 54-second intro overview video to the demo video index
#1763 Harden IdP bootstrap against default credentials
#1762 Enforce per-caller tool visibility on server.json export and virtual-server tool listing
#1761 Forward only allowlisted headers to the external registration gate
#1760 fix: enforce OBO egress target-audience/scope allowlist on live paths
#1759 Enforce the skill discovery gate on single-skill read and catalog surfaces
#1758 fix(audit): close two exposures on the audit path, and make degradation visible
#1757 fix(mongodb): run the init script from the image
#1756 fix(audit): record an identifiable user for Entra OBO calls
#1755 fix(search): grade keyword-matched tools instead of scoring every one 1.0
#1754 feat(observability): report the auth-path traffic mix per deployment and across the fleet
#1750 chore(deps): weekly lockfile update (2026-09-14)
#1749 chore(deps): bump the actions group in /.github/workflows with 3 updates
#1744 fix(dedup): strip catalog boilerplate before embedding, and report degraded similarity
#1743 chore: update image tags to 1.30.0
#1721 fix(dedup): filter duplicate advisories on cosine similarity, not display rank
#1720 fix(charts): ingress ssl policy
#1719 feat(security): per-tool blocking for unsafe tools (#1623)
#1718 feat(egress): pool shared HTTP clients on the egress hot path
#1711 feat(cimd): standalone Client ID Metadata Document publisher

Security Dependency Updates

Package Previous Updated Scope
astral-sh/setup-uv v10.0.1 v10.1.0 CI workflows (#1749, #1778)
codecov/codecov-action v7.0.0 v7.1.0 CI workflows (#1749, #1778)
docker/setup-qemu-action v4.2.0 v4.3.0 CI workflows (#1778)
actions/deploy-pages v5.0.0 v5.0.1 docs publishing workflow (#1778)
Python and frontend lockfiles see PRs see PRs weekly automated refresh (#1750, #1780)

Contributors

Thank you to everyone who landed work in this release:


Support


Full Changelog: 1.30.0...1.31.0

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

NewReleases is sending notifications on new releases.