Skip to content

v0.30.0

Choose a tag to compare

@toolhive-release-app toolhive-release-app released this 16 Jun 18:55
bc4bedd

🚀 Toolhive v0.30.0 is live!

This release expands the Virtual MCP server with rate limiting, header passthrough, and high-availability building blocks, introduces a new backend-agnostic authorization CRD, and hardens every MCP proxy against request-body and slow-client denial-of-service vectors — alongside the continuing vMCP core-extraction refactor.

⚠️ Breaking Changes

  • AuthorizerFactory Go interface gains a required ConfigKey() method — out-of-tree Go code that implements its own authorizer backend must add the method to compile. Kubernetes, CRD, and CLI users are not affected. (migration guide)
Migration guide: AuthorizerFactory.ConfigKey()

Who is affected: Only downstream Go developers who import ToolHive as a module and implement the pkg/authz/authorizers.AuthorizerFactory interface themselves (a private fork or closed-source authorizer backend). All in-tree backends (Cedar → "cedar", HTTP PDP → "pdp") and test mocks are migrated in this release. The JSON config envelope ({"version","type","<configKey>": ...}) is unchanged, so Kubernetes/operator, CRD, and thv CLI users are unaffected.

The interface gained a new required method ConfigKey() string, and Register() now panics at startup if a factory returns a reserved key ("", "version", or "type").

Before

type MyFactory struct{}

func (*MyFactory) ValidateConfig(rawConfig json.RawMessage) error { /* ... */ }
func (*MyFactory) CreateAuthorizer(rawConfig json.RawMessage, serverName string) (authorizers.Authorizer, error) { /* ... */ }

func init() {
    authorizers.Register("mybackend", &MyFactory{})
}

After

type MyFactory struct{}

// New: required by AuthorizerFactory in v0.30.0.
// Must be non-empty and not "version" or "type".
func (*MyFactory) ConfigKey() string { return "mybackend" }

func (*MyFactory) ValidateConfig(rawConfig json.RawMessage) error { /* unchanged */ }
func (*MyFactory) CreateAuthorizer(rawConfig json.RawMessage, serverName string) (authorizers.Authorizer, error) { /* unchanged */ }

func init() {
    authorizers.Register("mybackend", &MyFactory{}) // now panics if ConfigKey() is reserved
}

Migration steps

  1. Add a ConfigKey() string method to each out-of-tree AuthorizerFactory implementation, returning the non-empty JSON key under which your backend's config is nested.
  2. Ensure the returned key is not "", "version", or "type", and does not collide with in-tree keys ("cedar", "pdp").
  3. Recompile. No changes to ValidateConfig/CreateAuthorizer and no wire-format/JSON changes are required.

PR: #4777

🆕 New Features

  • Rate limiting can now be configured on a VirtualMCPServer via spec.config.rateLimiting (shared and per-user token buckets, backed by Redis session storage). (#5276)
  • New backend-agnostic MCPAuthzConfig CRD decouples authorization config from workloads and lets any registered authorizer backend (Cedar, HTTP PDP) be referenced via authzConfigRef on MCPServer, MCPRemoteProxy, and VirtualMCPServer. (#4777)
  • VirtualMCPServer gains spec.passthroughHeaders, an allowlist of incoming header names that vMCP forwards unchanged to every backend it calls — enabling backends that authenticate per-user from a header (e.g. x-mcp-api-key) to be used through vMCP. (#5466)
  • MCPRemoteProxy gains spec.replicas and spec.sessionStorage for high availability, mirroring MCPServer and VirtualMCPServer: run multiple replicas behind a load balancer with Redis-backed shared session state. (#5237)
  • The obo external-auth type on MCPExternalAuthConfig now exposes a real configuration schema (tenantId, authority, clientId, clientSecretRef, audience, scopes, subjectTokenProviderName, cacheSkew) for the Microsoft Entra On-Behalf-Of flow; the type remains inert in upstream builds (reports Valid=False / EnterpriseRequired). (#5494)
  • New disableUpstreamTokenInjection flag on the embedded auth server authenticates MCP clients via OAuth while forwarding an unauthenticated request to the backend — useful for proxying public MCP servers with client-side auth. (#4168)
  • thv llm setup, thv llm token, and thv llm proxy start gain a --skip-browser flag that prints the OIDC authorization URL instead of opening a browser, for headless/SSH/CI environments. (#5533)

🐛 Bug Fixes

  • Inbound request bodies on the MCP proxies (streamable, httpsse, transparent) and the vMCP server are now capped at 8MB and rejected with 413 Request Entity Too Large before buffering, closing an unbounded-buffering memory-exhaustion vector (embedded OAuth endpoints are capped at 64KB). Behavior change: clients sending a single message larger than 8MB — e.g. large inline base64 content — will now be rejected; the limit is not yet configurable. (#5492)
  • A 30-second request read timeout is now applied to the proxy servers, the management API server, and thv mcp serve, preventing slow or stalled uploads from holding connections open indefinitely. Behavior change: request reads taking longer than 30s are terminated; long-lived SSE/streaming responses are unaffected, and the timeout is not yet configurable. (#5501)
  • The transparent proxy no longer forwards X-Forwarded-Host to remote upstreams, fixing cross-host redirect loops where upstreams echoed the proxy hostname; X-Forwarded-For and X-Forwarded-Proto are still sent. (#4168)

🧹 Misc

  • Carry RateLimitMiddleware through the vMCP buildServeConfig path so the field is not dropped during the Serve migration (fixes a broken main). (#5500)
  • Apply the compound (kind, name) listMapKey to MCPOIDCConfigStatus and MCPExternalAuthConfigStatus referencingWorkloads so cross-kind name reuse stays distinct under merge-patch (data-safe schema change). (#5508)
  • Migrate the MCPOIDCConfig and MCPExternalAuthConfig controllers to MutateAndPatchStatus/MutateAndPatchSpec to avoid clobbering foreign-owned status conditions. (#5509)
  • Route the vMCP Serve path through the core for capability advertising and call routing, replacing the discovery-into-context middleware. (#5491)
  • Label the audit backend at the call site on the Serve path to eliminate per-request capability re-aggregation. (#5512)
  • Add vMCP core/server config derivation helpers, consolidating transport defaulting to a single edge resolver. (#5513)
  • Add a generic RunConfig seam (AdditionalMiddlewareConfigs) for handler-supplied middleware configs. (#5495)
  • Add Serve-path test coverage for authz/annotation omission and for the AS runner, status reporter, optimizer, and health-monitor lifecycles. (#5482, #5506)
  • Add an integration test for the MCPOIDCConfigoidcConfigRef → runconfig path. (#5536)
  • Add reference docs and a kind-cluster upgrade-guide walkthrough for the storage-version migrator. (#5451)
  • Add a /retest comment workflow (with trusted-user gating and app-owner support) to re-run failed GitHub Actions. (#5510, #5515)
  • Fix the Generate Release Notes workflow to trigger via workflow_run instead of the unsupported release event. (#5490)
  • Pre-pull the time server image in the proxy E2E job to stop intermittent flakes. (#5534)

📦 Dependencies

Module Version
container / docker libraries v0.21.6
aws-sdk-go-v2 monorepo v1.32.25
github.com/pressly/goose/v3 v3.27.1
github.com/lestrrat-go/httprc/v3 v3.0.6
golang.org/x/exp/jsonrpc2 c48552f (digest)
anthropics/claude-code-action d5726de (digest)
Full commit log

What's Changed

  • Trigger release notes via workflow_run not release event by @reyortiz3 in #5490
  • Cover Serve-path authz and annotation-enrichment omission by @tgrunnagle in #5482
  • Add storage-version migration docs + upgrade-guide walkthrough by @ChrisJBurns in #5451
  • Configure rate limits on VirtualMCPServer PR B 1 by @Sanskarzz in #5276
  • Carry RateLimitMiddleware through vMCP buildServeConfig by @ChrisJBurns in #5500
  • Add MCPAuthzConfig CRD for backend-agnostic authorization by @JAORMX in #4777
  • Add request timeouts to MCP proxy servers by @ChrisJBurns in #5501
  • Enforce request body size limits on proxies and vMCP by @ChrisJBurns in #5492
  • Replace discovery-into-context with direct VMCP calls by @tgrunnagle in #5491
  • Define OBOConfig CRD schema for Entra OBO flow by @tgrunnagle in #5494
  • Allow MCPRemoteProxy to work without upstream or client auth by @aron-muon in #4168
  • Use compound listMapKey on sibling config statuses by @ChrisJBurns in #5508
  • Add retest workflow for GitHub Actions by @ChrisJBurns in #5510
  • Cover Serve-path lifecycle for AS runner, status, optimizer, health by @tgrunnagle in #5506
  • Add RunConfig seam for injected middleware configs by @tgrunnagle in #5495
  • Update retest workflow to include app-owner by @ChrisJBurns in #5515
  • Update container and docker libraries to v0.21.6 by @renovate[bot] in #5519
  • Update aws-sdk-go-v2 monorepo to v1.32.25 by @renovate[bot] in #5518
  • Update anthropics/claude-code-action digest to d5726de by @renovate[bot] in #5516
  • Update golang.org/x/exp/jsonrpc2 digest to c48552f by @renovate[bot] in #5517
  • Update module github.com/pressly/goose/v3 to v3.27.1 by @renovate[bot] in #5524
  • Add passthroughHeaders to VirtualMCPServer for header forwarding by @juancarlosm in #5466
  • Add replicas and sessionStorage to MCPRemoteProxy for HA by @aron-muon in #5237
  • feat(llm): add --skip-browser to llm setup/token/proxy start by @jerm-dro in #5533
  • Pre-pull time server image in proxy E2E job by @jerm-dro in #5534
  • Migrate sibling config controllers to MutateAndPatchStatus/Spec by @ChrisJBurns in #5509
  • Add integration test for MCPOIDCConfig → oidcConfigRef → runconfig path by @Sanskarzz in #5536
  • Label audit backend in Serve handlers to avoid re-aggregation by @tgrunnagle in #5512
  • Add vMCP core/server config derivation helpers by @tgrunnagle in #5513
  • Update module github.com/lestrrat-go/httprc/v3 to v3.0.6 by @renovate[bot] in #5523
  • Release v0.30.0 by @toolhive-release-app[bot] in #5539

Full Changelog: v0.29.3...v0.30.0

🔗 Full changelog: v0.29.3...v0.30.0