v0.32.0
π Toolhive v0.32.0 is live!
This release advances vMCP cross-application authentication β adding the XAA (Cross-Application Access) outgoing-auth strategy, the OBO strategy seam, and upstream ID-token propagation β alongside meaningful security hardening (Origin/DNS-rebind protection and a credential-passthrough fix) and a batch of operator and proxy robustness fixes.
β οΈ Breaking Changes
- Origin validation & SSE CORS hardening β ToolHive now validates the HTTP
Originheader (MCP 2025-11-25 DNS-rebind protection) and removed the insecureAccess-Control-Allow-Origin: *from the legacy SSE transport. Default local (loopback) usage is unaffected; only browser clients on a non-http://localhostorigin need action via the new--allowed-originsflag (migration guide) (#4908). - vMCP Go embedders:
server.Newsignature changed βpkg/vmcp/server.Newdropped itsdiscovery.Managerparameter (7 β 6 args) and thepkg/vmcp/discoverypackage was removed. CLI, operator, and API behavior are unchanged; only out-of-tree code importing the vMCP library must update its call (migration guide) (#5627).
Migration guide: Origin validation & SSE CORS hardening
Who is affected: Only browser-based MCP clients that make cross-origin requests to ToolHive's SSE transport from an origin that is not http://localhost:<port> / http://127.0.0.1:<port> (for example, a web app served over HTTPS or from a custom hostname). Non-browser clients (IDEs, CLIs, MCP SDK clients) do not send an Origin header and are unaffected. Loopback binds (the default 127.0.0.1) auto-derive a matching local allowlist, so default local usage continues to work. Non-loopback binds without --allowed-origins are not enforced β a warning is logged instead.
Before
# Relied on the implicit Access-Control-Allow-Origin: * wildcard
thv run --transport sse some/mcp-serverAfter
# Explicitly allow the browser origin that needs cross-origin access
thv run --transport sse --allowed-origins https://my-web-app.example.com some/mcp-server
# The same flag is available on thv proxy:
thv proxy --allowed-origins https://my-web-app.example.com ...Migration steps
- Determine whether any client reaches ToolHive's SSE endpoint cross-origin from a browser. If not, no action is needed.
- For each such origin, pass
--allowed-origins=<scheme>://<host>:<port>onthv run/thv proxy. The flag is repeatable for multiple origins; matching is exact on scheme + host + port. - Consider migrating browser clients off the legacy SSE transport to the streamable-HTTP transport.
PR: #4908
Migration guide: vMCP server.New signature change
Who is affected: Only out-of-tree Go code that imports github.com/stacklok/toolhive/pkg/vmcp/server and calls server.New directly (e.g. vMCP library embedders). All in-tree callers were updated in the same PR. There is no impact for CLI, operator, or API consumers.
Before
mgr := discovery.NewManager(agg)
srv, err := server.New(ctx, cfg, router, backendClient, mgr, backendRegistry, workflowDefs)After
// discovery.Manager is removed; capability discovery is now the core's responsibility.
srv, err := server.New(ctx, cfg, router, backendClient, backendRegistry, workflowDefs)Migration steps
- Remove the
discovery.Managerargument from yourserver.Newcall (and delete thediscovery.NewManager(...)construction). - Ensure
Config.Aggregatoris set β the core now rejects a nil aggregator. - If you set
Config.AuthzMiddleware, also setConfig.Authz; the combination withoutAuthznow returnsErrInvalidConfiginstead of silently allowing all requests.
PR: #5627
π New Features
- New XAA (Cross-Application Access) outgoing auth strategy implementing the ID-JAG two-step token exchange (RFC 8693 β RFC 7523) for lazy per-backend cross-application access tokens (#5684).
- Surface upstream ID tokens through the auth middleware, consolidating upstream credential retrieval into a single bulk lookup that carries both access and ID tokens (#5682).
- Added the vMCP OBO (on-behalf-of) strategy seam β a new optional
OBOfield onBackendAuthStrategyplus strategy registration and override hook (#5624). - The vMCP optimizer can now use an OpenAI-compatible embedding client via the optional
embeddingProvider/embeddingModelconfig fields (defaults to TEI, so existing configs are unchanged) (#5633). - Added
insecureAllowHTTPtoEmbeddedAuthServerConfigso VirtualMCPServer deployers can explicitly allow anhttp://issuer for in-cluster hosts, with admission-time validation instead of a proxyrunner crash (#5671). - The operator Helm chart now prints a post-install
NOTES.txtwith verification commands, a minimal MCPServer example, and documentation links (#5656).
π Bug Fixes
- vMCP now returns HTTP 401 +
WWW-Authenticate(RFC 6750) when an upstream provider token is expired and cannot be refreshed, letting clients re-authenticate instead of receiving an opaque error (#5651). - Security: the upstreamswap
customheader strategy no longer forwards the client's ToolHive JWT inAuthorizationto the backend; the upstream IdP token is still delivered in the configured custom header (#5661). MCPRemoteProxynow mounts and validates the referenced OIDC CA bundle ConfigMap, fixing silent TLS failures and surfacing aCABundleRefValidatedstatus condition (#5630).- Concurrent upstream-token refreshes are now deduplicated on a shared refresher, preventing spurious upstream logouts on IdPs with refresh-token rotation + reuse detection (#5635).
- The operator no longer perpetually reconciles MCPServers using Redis session storage with a password ref β
deploymentNeedsUpdatenow mirrors the Redis password env var (#5639). - Container-internal target ports are no longer validated against host availability, so SSE/streamable-HTTP workloads keep their registry-defined target port (#5638).
- Remote proxies now set
X-Forwarded-Prototo the upstream scheme, fixing infinite redirect loops whenMCPRemoteProxyruns behind a TLS-terminating load balancer (#5646). - Upstream-token refresh now fails closed when a rotated refresh token can't be persisted, deleting the stale row instead of stranding a poisoned token (#5636).
- A typed-nil
*Identitystored in the request context is now treated as absent, restoring vMCP fallback identity injection and preventing nil-deref panics (#5653). - VirtualMCPServer now always applies its
PodTemplateSpecstrategic merge patch, so fields likeruntimeClassName,topologySpreadConstraints, andhostNetworkare no longer silently dropped (#5641). RestoreSessionno longer fabricates a partial identity, and session restore now threads the authenticated request context, fixing cross-pod Redis failover with upstream-auth strategies (#5650).DetachedEnvVarValidatorno longer rejects optional secret env vars left blank, fixing spurious "missing required secret" errors when installing registry servers via ToolHive Studio (#5689).
π§Ή Misc
- Landed Phase 2 of the plugin lifecycle epic β new
pkg/plugins/pluginsvcpackages, validation, and storage migration (no user-facing surface yet) (#5676). - Removed the now-unreachable legacy vMCP discovery seam and default router (#5627).
- Removed redundant stale-ref scans from config controller watches, relying on controller-runtime's old+new object enqueue (β648 lines) (#5626).
- Removed redundant annotation-based reconcile triggers in config controllers and added the missing
MCPToolConfigwatch (#5629). - Added unit + E2E test coverage for
--allow-docker-gatewaydeny/allow behavior (#5644). - Added E2E test infrastructure for upstreamInject identity propagation after cross-pod Redis restore, plus several embedded-auth-server in-cluster fixes (#5660).
- Expanded CONTRIBUTING guidelines with
good-first-issueandgood-tenth-issuedescriptions (#5634).
π¦ Dependencies
| Module | Version |
|---|---|
github.com/stacklok/toolhive-catalog |
v0.20260629.0 |
anthropics/claude-code-action |
v1.0.159 (digest a92e7c7) |
actions/create-github-app-token |
v3.2.0 |
alpine (Docker tag) |
v3.24.1 |
| Core workflow actions | (updated) |
| Setup and language actions | (updated) |
π Welcome to our newest contributors: @syf2211 and @claude[bot] π
Full commit log
What's Changed
- Remove dead vMCP discovery seam and default router by @tgrunnagle in #5627
- Remove redundant stale-ref scans from config controller watches by @ChrisJBurns in #5626
- Add good-first-issue and good-tenth-issue contribution guidelines by @ChrisJBurns in #5634
- Dedup upstream-token refresh on a shared refresher by @jhrozek in #5635
- Removed redundant annotation-based reconcile triggers in config ctrls by @Sanskarzz in #5629
- Mount and validate OIDC CA bundle on remote proxy by @ChrisJBurns in #5630
- Add vMCP OBO strategy seam (OBOConfig field + stub + override hook) by @tgrunnagle in #5624
- fix(operator): mirror Redis password env in deploymentNeedsUpdate by @syf2211 in #5639
- fix(runner): do not validate container target port on host by @syf2211 in #5638
- Add tests for --allow-docker-gateway behavior by @ChrisJBurns in #5644
- fix(proxy): set X-Forwarded-Proto to upstream scheme for remote proxies by @claude[bot] in #5646
- Fail closed on unpersisted rotated refresh token by @jhrozek in #5636
- fix(auth): treat typed-nil Identity in context as absent by @syf2211 in #5653
- fix(operator): always apply vMCP PodTemplateSpec strategic merge patch by @syf2211 in #5641
- Return 401+WWW-Authenticate when vMCP upstream token is unrefreshable by @tgrunnagle in #5651
- Add OpenAI-compatible embedding client to vmcp optimizer by @gabrielcosi in #5633
- Update anthropics/claude-code-action digest to a92e7c7 by @renovate[bot] in #5662
- Update core workflow actions by @renovate[bot] in #5663
- Update setup and language actions by @renovate[bot] in #5664
- Update anthropics/claude-code-action action to v1.0.159 by @renovate[bot] in #5665
- feat(operator-chart): add post-install NOTES.txt (#4615) by @syf2211 in #5656
- Expose InsecureAllowHTTP on EmbeddedAuthServerConfig for VirtualMCPServer by @tgrunnagle in #5671
- Strip client Authorization on upstreamswap custom header by @jerm-dro in #5661
- Add plugin core service, validation, and storage (Phase 2, THV-0077) by @JAORMX in #5676
- Update actions/create-github-app-token action to v3.2.0 by @renovate[bot] in #5667
- Update alpine Docker tag to v3.24.1 - autoclosed by @renovate[bot] in #5669
- Add Origin header validation for DNS-rebind protection by @JAORMX in #4908
- Update module github.com/stacklok/toolhive-catalog to v0.20260629.0 by @renovate[bot] in #5683
- Surface upstream ID tokens through auth middleware by @jhrozek in #5682
- Add XAA (Cross-Application Access) outgoing auth strategy by @jhrozek in #5684
- Stop fabricating partial identity in RestoreSession by @tgrunnagle in #5650
- Add E2E test for upstreamInject identity propagation after cross-pod Redis restore by @tgrunnagle in #5660
- Fix DetachedEnvVarValidator rejecting optional secret env vars by @danbarr in #5689
- Release v0.32.0 by @toolhive-release-app[bot] in #5692
New Contributors
Full Changelog: v0.31.0...v0.32.0
π Full changelog: v0.31.0...v0.32.0