v0.10.0 — A gateway for agent traffic
🚀 Get started
New to AISIX? Get the gateway running and route your first LLM request in minutes:
- 📖 Documentation — https://docs.api7.ai/ai-gateway/
- ⚡ Gateway quickstart (one container, declarative
resources.yaml) — https://docs.api7.ai/ai-gateway/getting-started/gateway-quickstart
📦 Download
Pull the container image from the GitHub Container Registry:
docker pull ghcr.io/api7/aisix:0.10.0Rolling tags (0.10, latest) and the docker.io/api7/aisix mirror for private/offline deployments are listed on the package page.
AISIX becomes a gateway for agent traffic, not only for LLM calls.
Highlights
- Forward-proxy traffic gets a real resource. The implicit
/passthrough/:providertunnel is replaced by explicitPassthroughRouteresources that match on path prefix, inboundHost, or both — so an IDE or coding agent can be pointed at the gateway and audited without touching the client (#982, #984, #985, #986). - MCP 2026-07-28, with the handshake-free discovery lifecycle and stateless transport, plus a per-server
protocol_versionpin for upstreams that no longer answer the older handshake (#980, #981). - MCP tool access is one rule across three layers — environment policy, team policy, and the key's own block — with allow lists intersecting and deny lists combining (#992, #993).
- Anonymous MCP access and OAuth 2.1 resource-server discovery, so credential-less clients can reach chosen entries as a bound principal, and standard clients can discover where to get a token (#859, #989).
- Consistent hashing and priority tiers for model groups (#997).
- A drain window on shutdown, so connections a load balancer routes inside its own detection window are still served (#995).
🧭 Routing
strategy: consistent_hash pins each session to one target by hashing a request key over a ring: the same key keeps landing on the same target, weights scale each target's share, and when a target fails only its own sessions move to their ring successors. The key comes from a configurable source chain (hash_on: header, cookie, API key, or client IP). Independently, every target takes a priority — targets partition into tiers, higher is preferred, and a lower tier receives traffic only once every target above it has failed or been health-filtered, so a backup pool can sit idle behind an active one (#997).
🧩 MCP
The tool ACL collapses into three layers of identical shape — the environment policy, the caller's team policy, and the key's mcp_access block — whose allow lists intersect and deny lists combine. A layer that is absent imposes no constraint; with no layer at all the grant is empty, so access is always granted explicitly (#992, #993).
/mcp and /mcp/{server} serve the 2026-07-28 revision. A registered server takes an optional protocol_version to pin the revision used for its upstream session — selection is explicit in both directions, with no probing and no silent fallback (#980, #981). An environment can open chosen entries to credential-less clients as a nominated API-key principal, gated by a mandatory source-CIDR allowlist (#989), and can publish OAuth 2.1 protected-resource metadata with a matching WWW-Authenticate challenge (#859).
Guardrails can be scoped to a single registered MCP server, and now scan a tool's structured output as well as its text blocks (#979).
🔌 Passthrough
A passthrough route binds one gateway entry to one upstream target. Host matching runs pre-routing so forward-proxy traffic arriving with its original Host can claim a path the gateway also uses; path matching runs as the router's fallback so a route can never shadow the gateway's own API. Callers authenticate with a gateway key, with the key in a header you name, or anonymously behind a source-CIDR allowlist; the upstream credential is either injected from a provider key or forwarded from the caller verbatim. The request envelope is detected per request and drives guardrail extraction, audit capture and token accounting, so non-LLM traffic records no phantom tokens (#982, #984, #985, #986, #988).
🔭 Observability
Exported traces now form a hierarchy: an HTTP SERVER span, a logical client span covering every retry and failover, and one child per upstream attempt. Ids are minted once per request, so a delivery retry re-sends byte-identical ids. A valid inbound traceparent is honored; a malformed one falls back to a local root rather than failing the request. The caller's traceparent/tracestate are no longer forwarded to providers (#996).
The deployment and fallback counters are now emitted, pre-dispatch failures are kept out of them, and failed requests are attributed to the caller that was resolved (#972, #977, #987).
🚦 Operations
On SIGTERM the gateway reports itself not-ready immediately, then keeps accepting connections for at least shutdown.min_drain_secs (default 30s) before closing its listener, adding Connection: close to HTTP/1.1 responses so pooling clients retire connections as they use them. The window is a minimum, not a deadline (#995).
Release images are built with profile-guided optimization (#975), and the allocator returns freed memory to the OS after load subsides (#970).
🐛 Notable fixes
- Streamed audio transcription was billed as free. The SSE decoder ended an event only on
\n\n, while providers frame transcription streams with\r\n\r\n, so the terminal event carrying the token counts was never decoded. Events now end on any pair of line terminators — this affects every streaming bridge, not only audio (#1003). - Streamed transcription is relayed live instead of being answered from a fully read body, and
/v1/audio/speechstreams as it arrives (#1000). - Strict validation names the offending setting when it rejects configuration a model kind never reads (#971).
⚠️ Breaking changes
- The implicit passthrough tunnel is gone.
/passthrough/:provider/*restno longer resolves by provider name; unclaimed/passthrough/*paths answer410 Gonewitherror.code: endpoint_removed. Recreate the old prefix as an explicit route —path_prefix: /passthrough/<provider>,target_urlset to the old API base,provider_key_idset to the key it borrowed — and client URLs keep working byte for byte. Routes are single-attempt relays (no upstream retries) and resolve no model (no cooldown cross-marking). Access requires an explicitallowed_routesgrant on the caller key (#982). strategy: weightedandrouting.stickyare removed. Weight is now a per-target attribute under every strategy. Migrateweighted→round_robin(weights kept, now smooth weighted round robin) andweighted+sticky→consistent_hash. A stored row still namingweightedfails to load rather than changing meaning silently; sticky cohorts reshuffle once, since consistent hashing builds its ring differently (#997).McpPolicy.mode,McpAccess.modeandApiKey.allowed_toolsare removed.mode: allbecomesallow: ["*"],mode: nonebecomesallow: [], and the key's grant ismcp_access.allow.allowis required on every layer, so a layer that only subtracts must spell its allow side["*"]. Two semantics change: a team policy now narrows the environment layer instead of replacing it, and a key with no block follows the policy layers instead of standing outside them. If you have configured MCP tool policies, re-check them after upgrading — the resulting access is recomputed under these rules (#992).- A guardrail blocking an MCP tool call now answers
result.isError: trueat HTTP 200, instead of JSON-RPCerror.code: -32600. Clients matching on-32600must readresult.isError(#979). - An unsupported
MCP-Protocol-Versionis now refused inside the JSON-RPC envelope (HTTP 400,-32600, supported list inerror.data) instead of a baretext/plain400.2024-11-05is no longer advertised (#980). - Exported OTLP traces changed shape from one flat span per usage event to a SERVER → logical → attempt hierarchy. Span-count alerts will see structural spans appear; the attempt span carries the full attribute set and is identifiable by
aisix.attempt_index(#996). /v1/audio/speechlatency is now time to first byte, since the response streams instead of buffering (#1000).
What's Changed
- perf(server): enable jemalloc background purge thread + post-load RSS decay leg by @membphis in #970
- fix(schema): name the dead knob when strict model validation rejects it by @jarvis9443 in #971
- build: fail-closed PGO for release images by @membphis in #975
- fix(obs): emit the deployment and fallback counters that had no caller by @jarvis9443 in #972
- fix(obs): keep pre-dispatch failures out of the deployment counters by @jarvis9443 in #977
- feat(mcp): scan structured tool output, block as a tool error, scope guardrails per server by @jarvis9443 in #979
- feat(mcp): upgrade rmcp 1.8.0 → 3.1.2 — serve MCP 2026-07-28, pin MRTR/cache/version defaults by @moonming in #980
- feat(mcp): per-server protocol_version for modern-only (2026-07-28) upstreams + conformance CI gate by @moonming in #981
- feat(passthrough): explicit PassthroughRoute resources replace the implicit provider tunnel by @jarvis9443 in #982
- fix(passthrough): OTLP route/identity attribution, route on rejected events, header_key 401 names its header by @jarvis9443 in #983
- fix(passthrough): forward-proxy routes may claim reserved prefixes and mirror the whole path by @jarvis9443 in #984
- feat(passthrough): openai_responses protocol hint for agent traffic by @jarvis9443 in #985
- feat(passthrough): per-request envelope detection, drop protocol/streaming config by @jarvis9443 in #986
- fix(obs): attribute failed requests and label the usage-event/cancel counters by @jarvis9443 in #987
- feat(mcp): OAuth 2.1 resource-server discovery surface for /mcp by @membphis in #859
- feat(mcp): anonymous access to named /mcp entries by @jarvis9443 in #989
- fix(passthrough): report every usage dimension a detected envelope carries by @jarvis9443 in #988
- docs(readme): MCP gateway protocol-generation support in the feature list by @moonming in #990
- refactor(mcp): resolve tool access as three intersecting layers by @jarvis9443 in #992
- fix(mcp): keep loading api_key rows projected before the layered ACL by @jarvis9443 in #993
- feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin by @jarvis9443 in #997
- feat(server): keep serving through a drain window before closing the listener by @jarvis9443 in #995
- fix(mcp): consume the control plane's legacy mcp_access mode tombstone by @jarvis9443 in #1002
- fix(audio): relay a streamed transcription live, and quiet the metadata-probe WARNs by @jarvis9443 in #1000
- fix(sse): frame events on any line-terminator pair, not just \n\n by @jarvis9443 in #1003
- feat(guardrails): local CPU embedding-model guardrail MVP by @membphis in #999
- feat(obs): OTLP trace foundation — stable IDs, W3C traceparent, span hierarchy by @moonming in #996
Full Changelog: v0.9.0...v0.10.0