[1.1.1] - 2026-11-10 - Token Exchange Delegation, CEL Tool Validation, A2A v1 Routes, and Security Hardening
Overview
Release 1.1.1 delivers token exchange delegation and Admin UI support, CEL tool validation policies, A2A /v1 task and agent card routes, and upstream progress relay to modern clients. It also hardens ownership, OAuth discovery, credential masking, and token scoping, and it completes the removal of the repository-owned Rust runtime. This release consolidates 72 PRs across:
- Protocol & Transport - Modern clients receive relayed upstream progress, and federated pagination results are deduplicated.
- Security & Auth - Added token exchange delegation, CEL tool validation policies, bearer-token precedence over auth cookies, create-time ownership derivation, issuer-scoped OAuth endpoint validation, credential masking, and removal of the emSign/eMudhra CA roots.
- API & Platform - Added
/v1A2A task, push notification config, and agent card routes, A2A 1.0 agent cards,mcp_server_idandvirtual_serveraliases, andserver_idon tool preview. - Operations & Tooling - Added an Admin UI deprecation notice, dataplane publishing on control-plane changes,
prek-based pre-commit hooks, and removal of the Rust build, CI, and benchmark infrastructure. - Testing - Stabilised sandbox worker startup and the OTEL and A2A test suites, and split
main.pystartup helpers for direct test invocation.
Added
Protocol & Transport
-
Progress for modern requests in JSON mode (#6647, #6648, #7270) - With
JSON_RESPONSE_ENABLED=true, a 2026-07-28 request that carries_meta.progressTokenand acceptstext/event-streammay get atext/event-streamresponse once progress is sent or after 15 s without a response; a request that finishes earlier without progress gets JSON. On that stream, errors arrive as JSON-RPC errors with HTTP 200, and closing the stream cancels the request. Legacy clients see no change. The gateway now relays upstream progress to modern clients onresources/readandprompts/get(#6648),completion/complete, and direct-proxytools/call. The gateway no longer forwards the clientprogressTokenupstream onresources/read,prompts/get, andcompletion/complete, including legacy and/rpccompletion. A retried upstream resource read sends no progress callback, so progress values never restart. -
Server instructions in the handshake (#4693, #7254) - A virtual server now sends
instructionsin its handshake answer for bothinitialize(2025-11-25) andserver/discover(2026-07-28). When a virtual server uses tools from exactly one upstream server, the gateway sends that server'sinstructions, stored onGateway.upstream_instructionsat connect time and at each refresh. An operator can setServer.instructionsthrough the REST API (ServerCreate,ServerUpdate) and the Admin UI.nullselects the single-upstream rule, and an empty string sends noinstructions. A server with tools from several upstream servers and no operator text sends the gateway default text. Two idempotent Alembic migrations add the columns. Merging instructions from several upstream servers remains open under #4693.
API & Platform
- Product-terminology request aliases for
gateway_idand the virtual-serverserverbody key (#6544, #7179) - Tool, resource, and prompt create/update bodies, and the gateway/MCP-server handshake test request, now also acceptmcp_server_idas an alias ofgateway_id("MCP server" is the product term for what the code calls a "gateway"). The tool, resource, and prompt list endpoints andGET /v1/searchacceptmcp_server_idas a query alias ofgateway_id.POST /serversandPOST /v1/virtual-serversalso accept a top-levelvirtual_serverkey as an alias ofserver. Sending two spellings with different values returns 422. Existing field names and body keys keep working, OpenAPI property names are unchanged, and responses keep the existing key names. In OpenAPI, theBody_create_server_*request components are replaced byServerCreateRequest. Output-side aliasing, RBAC permission strings, auditresource_typevalues, and config names remain tracked under #6544. - A2A task and push notification config routes (#6702, #7372) - Clients can now read and manage A2A task data through
/v1/a2a/{agent_id}/tasks.GETlists tasks withpageSize,pageToken, andnextPageTokenpagination,GET .../{task_id}returns one task, andPOST .../{task_id}/cancelcancels it.POST,GET, andDELETEon.../{task_id}/pushNotificationConfigsmanage push notification configs. Reads requirea2a.read, cancel requiresa2a.invoke, and config writes requirea2a.update. Routes apply the same team and visibility scoping as the internal handlers, and return 404 for tasks the caller cannot see. Responses never includeauth_token. The/_internal/a2a/*routes are unchanged. - Public A2A agent card lookup (#6700, #7182) - Added
GET /v1/a2a/{agent_id}/card, gated ona2a.read. The route resolves a standalone A2A agent's card first, then falls back to a virtual server exposing an A2A interface. Previously the card was reachable only from the internal Rust-sidecar route. server_idon tool preview (#7249) -ToolPreviewRequestaccepts an optionalserver_id. The gateway verifies that the caller can access the virtual server, then considers only the tools attached to it. Preview withoutserver_idkeeps the global behavior.- Post-authorization validation of stored OAuth connections (#7314, #7343) -
POST /v1/mcp-servers/test-handshakeacceptsvalidation_mode=post_authorization. The mode checks that the caller's stored OAuth token permits a livetools/listrequest on an enabledauthorization_codegateway. It requiresgateways.readandgateways.update, rejects caller-supplied credential overrides, and reads the stored token without refresh or persistence. The response reports separate token, negotiation, and listing outcomes within a shared ten-second deadline. - Dataplane publishing on control-plane changes (#5578, #5580, #6994, #6979) - Changes to virtual servers, tools, resources, prompts, gateways, users, and teams now notify the dataplane publisher. The publisher wakes on these events and publishes to Redis sooner. It uses a lock so that several instances can run. Published routes exclude gateways owned by inactive users.
- OAuth completion signal to the parent app (#7185) - After the OAuth callback page fetches tools for a gateway, it posts an
oauth_callbackmessage withstatus,gatewayId, andgatewayNameto the opener window and closes itself.
Security & Auth
- Token exchange Admin UI (#7236, #7265) - Platform administrators can create and edit
token-exchangegateways in the Admin UI. Atoken-exchangegateway can also hold an optionaldiscoveryclient-credentials credential. Registration, Fetch Tools, refresh, and health checks use it. Tool calls keep the per-user RFC 8693 exchange. - Token exchange delegation (#7238, #7266) - A
token-exchangegateway can send an RFC 8693actor_tokenwith each exchange. Setoauth_config.actor_token_sourcetoclient_credentialsorclient_assertion. The defaultnonekeeps impersonation, and the request body does not change.actor_token_typeoverrides the default token type. The optionaloauth_config.actorobject names a separate actor client throughclient_id,client_secret,token_url, andtoken_endpoint_auth_method. Delegation fails closed: when the actor token is unavailable, the call fails and no impersonation request is sent. Deploy the release to every worker before you enable delegation, because an older worker ignoresactor_token_sourceand impersonates. - Tool validation policy (#5046, #7444) - A CEL expression,
tool_validation_policy, on a gateway or a tool decides which discovered tools are stored enabled. A tool inherits its gateway's policy, and a gateway without one inherits the globalTOOL_DESCRIPTION_FORBIDDEN_PATTERNSlist. Setting a policy requires the newvalidation_policy.managepermission, held by platform administrators and, for records of shared teams, team administrators, and writes an audit entry. Expressions run in a forked sandbox (CEL_EXECUTION,CEL_TIMEOUT_SECONDS,CEL_WORKERS).- Upgrade note: a discovered tool the global list refuses is now stored disabled, with the reason in
validation_refusal, instead of being skipped, andskipped_toolsno longer lists it.POST /toolsreturns 400 instead of 422 for a refused description.PUT /tools/{id}saves a refused description and disables the tool instead of returning 422. A refused tool is never listed or invocable, as before.
- Upgrade note: a discovered tool the global list refuses is now stored disabled, with the reason in
- Upstream OAuth flows follow the MCP 2026-07-28 authorization (#6639, #7333) - The OAuth callback validates the
issparameter against the authorization server issuer before it redeems the code (RFC 9207). A presentissmust match exactly. An absentissis refused only when the server advertisesisssupport. A gateway without a configured issuer skips the check. On a provider error callback with a mismatchingiss, the gateway replaces the provider error text. Dynamic client registration requests sendapplication_type, set by the newDCR_APPLICATION_TYPEsetting (autoby default:nativefor a loopback redirect URI,webotherwise). When a gateway's OAuth issuer changes, the gateway deactivates registered clients bound to the old issuer and logs a warning if a static client ID carries over unchanged. - Webhook HMAC replay protection (#7329, internal issue 1149, PSIRT ADV0369535) - The
webhook_notificationplugin acceptshmac_include_timestamp: true. The signature then covers{timestamp}.{delivery_id}.{body}, and the plugin sendsX-TimestampandX-Delivery-Idheaders. Each retry is signed again with a fresh timestamp. The default staysfalse, so existing receivers keep verifyingHMAC(body). The default flips totruein a later minor release.
Operations & Tooling
- Admin UI deprecation notice (#7297, #7301) - After login, the Admin UI shows a dismissible banner below the header. The banner announces that Admin UI support ends on 27 January 2027 and links to the new UI repository and its getting-started guide. The banner is on by default. Set
MCPGATEWAY_UI_DEPRECATION_NOTICE_ENABLED=falseand restart the gateway to hide it. The date is informational. The change adds no automatic expiry, redirect, or shutdown. - Multi-architecture builds skip the Admin UI on
s390xandppc64le(#7294, #7295) - The Containerfile and the multiplatform workflow skip the HTMX Admin UI and Node.js frontend build steps forlinux/s390xandlinux/ppc64le. - Faster pre-commit with
prek(#7475) -make pre-commitruns the hooks throughprekon all files. The newmake pre-commit-fastruns them on files changed sincePRE_COMMIT_BASE_REF(defaultorigin/main), and pull request CI uses it.PREK_VERSIONpins theprekrelease. - Web UI SSO client seed (#7050) - Added a seed for the Web UI SSO client.
Changed
- A2A agent cards follow the 1.0 schema (breaking for card consumers) (#7027, #7209) - Cards for agents with a stored protocol version of 1.x now use the A2A 1.0
AgentCardshape, withsupportedInterfaces,securitySchemes, andsecurityRequirements.protocol_versiondefaults to"1.0", so most existing agents change shape. These fields leave 1.x cards:url,protocolVersion,supportsAuthenticatedExtendedCard, andcapabilities.stateTransitionHistory. A caller that sendsA2A-Version: 0.3to the sidecar keeps the legacy shape for a 1.x agent. A caller that sends no version follows the stored version. Agents with a stored version of 0.x keep the legacy shape. Server cards now aggregatestreaming,pushNotifications, and the default input and output modes from enabled associated agents instead of hardcoded values. Server cards skip malformed capability entries. Cards never expose credential values. Agents withauthheadersauth,query_paramauth without a stored parameter name, or an incomplete OAuth configuration omit the security scheme, because 1.0 requires a name or flow URLs that the stored configuration cannot supply. Scheme names arebasicAuth,bearerAuth,apiKeyAuth, andoauth2Auth. OAuth schemes read the storedscopeslist and always emit the scope map. - Token exchange audit events carry
purposeandmode(#7236, #7238) - Eachtoken-exchangeaudit event has apurposekey (invocationordiscovery) and amodekey (impersonationordelegation). The log message changes fromtoken-exchange succeeded user=...totoken-exchange succeeded purpose=... mode=... user=..., and the failure message changes the same way. Update log-text alert rules that match the old prefix. Structured consumers ofextra["token_exchange"]are not affected. - Plugin manager cache TTL removed (#7256, #7428) - Removed the unused
plugin_manager_cache_ttlsetting and theDEFAULT_CACHE_TTLconstant. Plugin managers now live for the worker lifetime and change only through explicit invalidation (invalidate_all,invalidate_team,reload_tenant). Multi-worker deployments rely on Redis pub/sub invalidation. Multi-worker deployments without Redis need a worker restart after binding changes. main.pystartup helpers extracted (#7229, #7230, #7231, #7232, #7233) - Extracted_register_middleware,_include_optional_routers,_register_ui_routes,_wait_for_redis_at_startup, and_enable_plugin_subsystem_if_configuredso that tests can call each helper directly. Behavior is unchanged.
Removed
- Repository-owned Rust runtime and workspace (#5817, #7258, #7354, #7359, #7369) - Remove
crates/mcp_runtime, the root Cargo workspace, the Rust test suites and CI workflow, the Rust-vs-Python plugin benchmark targets, and Rust build infrastructure. The separate external Rust dataplane remains outside this removal. devpiMakefile targets (#7280) - Removed thedevpiMakefile targets and references.
Breaking Changes
- REST tool URL template values are percent-encoded (#7387) - Values now fill
{param}as one encoded path segment or one query value. Before, the gateway inserted the raw value into the URL./reaches the backend as%2F. Tools that relied on a raw/in one parameter must use two parameters.- In a query placeholder,
&and=stay inside the value, and+arrives as a literal+, not a space. - A space in a path placeholder used to fail URL validation and now goes out as
%20. - A value that already holds
%XX(for example42%2F43) used to pass and now returns an error. - A value with a
.or..segment returns an error.
- LLM gateway models authorization (#7388) -
GET /llm/gateway/modelsandGET /v1/llm/gateway/modelsnow require thellm.readRBAC permission. Previously, authentication alone passed this route. It now matchesGET /llmchat/gateway/modelsandGET /v1/models. All built-in roles holdllm.read. Principals withoutllm.read(no roles, custom roles, or a customDEFAULT_USER_ROLE) now receive403. - Credential updates require
auth_type(#6704, #7172) -PUT /a2a/{agent_id}andPUT /gateways/{gateway_id}return 422 when a request setsauth_token,auth_password, orauth_header_valuewithoutauth_type. Before, the A2A route returned 200 and ignored the credential, and the gateway route returned 200 and wiped the stored credential. Empty strings count as unset. The Admin UI always sendsauth_type. Partial-body REST clients must addauth_type. - Gateway test endpoints require
gateways.updateand ownership for stored credentials (#7390) -POST /v1/mcp-servers/testandPOST /admin/gateways/testreturn 403 when the target MCP server has a stored basic, bearer, custom-header, or OAuth credential. An OAuth authorization-code token is the caller's own and is not affected. To pass, the caller needsgateways.updatein the MCP server's team and must pass the ownership check ofPUT /gateways/{gateway_id}. The owner, a team owner of a team MCP server, and a platform admin pass that check. For an MCP server without a team,gateways.updatemust come from a global role.POST /v1/mcp-servers/test-handshakeapplies the same rule when the request adds a path or headers. A bare handshake still needs onlygateways.read. Tests against MCP servers without stored credentials also need onlygateways.read. To test new headers with onlygateways.read, usecredential_mode: candidate_only. - Resource reads on token-exchange gateways fail closed (#7236) -
resources/readon atoken-exchangegateway now raisesResourceErrorwhen the request has no user JWT or the RFC 8693 exchange fails. Before, the read went upstream without anAuthorizationheader. MCP clients andGET /resources/{id}callers now receive an error instead of an unauthenticated upstream response. Unauthenticated callers on deployments that setMCP_REQUIRE_AUTHtofalseand callers whose authorization server rejects the exchange are affected. Send an authenticated user JWT and verify the gateway's token-exchange configuration before upgrading.
Security
X-Upstream-Authorizationno longer overrides gateway-managed credentials (#7341) - The header is dropped, with a warning, for gateways withauth_typeoauth,bearer,basic, orauthheaders, and for REST tools and A2A agents with the same auth types. Previously any authenticated caller could replace the stored per-user upstream credential.- Per-gateway identity propagation on tool and resource calls (#7329, internal issue 1149, PSIRT ADV0369535, CWE-200) - MCP and REST tool calls and resource reads ignored the gateway
identity_propagationoverride and sent user identity headers when the global setting was on. A gateway set toenabled: falsenow receives no identity headers or_meta. Deployments with global propagation on and a gateway override of off no longer disclose user identity to that gateway. - Gateway connectivity test credential gate (#7390) - Gateway connectivity tests send stored MCP server credentials only for callers with
gateways.updatewho pass the MCP server ownership check. - Create ownership derived from the authenticated caller (#7296) -
POST /servers,/resources,/prompts,/tools, their bulk variants, and the Admin UI tool paths ignore the payloadowner_emailandteam_id. The owner is the caller. A nestedteam_idthat differs from the top-levelteam_id, lies outside the token's teams, or lacks the create permission returns 403, and a public-only token cannot createteamorprivaterecords. Admin bulk tool import assigns items to the token team or the caller's personal team.POST /admin/grpcreturns 403 for ateam_idoutside the caller's teams. Update routes check ownership and the target team before name or URI conflicts, so a 409 no longer confirms that a record exists in another team. Resources and prompts that gateway discovery creates keep the gateway's team and owner. Requests inside the caller's own team keep their status codes and bodies. - Discovered OAuth endpoints are validated against the issuer origin (#7268) - The gateway checks
token_endpointandauthorization_endpointfrom an authorization server metadata document before it stores them inoauth_config. Each endpoint must share the origin of the configured issuer and pass the outbound URL policy. ssrf_allow_localhostapplies to OAuth HTTP guards (#7165, #7196) - OAuth endpoints on loopback hosts no longer fail an extra HTTPS-only check whenssrf_allow_localhost=true. External HTTP URLs stay rejected, and localhost HTTP stays disabled by default.jq_guarddenies more built-ins (#7413) - The tool response filter gate now denieshalt,halt_error,builtins,while,until,recurse,recurse_down,foreach, andrepeat.- emSign and eMudhra CA roots revoked (#7300) - The container image removes the emSign Root CA G1/C1 and emSign ECC Root CA G3/C3 anchors from the OS trust store and from the Python
certifibundle. oauthlib4.0.1 (#7471) - Raised theoauthlibfloor to>=4.0.1for GHSA-3jp2-3mrg-2vxc.
Fixed
- OIDC JWKS fetches honor
SKIP_SSL_VERIFY(#4026, #7217) - JWKS retrieval for SSO and external-IdP token verification now uses the shared TLS verification setting, like OIDC discovery, token exchange, and user info. Before, an identity provider with a self-signed certificate failed at JWKS retrieval even withSKIP_SSL_VERIFY=true. With the flag set, the gateway logs a warning on each JWKS fetch, because unverified JWKS keys let a network attacker forge tokens. Use the flag in development only. Per-provider CA bundles remain open under #4026. - External-IdP tokens on
/mcp(#7435, #7450) - Trusted external-IdP bearer tokens now authenticate on/mcp, matching/rpc. Anoauth_enabledvirtual server now also accepts trusted-provider tokens whose issuer is outside itsauthorization_servers. The gateway checks the audience against the providerapi_audience, not the serverresource. No per-server opt-out exists. - REST URL template reuses a placeholder (#7387) - A REST tool URL template that uses the same placeholder twice, such as
/{id}/x/{id}, now fills both occurrences. Before, it failed with "Required URL parameter 'id' not found in arguments". - Actionable error when an upstream MCP server redirects (#7163, #7416) - Health checks, gateway registration, Test Connection, and the handshake test now report
Upstream redirected to <scheme://host/path>; update the gateway URL to the redirect target.for an upstream 301, 302, 303, 307, or 308 response. The message drops the query string, fragment, and userinfo of theLocationheader. Before, the gateway showed a generic error. The gateway still does not follow the redirect, to keep SSRF and credential-forwarding protection. Register the redirect target URL directly, for examplehttps://host/mcp/instead ofhttps://host/mcp. - Observability sessions (#5073) - Reuse one observability session per HTTP trace lifecycle, instead of four to six, while preserving independent span and trace completion. Recover trace completion after span persistence failures and redact credentials from HTTP trace URLs and exception stack traces.
- A2A and gateway credential masking (#6704, #7172) -
A2AAgentReadandGatewayReadmask stored credentials on everya2a.readandgateways.readpath. Regression tests cover basic, bearer, and authheaders auth types. - A2A agent credential rotation (#7172) -
PUT /a2a/{agent_id}now persists a new basic, bearer, or custom-header credential. The update path ignored these values before. - Admin UI A2A edit keeps stored credentials (#7172) - The Admin UI form resubmits the mask for an unchanged credential.
update_agentno longer stores the mask in place of the real credential, for basic, bearer, and multi-header auth. - Masked password with a new username (#7172) - An update that changes
auth_usernameand keeps the maskedauth_passwordnow pairs the new username with the stored password, for A2A agents and gateways. - Gateway description-only update keeps credentials (#7172) -
PUT /gateways/{gateway_id}with only unrelated fields, such asdescription, no longer wipesauth_value. - Instana tracer
contextkeyword (#7208, #7174) - The OpenTelemetry request middleware retries the span start without thecontextkeyword when the tracer rejects it withTypeError, so Instana tracers older than 3.12 no longer abort inbound requests that carry atraceparentheader. - Plugin manager rebuilds (#6532, #7245) - Tools of a team without tool-specific plugin bindings share one cached plugin manager, so external plugins such as OPA initialise once per team instead of once per tool. Tools with their own bindings keep their own manager. The invalidation listener now rebuilds managers after each Redis resubscribe.
- LLM Chat loads MCP tools with the MCP SDK client (#7090, #7332) -
langchain-mcp-adaptershas no release compatible with MCP SDK 2.x and is removed from thellmchatextra; LLM Chat lists and calls tools throughmcp.client.Client. - Bearer token precedence over auth cookies (#7389) - When a request carries both a Bearer token in the configured auth header (
AUTH_HEADER_NAME, defaultAuthorization) and ajwt_tokenoraccess_tokencookie, every auth layer (token scoping,require_auth,require_admin_auth,/admin/*, LLM chatconnect) now authenticates the Bearer principal, and token scopes apply to that credential. Cookie-only andAuthorization: Basicrequests keep cookie authentication. LLM chatconnectalso accepts theaccess_tokencookie. - Duplicate items in federated pagination (#6936, #7253) - An upstream MCP server that repeats an item across pagination responses no longer blocks gateway registration. The gateway deduplicates by tool and prompt
name, resourceuri, and templateuri_template, and keeps the first object and the encounter order. - Admin UI edit forms return 422 for missing fields (#6937, #6980) - The Admin UI resource, prompt, gateway, and A2A edit forms return HTTP 422 when a required field is missing. A missing or blank prompt template is rejected. Partial updates of federated resources keep working.
- Sandbox workers start before the pool serves requests (#7361) -
SandboxPool.start()now warms every configured worker. Before, it warmed one, and a lazily spawned worker could exceed the one-second call timeout under parallel load.
Documentation
- Rust runtime removal (#5823, #7441) - Update deployment and contributor guidance, architecture diagrams, and the deprecation registry. ADR-058 records verified removals and remaining internal dispatch cleanup.
- Developer role permissions (#6935, #7252) - The role summaries, RBAC guide, multi-tenancy guide, and provisioning tutorials now state that the built-in
developerrole grants resource CRUD and gateway management. Authorization behavior is unchanged. - Repeated pagination cursor contract (#6938, #7251) - The
get_list_paginated()docstring now states that a repeated cursor logs a warning and stops pagination. It does not raiseValueError.
Testing
- A2A request context tests (#7331) - The tests use JWT-shaped bearer tokens.
- OTEL span flush in tests (#7330) - Tests set
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=1to flush every span immediately.
Chores
| PR | Description |
|---|---|
| #7370 | tolerate a dequeued merge-queue ref in SARIF upload |
| #7092 | suppress audited Makefile false positives inline |
| #7384 | suppress false-positive detect-secrets findings in Python files |
| #7477 | prune stale baseline entries and keep keys sorted |
| #7235 | gate the MCP conformance matrix with cf-integration 0.6.1 |
| #7379 | default tests to rstest on Python 3.14 |
| #7495 | update python, npm and container image dependencies |
| #7290 | update secrets baseline, README and SECURITY |
| #7302 | routine Python dependency updates |
| #7342 | resolve Dependabot security vulnerabilities |
| #7356 | audit found secrets in .secrets.baseline
|
| #7358 | update CPEX plugin versions |
| #7429 | remove unused pytype
|
| #7492 | match the renamed pre-commit workflow steps in the CI decision |