Skip to content

Release v0.56.0 - #6917

Merged
rdimitrov merged 1 commit into
mainfrom
release/v0.56.0
Oct 22, 2026
Merged

rdimitrov merged 1 commit into
mainfrom
release/v0.56.0

Conversation

@toolhive-release-app

Copy link
Copy Markdown
Contributor

Release v0.56.0

Version Bump

minor release

Files Updated

  • VERSION
  • deploy/charts/operator-crds/Chart.yaml (path: version)
  • deploy/charts/operator-crds/Chart.yaml (path: appVersion)
  • deploy/charts/operator/Chart.yaml (path: version)
  • deploy/charts/operator/Chart.yaml (path: appVersion)
  • deploy/charts/operator/values.yaml (path: operator.image)
  • deploy/charts/operator/values.yaml (path: operator.toolhiveRunnerImage)
  • deploy/charts/operator/values.yaml (path: operator.vmcpImage)
  • Helm chart docs (via helm-docs)

Next Steps

  1. Review this PR
  2. Merge to main
  3. Release automation will handle the rest

Checklist

  • Version bump is correct
  • All CI checks pass

Release-Triggered-By: rdimitrov
@github-actions github-actions Bot added the size/XS Extra small PR: < 100 lines changed label Oct 22, 2026
@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.80%. Comparing base (d90a504) to head (b8f1d32).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6917   +/-   ##
=======================================
  Coverage   79.79%   79.80%           
=======================================
  Files         828      828           
  Lines       84632    84632           
=======================================
+ Hits        67534    67541    +7     
+ Misses      17093    17086    -7     
  Partials        5        5           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rdimitrov
rdimitrov merged commit aed6a97 into main Oct 22, 2026
44 checks passed
@rdimitrov
rdimitrov deleted the release/v0.56.0 branch October 22, 2026 11:47
@github-actions

Copy link
Copy Markdown
Contributor

📝 Generated release notes for v0.56.0

Auto-generated by the release-notes skill. Review and, if good, apply with:

gh release edit v0.56.0 --notes-file <paste-below>.md
Click to expand release notes

🚀 Toolhive v0.56.0 is live!

This release hardens the embedded authorization server: Dynamic Client Registration is now capped, CIMD fetches are rate-limited on every route, and expired upstream sessions get a 401 challenge. It also closes a DNS-rebinding gap in vMCP. On the CLI side, thv llm setup now takes Claude Code's model list from the LLM gateway, and Claude Desktop is configured in the right place on Linux.

⚠️ Breaking Changes

  • DCR clients are capped at 10,000 by default, and unused clients expire after 24h: on Redis or memory storage, once there are 10,000 live DCR clients, /oauth/register returns 503. If you already have more than that, raise max_dcr_clients / maxDCRClients (or set it to 0) before upgrading (migration guide)
  • thv llm setup --client claude-code --bedrock-compat now needs the gateway's model list: tier pins now come from the gateway's /v1/models instead of hardcoded IDs. If discovery fails, or the list has no Bedrock IDs, Claude Code is not configured. Have the gateway serve the Anthropic model list, or pass --models pinning all four tiers (migration guide)
  • vMCP rejects a non-loopback Host on loopback connections for every MCP request (security fix, GHSA-7h6f-8rq9-53cw): a tunnel or reverse proxy that reaches vMCP on 127.0.0.1 and passes the client's Host through must now rewrite it to a loopback name (migration guide)
Migration guide: DCR client cap and 24h unused-client TTL

Who is affected: deployments of the embedded authorization server (CLI RunConfig, MCPExternalAuthConfig spec.embeddedAuthServer, VirtualMCPServer spec.authServerConfig) that rely on open Dynamic Client Registration, especially on Redis storage.

What changes:

  • Default cap: live DCR clients are capped at 10,000 on both Redis and memory storage.
    • At the cap, /oauth/register returns 503 server_error. The Retry-After header gives the seconds until the oldest counted client expires.
    • Operator-configured clients and CIMD clients don't count toward the cap.
  • Startup backfill: existing DCR rows are added to the count at startup and keep their current TTL, which can be up to 30 days. A deployment that already holds more than 10,000 live DCR clients will reject new registrations right after the upgrade until the count drops below the cap.
  • Unused clients: a newly registered client now expires after 24h unless it completes a token exchange. The first successful exchange extends it to 30 days, as before.
  • Memory store: it no longer evicts the least-recently-used client when full. It rejects new registrations instead.
  • Register rate limit: now configurable. The default is still 1/s with a burst of 5, applied per replica.
  • New metrics: toolhive_authserver_dcr_clients (gauge) and toolhive_authserver_dcr_capacity_rejections (counter). A WARN is logged when the cap is reached.

Before

# RunConfig: no cap, unused clients live 30 days, register rate hardcoded
storage:
  type: redis

After

# RunConfig
storage:
  type: redis
  max_dcr_clients: 50000        # default 10000; 0 = unlimited
registration_rate_limit:        # optional; default 1/s, burst 5
  requests_per_second: 2
  burst: 10
# Operator: MCPExternalAuthConfig (VirtualMCPServer: spec.authServerConfig)
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPExternalAuthConfig
metadata:
  name: my-embedded-auth
spec:
  type: embeddedAuthServer
  embeddedAuthServer:
    # ... existing issuer / upstreamProviders unchanged ...
    storage:
      type: redis
      maxDCRClients: 30000      # default 10000 if omitted; 0 = unlimited
    registrationRateLimit:      # optional; default maxTokens 5, refillPeriod 5s
      maxTokens: 3              # burst
      refillPeriod: 18s         # must be > 0s

Migration steps

  1. Count your live DCR clients before upgrading. After upgrading, the toolhive_authserver_dcr_clients gauge and the startup WARN also report it.
  2. CLI / RunConfig users: if you are near or over 10,000, set storage.max_dcr_clients to a value well above that count (0 removes the cap) before rolling out v0.56.0.
  3. Operator users: upgrade in this order:
    1. Upgrade the operator-crds chart to 0.56.0. Older CRDs drop the new fields.
    2. Set storage.maxDCRClients (and optionally registrationRateLimit) on your resources.
    3. Upgrade the operator chart.
  4. Use 0 (unlimited) only as a temporary measure. Prefer a finite cap of two to three times the observed live count.
  5. Some clients must stay valid for more than 24h without completing a token exchange. Register them as operator-configured clients instead of through DCR.

PRs: #6863 — Closes #6861; #6867 — Closes #6864

Migration guide: Claude Code gateway model discovery

Who is affected: users who run thv llm setup for Claude Code with --bedrock-compat (including unattended or MDM provisioning) against a gateway that either doesn't serve an Anthropic /v1/models list or serves one without Bedrock-form model IDs. Settings that are already written are not changed until setup runs again.

What changes:

  • Setup now writes CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, so Claude Code's /model picker follows the gateway's catalogue.
  • With --bedrock-compat, tier pins are taken from the gateway's model list: for each family, the newest Bedrock ID. There are now four tiers: haiku, opus, sonnet and the new fable.
  • When discovery fails, or the catalogue has no Bedrock IDs:
    • Claude Code is not configured. Setup errors if claude-code was named, and skips it with a warning if it was auto-detected.
    • The exception is when --models pins all four tiers.
  • --lazy --bedrock-compat now also requires --models for Claude Code.

Before

# Always succeeded; tiers pinned to Bedrock IDs hardcoded in ToolHive
thv llm setup --client claude-code --bedrock-compat

After

# Gateway serves the Anthropic models list: pins are discovered automatically
thv llm setup --client claude-code --bedrock-compat

# Gateway without a usable model list: pin all four tiers explicitly
thv llm setup --client claude-code --bedrock-compat \
  --models <haiku-id>,<opus-id>,<sonnet-id>,<fable-id>

Migration steps

  1. Preferred: make sure the gateway serves the Anthropic model list with Bedrock-form IDs. Set --anthropic-path-prefix if it lives under a non-default path.
  2. Otherwise, pass --models pinning all four tiers (haiku, opus, sonnet, fable). Pinning only some of them still fails when discovery fails.
  3. Update lazy or MDM provisioning scripts that use --bedrock-compat to include --models.
  4. Note that --models is saved, shared with Pi and Claude Desktop, and does not follow later changes to the gateway's catalogue.

PRs: #6862, #6884

Migration guide: vMCP loopback Host guard

Who is affected: anything that reaches vMCP's MCP endpoint over a loopback connection while sending a Host header that isn't a loopback name. Examples:

  • Tunnels and reverse proxies that pass the client's Host through: cloudflared, Caddy, Traefik, oauth2-proxy, or nginx with proxy_set_header Host $host.
  • Sidecars that forward to 127.0.0.1.
  • Clients that use host.docker.internal, 0.0.0.0, or an /etc/hosts alias.

Clients that connect to localhost or 127.0.0.1 are not affected. Neither is traffic to a pod or LAN IP, or the /health, readiness, /status, /api/backends/health and /metrics routes.

What changes: go-sdk already rejected such requests for Legacy-protocol sessions. vMCP now applies the same check to:

  • Modern (2026-07-28) requests
  • Legacy sessions rehydrated on another replica
  • Legacy DELETE requests

These requests now get a plain-text 403 Forbidden: invalid Host header "<host>", and vMCP logs a WARN: rejected request with a non-loopback Host header on a loopback listener. Without this check, a browser page whose hostname resolves to 127.0.0.1 could drive a local vMCP that uses anonymous incoming auth, which is the quick-mode default.

Before

location / {
  proxy_pass http://127.0.0.1:4483;
  proxy_set_header Host $host;   # client's Host was accepted for Modern requests
}

After

location / {
  proxy_pass http://127.0.0.1:4483;
  proxy_set_header Host localhost:4483;   # rewrite Host to a loopback name
}

Migration steps

  1. Look for the WARN log line above, or for 403 responses on the MCP route, after upgrading.
  2. Configure the proxy or tunnel to rewrite Host to a loopback name:
    • cloudflared: originRequest.httpHostHeader
    • Caddy: header_up Host {upstream_hostport}
    • oauth2-proxy: --pass-host-header=false
    • nginx: set proxy_set_header Host localhost:<port> instead of $host
  3. Point local clients at localhost or 127.0.0.1 instead of aliases such as host.docker.internal.

Advisory: GHSA-7h6f-8rq9-53cw — commit f42472c2

🆕 New Features

  • Operator users can set the DCR client cap (storage.maxDCRClients) and the /oauth/register rate limit (registrationRateLimit) on MCPExternalAuthConfig and VirtualMCPServer embedded auth servers (#6867)
  • The embedded auth server's register rate limit is configurable through registration_rate_limit, and two new metrics report the live DCR client count and rejections at the cap (#6863)
  • Programs that embed the authorization server in Go can list client IDs that skip the consent page, so automated flows redirect straight to the upstream IdP. With no list configured, nothing changes (#6882)
  • Claude Code configured by thv llm setup now discovers the gateway's models itself, adds a fourth "fable" tier, and takes Bedrock tier pins from the gateway's catalogue instead of stale hardcoded IDs (#6862)
  • The embedded auth server can serve on its own optional TLS listener (port 8443) and extract SPIFFE IDs from client certificates. This is groundwork for SPIFFE X.509-SVID client authentication, which is not yet enabled (#6843)
  • vMCP now starts the embedded auth server's TLS listener when it is configured, and rejects a vMCP or diagnostics port that collides with it (#6891)

🐛 Bug Fixes

  • Security: vMCP now applies the loopback Host check to Modern-protocol requests, rehydrated sessions and session DELETEs, closing a DNS-rebinding gap against local vMCP instances (GHSA-7h6f-8rq9-53cw)
  • Security: the embedded auth server now rate-limits CIMD document fetches on every route that resolves a CIMD client ID (/oauth/token, /oauth/authorize, device authorization) and rejects multipart bodies on GET /oauth/authorize. Rate-limited lookups now return 429 instead of invalid_client, and a recently expired cached client is served while fetches are throttled (d90a504d)
  • A session whose stored upstream tokens have all expired now gets HTTP 401 with WWW-Authenticate: Bearer error="invalid_token", so clients sign in again on their own instead of hitting a dead-end 403 (#6846)
  • The Redis upstream token lookup now checks that each stored row belongs to the requested user, matching what the memory backend already did (#6876)
  • Private-address checks (used for CIMD fetches, the token-exchange issuer, and plugin and skill sources) now decode IPv4-compatible (::/96) and SIIT (::ffff:0:0:0/96) addresses, so these forms can no longer be used to reach private IPv4 addresses (#6878)
  • On Linux, Claude Desktop is now detected through ~/.config/Claude, its config is written to ~/.config/Claude-3p/configLibrary/, and ToolHive warns when /etc/claude-desktop/managed-settings.json overrides the local config (#6883)
  • thv llm setup model-discovery errors now name the likely cause and what to check, and a --models value that pins all four tiers lets Claude Code setup continue past a discovery failure (#6884)
  • The external-auth example manifests now use the CRD's camelCase field names and the scopes list, so they pass validation when applied (#6330)

🧹 Misc

  • Added the OSS Scanner Dockerfile and threat model so ToolHive can be enrolled in Anthropic's OSS Scanner vulnerability scanning (#6874)
  • Extended the OSS Scanner threat model to cover the embedded authorization server (#6879)
  • The unit test suite now passes when run as root and without a container runtime or keyring (#6875)
  • Added native Go fuzz targets for the parsers that handle untrusted input (MCP messages, WWW-Authenticate, JWT claims, session bindings, skills, plugins, RunConfig) (#6877)
  • Added regression coverage showing vMCP backend init recovers from JSON-RPC -32601 errors sent with HTTP 404/405/501 statuses (#6873)
  • Added tests for client responses to server-initiated requests posted through the vMCP, Streamable HTTP and transparent proxy handlers (#5885)

👋 Welcome to our newest contributor: @3nesdeniz 🎉

Full commit log

What's Changed

New Contributors

Full Changelog: v0.55.2...v0.56.0

🔗 Full changelog: v0.55.2...v0.56.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release size/XS Extra small PR: < 100 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant