github.com/stacklok/toolhive
v0.44.0
#879 most downloaded on Go modules
stacklok/toolhive
What this package is like to depend on
Last release 3 days ago
21 Aug 2026
Ships on a steady schedule
a new release about every 8 days
Rarely documented
notes for 10 of 157 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
447 releases · first in 2025
303 releases in the last 12 months
see the full history below
Release timeline
447 releases · Apr 2025 to Aug 2026Releases
latest 60 of 447-
v0.44.1-0.20260821102605-e25de16cf97221 Aug 2026 pre-releaseNothing published for this version
-
v0.44.1-0.20260819153858-0b8acf26421e19 Aug 2026 pre-releaseNothing published for this version
-
v0.44.018 Aug 2026Release notes
Open source →What's Changed
- Push skills unsigned until keyless signing lands by @samuv in #6334
- Support actor_token in RFC 8693 token exchange by @jhrozek in #6331
- Guard OAuth token and DCR endpoints against SSRF by @jhrozek in #6351
- fix(auth): require audience when introspecting via Google tokeninfo by @SashaMIT in #6236
- Fix stale version comment on codespell action pin by @samuv in #6360
- Harden external may_act delegation by @jhrozek in #6353
- Use an absolute thv path in the Claude Desktop helper shim by @jerm-dro in #6354
- Release v0.44.0 by @toolhive-release-app[bot] in #6365
Full Changelog: v0.43.0...v0.44.0
-
v0.43.1-0.20260814175116-8343851e8a5814 Aug 2026 pre-releaseNothing published for this version
-
v0.43.014 Aug 2026Release notes
Open source →What's Changed
- Guard against shell injection via github.ref in image workflow by @ChrisJBurns in #6248
- Extend private-IP guard to 6to4/Teredo addresses by @ChrisJBurns in #6249
- Parse multi-line Modern SSE events by @kocaemre in #6126
- Add zizmor to CI and stop double-running the security scan by @ChrisJBurns in #6251
- Pin actions to commit SHAs and fix stale version comments by @ChrisJBurns in #6254
- Stop persisting git credentials in CI workflow checkouts by @ChrisJBurns in #6255
- Bind workflow expressions and drop persisted credentials by @ChrisJBurns in #6258
- Derive PR number from the triggering run in the size labeler by @ChrisJBurns in #6259
- Gate the Claude workflow on write access, not contribution history by @ChrisJBurns in #6260
- Stop writing the PR number into the size label artifact by @ChrisJBurns in #6261
- Suppress four zizmor findings that cannot be fixed by @ChrisJBurns in #6262
- Scope the release app token to what releaseo needs by @ChrisJBurns in #6266
- Scope releaser permissions and bind the tag expression by @ChrisJBurns in #6263
- Pass only the secret the test workflow needs by @ChrisJBurns in #6269
- Bind the GHCR credentials and chart version in helm-publish by @ChrisJBurns in #6270
- Scope the remaining release app tokens and bind a trailer by @ChrisJBurns in #6272
- Rate limiting observability (metrics and tracing) PR B by @Sanskarzz in #5800
- Clear the remaining zizmor findings in helm-publish by @ChrisJBurns in #6275
- Make the zizmor check blocking by @ChrisJBurns in #6274
- Clear the last zizmor findings in the release workflows by @ChrisJBurns in #6281
- Drop build fingerprint from proxy /health response by @Nashon-Steffen in #6280
- Let an explicit non-Modern header outrank a reserved key by @amirejaz in #6231
- Deflake the initialize dial-error proxy test by @amirejaz in #6264
- Support confidential clients in dynamic client registration by @jhrozek in #6252
- fix(vmcp): reject Authorization and Cookie in passthroughHeaders, as documented by @SashaMIT in #6235
- fix(auth): bind OAuth callback listener to loopback only by @SashaMIT in #6238
- Display recorded trust state to the user by @samuv in #6137
- Bound client-controlled metric label length by @Nashon-Steffen in #6279
- Sign the macOS thv binary with a Developer ID certificate by @eleftherias in #6156
- Sign pushes by default and remove the lock feature gate by @samuv in #6139
- Ignore unsupported grant types in CIMD documents by @amirejaz in #6297
- fix(authz): make Cedar URI entity IDs collision-free by @SashaMIT in #6239
- Add plugins key to lock file schema by @samuv in #6303
- Update module github.com/stacklok/toolhive-catalog to v0.20260810.0 by @renovate[bot] in #6257
- Add PluginLockService and managed install flag by @samuv in #6311
- Record certificate ref and runner in lock provenance by @samuv in #6312
- Pin CodeQL SARIF action comment to v4.37.6 by @samuv in #6313
- Reset the LLM config when the last tool is torn down by @jerm-dro in #6295
- Use a bare thv command as the LLM token helper by @jerm-dro in #6326
- Bump go.mod to get CVE fixes by @jhrozek in #6327
- Make RFC 8693 delegate clients reachable and usable by @jhrozek in #6320
- Enforce recorded ref and runner on skill verification by @samuv in #6315
- Release v0.43.0 by @toolhive-release-app[bot] in #6333
New Contributors
Full Changelog: v0.42.1...v0.43.0
-
v0.42.2-0.20260812124719-52ad2932e0b412 Aug 2026 pre-releaseNothing published for this version
-
v0.42.110 Aug 2026Release notes
Open source →🚀 Toolhive v0.42.1 is live!
A security-hardening patch release: three authorization gaps are closed (non-JSON POSTs bypassing Cedar, filtered vMCP tools staying callable, and unvalidated OIDC issuer URLs), alongside a deny-by-default visibility model for vMCP tool aggregation and a fail-closed consent model for external OIDC subject tokens.
⚠️ Breaking Changes
- Non-JSON
POSTrequests are now rejected instead of skipping authorization — with Cedar authorization enabled, aPOSTwithoutContent-Type: application/json(including a missing header) returns400rather than being forwarded unauthorized; set the header on all MCP POSTs (migration guide) - vMCP tools hidden from
tools/listare no longer directly callable — a tool excluded viafilter/excludeAll/excludeAllToolsnow returns-32602on the Modern (2026-07-28) path instead of executing; un-filter it or reach it through a composite tool (migration guide) MCPOIDCConfiginline issuer and JWKS URLs are now validated — stored inline configs with a malformed or plain-HTTP URL flip toValid=Falseon their next reconcile and block reconciliation of every workload referencing them; addinsecureAllowHTTP: trueor switch to HTTPS (migration guide)
Who is affected: only deployments that configure Cedar authorization (
--authz-config, orauthzConfigin the CRD). Deployments without an authorization config are entirely unaffected.Previously,
shouldSkipInitialAuthorizationskipped Cedar evaluation for anyPOSTwhoseContent-Typewas notapplication/json— but skipping authorization did not stop the request. The proxy forwarded the body verbatim and MCP backends parse JSON-RPC without checkingContent-Type, so atools/callsmuggled undertext/plainexecuted with no policy evaluation at all. Such requests now fall through to the parsed-request check and are refused.Before
POST /mcp HTTP/1.1 Content-Type: text/plain {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"delete_repo"}}
→ forwarded to the backend and executed, with no Cedar evaluation.
After
POST /mcp HTTP/1.1 Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"delete_repo"}}
→ parsed and evaluated against your Cedar policies. The
text/plainform now returns400 Invalid or malformed MCP request.Migration steps
- Ensure every MCP client, script, and
curlinvocation sendsContent-Type: application/jsonon POST requests. A missingContent-Typeheader is also now rejected. Spec-conformant MCP Streamable HTTP clients already comply. - Media-type matching is now case-insensitive and parameter-aware, so
Application/JSONandapplication/json; charset=utf-8are accepted. Near-miss types that previously prefix-matched —application/json-rpc,application/jsonx— are not. - If a server behind the transparent proxy also serves non-MCP POST endpoints (form or multipart uploads) and you run Cedar authorization, those requests are now refused too; mount them outside the proxy.
- Alerting keyed on audit outcomes may see new
deniedevents, since these refusals are now audited as denials rather than generic failures.
PR: #6234
Migration guide: hidden vMCP tools are no longer directly callableWho is affected: vMCP operators using
aggregation.toolsfilter, per-workloadexcludeAll, or globalexcludeAllTools, whose clients speak the Modern (2026-07-28) revision.Tool filtering was enforced on the Legacy path (which registers one handler per advertised tool) but not on the Modern one, which is stateless and resolved
tools/callstraight against the routing table — and the routing table deliberately holds every backend tool so composite workflow steps can reach them. A Modern client that knew a filtered tool's name could call it successfully.core.CallToolnow resolves against the advertised view, so filtering holds identically on both revisions.Before
aggregation: tools: - workload: github filter: ["get_issue"] # create_issue hidden from tools/list
After
// Modern client, tools/call { "name": "github_create_issue" } // → JSON-RPC error -32602, HTTP 400; the backend is never invoked
To keep a tool reachable while hidden from
tools/list, wrap it in a composite tool:compositeTools: - name: file_issue steps: - id: create type: tool tool: github.create_issue # workflow steps still reach hidden tools
Migration steps
- If you relied on calling a filtered tool directly by name, remove it from
filter/ dropexcludeAllfor that workload so it appears intools/list— advertised now means callable, and only advertised is callable. - If the tool must stay hidden but reachable, define a composite tool whose step targets it and call the composite by its advertised name. Composite workflow steps are unaffected and still reach hidden backend tools.
- If a client called a tool by its
{workloadID}.{toolName}alias, switch to the exact conflict-resolved name shown intools/list(e.g.github_create_issue). The dotted alias remains valid inside composite workflow step definitions — only directtools/callrejects it. tools/callfor an unknown or hidden tool now answers-32602at HTTP 400 (previously-32603at HTTP 200), matching the MCP specification's "Unknown tool" protocol error. Clients should inspect the JSON-RPC body and treat this as a call-level error, not a connection failure.
Who is affected: clusters with
MCPOIDCConfigresources ofspec.type: inlinewhoseissuerorjwksUrlis plain HTTP, malformed, missing a scheme or host, or uses a non-HTTP(S) scheme. In practice this is dev/test clusters pointing at an in-cluster Keycloak or Dex over HTTP; production HTTPS setups are unaffected.kubernetesServiceAccountconfigs are explicitly skipped.Validation runs at reconcile time, not at admission — so it applies to already-stored objects, not just new applies. A failing config gets
Valid=False, and everyMCPServer,MCPRemoteProxy, andVirtualMCPServerreferencing it getsOIDCConfigRefValidated=Falseand stops reconciling. Already-running pods keep serving, so a stalled workload can look healthy while silently ignoring spec changes, image updates, and rollouts.Before
apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPOIDCConfig metadata: name: keycloak-auth spec: type: inline inline: issuer: http://keycloak:8080/realms/toolhive jwksUrl: http://keycloak:8080/realms/toolhive/protocol/openid-connect/certs
After
# Production — switch to HTTPS spec: type: inline inline: issuer: https://keycloak.example.com/realms/toolhive jwksUrl: https://keycloak.example.com/realms/toolhive/protocol/openid-connect/certs # Dev/test only — opt in explicitly; one flag now covers both URLs spec: type: inline inline: issuer: http://keycloak:8080/realms/toolhive jwksUrl: http://keycloak:8080/realms/toolhive/protocol/openid-connect/certs insecureAllowHTTP: true
Migration steps
- Before upgrading, audit your inline configs:
kubectl get mcpoidcconfigs -A -o jsonpath='{range .items[?(@.spec.type=="inline")]}{.metadata.namespace}{"/"}{.metadata.name}{"\t"}{.spec.inline.issuer}{"\t"}{.spec.inline.jwksUrl}{"\n"}{end}' - Flag any entry whose
issuerorjwksUrlishttp://, has no scheme, or is otherwise malformed. An emptyjwksUrlis fine — it falls back to discovery. - For production, change both URLs to
https://. For dev/test only, addinsecureAllowHTTP: trueunderspec.inline. - After upgrading, verify:
kubectl get mcpoidcconfig <name> -o jsonpath='{.status.conditions[?(@.type=="Valid")]}'. The failure message names the offending URL. - If a workload stalls, check
OIDCConfigRefValidatedon the referencingMCPServer/MCPRemoteProxy/VirtualMCPServer.
🔄 Deprecations
pkg/container/images.NewCompositeKeychaindeprecated in favour ofgithub.com/stacklok/toolhive-core/container/images.NewCompositeKeychain— the local function is now a thin wrapper with identical behaviour and will be removed in a future cleanup wave; Go module consumers only, no CLI or CRD surface (#6147)
🆕 New Features
- vMCP operators can set
aggregation.defaultToolVisibility: denyso that only workloads explicitly listed inaggregation.toolshave their tools advertised, closing the fail-open gap where adding a workload to a group silently exposed it (#6163) - Composite tools now support MCP tool annotations (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint), with a conservative fail-closed safety floor derived from the workflow's step tools when none are set explicitly (#6208) - The embedded auth server accepts
trusted_issuers, letting agents exchange subject tokens minted by an external OIDC issuer (Entra, Okta, Keycloak) for ToolHive-scoped delegated tokens under a fail-closed RFC 8693 consent policy (#6149) TOOLHIVE_API_TIMEOUToverrides the CLI's API client timeout forthv skillandthv ai-plugin, for anyone who wants to fail faster than the new 10-minute default (#6224, #6228)
🐛 Bug Fixes
- vMCP can call tools on dual-era stdio backends again — the removed
logging/setLevelRPC is no longer sent to backends that negotiate MCP 2026-07-28, where the rejection was fatal and closed the session while health checks stayed green (#6184) - A single unhealthy vMCP backend no longer inflates
initializelatency for an entire server: new sessions skip backends the health monitor has classified unhealthy or unauthenticated, while degraded backends are still attempted and restored sessions are unchanged (#6162) - Interactive OIDC login through
thv llm proxynow completes when the calling client times out mid-login — the callback listener is rooted in the proxy's lifetime rather than the inbound request's, which is the normal case forthv llm setup --lazy(#6229) - A slow or failed first JWKS fetch no longer permanently disables token validation for the life of the process;
ErrNotReadyandErrResourceAlreadyExistsare treated as registered, so validation self-heals once a background fetch succeeds (#6221) - JWKS registration passes ToolHive's CA-aware HTTP client per resource, preventing a future
jwxbump from silently bypassing custom CA bundles and private-IP policy on every JWKS fetch (#6220) - Skill and plugin operations that pull OCI artifacts no longer fail on slow or large pulls — the client default rose from 30s to 10 minutes and the skills/plugins routers moved off the flat 60s server cap — and a timeout now says the request timed out instead of claiming the server is unreachable (#6224, #6228)
thv skill upgradeno longer requires--allow-ref-changefor a version change within the same repository; the flag now means "permit the artifact to move to a different repository, org, or registry", and same-repository tag moves — including moves to an older tag — proceed unprompted, with digest pinning and the signer-change guard unchanged (#6225)thv skillcommands accept a relative--project-rootsuch as., resolving it against the working directory instead of failing withproject_root must be absolute(#6223)thv ai-plugincommands accept a relative--project-rootthe same way, matchingthv skill(#6226)
🧹 Misc
- The
defaultToolVisibilityCRD reference no longer carries maintainer-internal defaulting rationale, and an unreachable nil-check was removed from the deny-visibility validator (#6233) pkg/container/imageskeychain logic is delegated totoolhive-corev0.0.37, with the local file reduced to a deprecated wrapper (#6147)
📦 Dependencies
Module Version github.com/go-git/go-git/v5v5.19.2 (fixes CVE-2026-71556 — worktree operations may follow symlinks) 📝 Upgrade notes
- Apply the CRDs before the operator.
aggregation.defaultToolVisibilityrequires the v0.42.1 CRDs. Theaggregationsubtree does not preserve unknown fields, so on a cluster running the new operator against old CRDs the field is pruned at admission and aggregation silently falls back toallow— every workload in the group has its tools advertised. Verify withkubectl get virtualmcpserver <name> -o jsonpath='{.spec.config.aggregation.defaultToolVisibility}'. defaultToolVisibilitygates tools only. Resources, resource templates, and prompts from unlisted backends are still advertised.- Composite tools now advertise derived annotations. When
annotationsis not set, a conservative floor is derived from the workflow's step tools; because most backends declare no annotations today, composite tools typically now advertisedestructiveHint: true/openWorldHint: true. These match the MCP specification's defaults for absent annotations, but clients that key off explicit hints may begin prompting for confirmation on composite tools that previously carried none. A contradictory explicit annotation causes the tool to be dropped at advertise time with a warning — this is detected at runtime, not bythv vmcp validateor the operator. - Skipped backends are not re-attached to an existing vMCP session. A backend excluded at session open because it was unhealthy stays absent from that session even after it recovers; reconnect to pick it up.
👋 Welcome to our newest contributors: @lopster568, @SashaMIT 🎉
Full commit logWhat's Changed
- Pass CA-aware HTTP client to JWKS registration by @danbarr in #6220
- Handle non-fatal httprc errors in JWKS registration by @danbarr in #6221
- Resolve relative --project-root before the API call by @samuv in #6223
- Stop severing skill artifact pulls at fixed timeouts by @samuv in #6224
- Block only repository moves, not tag moves, on upgrade by @samuv in #6225
- Resolve relative --project-root for ai-plugin too by @samuv in #6226
- Stop severing plugin artifact pulls at fixed timeouts by @samuv in #6228
- Keep the OIDC callback listener alive when an LLM proxy client disconnects by @jhrozek in #6229
- Skip logging/setLevel on a Modern-negotiated session by @amirejaz in #6184
- Add deny-by-default visibility to vMCP aggregation by @jerm-dro in #6163
- Reject tools/call for tools hidden from tools/list by @jerm-dro in #6216
- Support MCP tool annotations for composite tools by @JAORMX in #6208
- Delegate images keychain to toolhive-core by @JAORMX in #6147
- Add a consent model for external OIDC subject tokens by @jhrozek in #6149
- Clean up deny-visibility validator and CRD docs by @jerm-dro in #6233
- Skip known-bad backends when opening vMCP sessions by @jerm-dro in #6162
- Validate MCPOIDCConfig inline issuer and JWKS URLs by @lopster568 in #5936
- Update module github.com/go-git/go-git/v5 to v5.19.2 [SECURITY] by @renovate[bot] in #6244
- fix(authz): reject non-JSON POSTs instead of skipping authorization by @SashaMIT in #6234
- Release v0.42.1 by @toolhive-release-app[bot] in #6247
New Contributors
- @lopster568 made their first contribution in #5936
- @SashaMIT made their first contribution in #6234
Full Changelog: v0.42.0...v0.42.1
🔗 Full changelog: v0.42.0...v0.42.1
- Non-JSON
-
v0.42.005 Aug 2026Release notes
Open source →🚀 Toolhive v0.42.0 is live!
AI-tool plugin management goes end to end —
thv ai-plugingains a full CLI, REST API, and registry catalog — and the skills supply chain gets Sigstore signature verification at install, sync, and upgrade time. Alongside that, a large batch of MCP dual-era correctness fixes lands: multiple clients can finally share a stdio server, and vMCP stops flapping between the Modern and Legacy revisions.⚠️ Breaking Changes
- Config CRD status fields removed —
status.referencingWorkloadsandstatus.referenceCount(and theReferencesprinter column) are gone from all six config CRDs; replace any automation reading them with a workload field query (migration guide) - Cedar policy is now evaluated against the mutated MCP request — if you run a mutating webhook together with authorization, policy decisions and audit records can change on upgrade; re-audit your policies against the post-mutation shape first (migration guide)
- Recovered HTTP panics no longer produce a log line — an unintended regression from the recovery-middleware migration; without Sentry configured a recovered panic is now silent apart from the 500 (migration guide)
- Go API removals for out-of-tree importers —
pkg/telemetry/providerswas deleted and two long-publishedoptimizerdecconstants were removed (migration guide)
Who is affected: anyone reading
status.referencingWorkloadsorstatus.referenceCountfromMCPOIDCConfig,MCPAuthzConfig,MCPExternalAuthConfig,MCPToolConfig,MCPWebhookConfig, orMCPTelemetryConfig—kubectlusers relying on theREFERENCEScolumn, scripts and GitOps assertions usingjsonpath/jqon those paths, Chainsaw/kuttl tests, kube-state-metrics custom-resource-state configs and the dashboards built on them, and Go code reading.Status.ReferencingWorkloads/.Status.ReferenceCount.MCPWebhookConfigandMCPTelemetryConfigonly ever hadreferencingWorkloads.MCPTelemetryConfignever had aReferencesprinter column, so itskubectl getoutput is unchanged.Upgrade safety: these were derived values computed from workload specs — the source of truth (
spec.*ConfigRefon workloads) is untouched, so nothing unrecoverable is lost. Applying the new schema does not rewrite or reject existing stored objects; residual values stay inert in etcd until each object's status is next written. No storage-version bump, no CRD delete/recreate, no migration job. Deletion protection is unchanged — every config controller still recomputes referrers live at deletion time and setsDeletionBlocked=Truewith reasonReferencedByWorkloads.Before
$ kubectl -n toolhive-system get mcpoidcconfig NAME SOURCE VALID REFERENCES AGE my-oidc inline True 3 5d
After
$ kubectl -n toolhive-system get mcpoidcconfig NAME SOURCE VALID AGE my-oidc inline True 5d
To list referrers, query the workloads by their config-ref:
kubectl -n toolhive-system get mcpservers,mcpremoteproxies,virtualmcpservers -o json \ | jq -r --arg n my-oidc '.items[] | select((.spec.oidcConfigRef.name // .spec.incomingAuth.oidcConfigRef.name) == $n) | "\(.kind)/\(.metadata.name)"'
The reference paths per config kind, exactly as the operator's own indexers define them:
Config kind Workload kinds tracked Spec paths MCPOIDCConfigMCPServer, MCPRemoteProxy, VirtualMCPServer spec.oidcConfigRef.name;spec.incomingAuth.oidcConfigRef.name(vMCP)MCPAuthzConfigMCPServer, MCPRemoteProxy, VirtualMCPServer spec.authzConfigRef.name;spec.incomingAuth.authzConfigRef.name(vMCP)MCPTelemetryConfigMCPServer, MCPRemoteProxy, VirtualMCPServer spec.telemetryConfigRef.nameMCPExternalAuthConfigMCPServer, MCPRemoteProxy spec.externalAuthConfigRef.name, orspec.authServerRef.namewhenspec.authServerRef.kind == "MCPExternalAuthConfig"MCPToolConfigMCPServer spec.toolConfigRef.nameMCPWebhookConfigMCPServer spec.webhookConfigRef.nameNote
kubectl --field-selectorwill not work for these paths — the operator's indexes are controller-runtime cache indexes, not API-server field selectors. Use-o json | jqor-o custom-columns.Migration steps
- While still on v0.41.x, snapshot anything you may need:
kubectl get mcpoidcconfigs,mcpauthzconfigs,mcpexternalauthconfigs,mcptoolconfigs,mcpwebhookconfigs,mcptelemetryconfigs -A -o json > /tmp/thv-config-refs-pre-0.42.json - Grep your automation for
referenceCount,referencingWorkloads, and theReferences/REFERENCEScolumn — shell scripts,kubectl wait --for=jsonpath=, Chainsaw/kuttl assertions, Argo CD/Flux health checks, kube-state-metrics configs, Grafana panels, Kyverno/Gatekeeper rules. - Rewrite each hit with the query for that config kind from the table above. For "is this config still in use?" checks, prefer the condition:
kubectl -n NS get mcpoidcconfig my-oidc -o jsonpath='{.status.conditions[?(@.type=="DeletionBlocked")].message}' helm upgradetheoperator-crdschart, then theoperatorchart. No pre/post hooks needed.- Verify:
kubectl -n toolhive-system get mcpoidcconfigshowsNAME SOURCE VALID AGE, and deletion of a referenced config still leaves it withDeletionBlocked=True. - Go consumers: drop
.Status.ReferencingWorkloads/.Status.ReferenceCountreads. TheWorkloadReferencetype (Kind,Name) is still exported if you want to keep your own list shape.
PR: #5631 — completes the cleanup tracked in #5607
Migration guide: Cedar policy now sees the post-mutation requestWho is affected: only workloads configured with at least one mutating webhook and either Cedar authorization or any consumer of audit / telemetry / usage metrics. Both are shipped, supported, non-mutually-exclusive configurations —
thv run --webhook-config <file with a mutating: entry> --authz-config <file>, orMCPWebhookConfig.spec.mutatingin the operator. Workloads with no mutating webhook see zero change; the republish is gated on the body actually having changed.What was wrong:
ParsingMiddlewareparses the request body once and refuses to parse again. The mutating webhook replacedr.Bodybut passed the request through unchanged, so Cedar evaluated policy against the tool name and arguments that arrived while the backend executed the ones that ran. The audit half was reachable in the default configuration: the event type andtarget.nameresolve through the parsed-request holder regardless ofincludeRequestData(which defaults tofalse), so the audit trail named a request that never executed. Telemetry and usage metrics drifted the same way.Security framing, stated precisely: before v0.42.0, a client could reach a tool or argument set Cedar would have denied by sending a permitted request shape that the webhook rewrote into a forbidden one. A second bug narrowed this in practice:
r.ContentLengthwas not refreshed alongsider.Body, so a mutation that shrank the body failed at the reverse proxy and one that grew it was truncated into invalid JSON. The bypass was live for length-preserving rewrites — which is exactly case/format normalization, and a webhook can pad JSON whitespace to hold length constant. That staleContent-Lengthis also fixed here.Before
client request ──► ParsingMiddleware ──► parse cached ──► mutating webhook rewrites body │ │ Cedar reads ◄────────────────┘ (pre-mutation) audit reads backend runs post-mutationAfter
client request ──► ParsingMiddleware ──► parse cached ──► mutating webhook rewrites body │ RepublishParsedMCPRequest (body changed) │ Cedar reads ◄────────────────┘ (post-mutation) audit reads backend runs post-mutationMigration steps
- Check whether you set
--webhook-configwith amutating:entry (orMCPWebhookConfig.spec.mutating). If not, stop — no action needed. - Read each mutating webhook's patch and enumerate what it rewrites: the JSON-RPC
method,params.name, and/orparams.arguments. - Re-check your Cedar policies against the post-mutation shape — resource names (
MCP::Tool::"<name>") and everywhen { context.arg_* }clause. Policies that were passing only because they never saw the rewrite will now deny, and vice versa. - Update SIEM rules, dashboards, and saved queries keyed on audit
typeortarget.name— for mutated requests those values change on upgrade. - Expect two new fail-closed responses replacing what previously reached the backend: 400 if a webhook rewrites a single request into a JSON-RPC batch, and 500 if a webhook emits a body that is not a valid JSON-RPC request.
Gaps this deliberately does not close, all documented rather than fixed:
- With
includeRequestData: true, the recorded request payload is still the pre-mutation body (audit readsr.Bodybefore the webhook), so event type/target name are post-mutation while the payload is not. - After a webhook renames a tool, the
Mcp-Method/Mcp-Nameheaders forwarded to the backend still name the original tool. A conformant Modern backend rejects the mismatch, so it fails closed — but a mutating webhook should not rename tools on the Modern path. - The tool-call filter and rate limiter run outside
ParsingMiddlewareand still decide against the request as received, so--toolsfiltering remains bypassable by a webhook rename. Tracked in #6134.
This is an unintended regression, not a design decision. It is called out here because it costs you diagnostics silently, and a one-line fix is expected in a patch release.
Who is affected: any operator who relies on ToolHive's logs to diagnose a recovered HTTP panic — including log-based alerts, log-derived metrics, and support bundles. Everyone running without Sentry configured (the default) is affected most.
What changed:
pkg/recoverybecame a thin shim overtoolhive-core/recovery. Core'sMiddlewarerecovers panics silently unless a logger is injected viaWithLogger, and ToolHive's shim passes onlyWithPanicHandler. The OTel span error recording and Sentry issue reporting are genuinely preserved — same span status (codes.Error,"panic recovered"), same sanitization, same raw value to Sentry, same ordering — but theslog.Errorline and its stack trace are gone, and no other middleware picks them up.Before (v0.41.0)
time=... level=ERROR msg="Panic recovered: runtime error: index out of range [3] with length 2 Stack trace: goroutine 42 [running]: runtime/debug.Stack() ..."After (v0.42.0)
(no log output — the client receives 500 Internal Server Error and nothing is recorded locally)Migration steps
- If you have alerts or log-based metrics matching
Panic recovered, they will stop firing. Do not interpret the silence as "no panics" — re-point them at the 500-response rate or at Sentry until the log line returns. - Configure Sentry if you have not already;
ReportPanicstill sends the raw panic value, so panics remain visible as Sentry Issues with full context. - Traces are unaffected — the request span still carries
RecordErrorplus an error status, so OTel-based panic detection keeps working. - When the fix lands, the restored line will be structured (
msg="panic recovered"withpanic,method,path, andstackattributes) rather than the old single formatted string, so write any new log parser against that shape.
PR: #6145
Migration guide: Go API changesWho is affected: only out-of-tree Go code importing ToolHive packages. No CLI, REST API, or CRD surface changes here, and no in-tree caller is affected.
pkg/telemetry/providerswas deleted (#6146)The package and its
/otlpand/prometheussubpackages were removed and consumed fromtoolhive-coreinstead. The graduation is verbatim — every non-test file is byte-identical apart from two self-referential import paths — so all 12 options (WithServiceName,WithServiceVersion,WithOTLPEndpoint,WithHeaders,WithInsecure,WithCACertPath,WithTracingEnabled,WithMetricsEnabled,WithSamplingRate,WithEnablePrometheusMetricsPath,WithCustomAttributes,WithExtraSpanProcessors) plusNewCompositeProvider,ProviderOption, andCompositeProviderkeep identical names and signatures. Nothing about emitted telemetry changes — resource attributes, service-name defaulting, OTLP exporter/TLS config, and Prometheus exporter registration all behave as before.Before
import "github.com/stacklok/toolhive/pkg/telemetry/providers"
After
import "github.com/stacklok/toolhive-core/telemetry/providers"
Two
optimizerdecconstants were removed (#6175)pkg/vmcp/session/optimizerdecno longer exportsCallToolArgToolNameorCallToolArgParameters. Both have been part of the published API since v0.15.0. They existed to read thecall_tooltarget out of a raw arguments map, a pattern that is now known-unsafe:encoding/jsonfalls back to case-insensitive field matching, so a map index and a struct decode resolve different key sets.Before
toolName, _ := args[optimizerdec.CallToolArgToolName].(string) params, _ := args[optimizerdec.CallToolArgParameters].(map[string]any)
After
// Decode with the same call both dispatch sites use, so key matching cannot diverge. in, err := schema.Translate[optimizer.CallToolInput](args)
registry.Providergained three methods (#6135)ListAvailablePlugins(),GetPlugin(namespace, name), andSearchPlugins(query)were added to the interface. Implementations that embedregistry.BaseProviderpick up no-op defaults and need no change; anything satisfying the old method set directly will no longer compile.Migration: embed
registry.BaseProviderin your provider struct, or implement the three methods.🔄 Deprecations
pkg/audit's MCP event constants,LevelAudit, andNewAuditLoggerare now transitional aliases forgithub.com/stacklok/toolhive-core/auditand will be removed once the migration's cleanup wave rewrites imports per subtree — prefer thetoolhive-core/auditsymbols in new code (#6148)
🆕 New Features
- Manage plugins for AI coding tools with the new
thv ai-plugincommand group —build,validate,push,install,list,info,uninstall, plus local build management viabuildsandbuilds remove— targeting Claude Code and Codex (#5782) - The same plugin surface is available over REST at
/api/v1beta/plugins(10 endpoints) with a matching Go HTTP client inpkg/plugins/client, so the CLI, API, and external tooling share one contract (#5782) thv ai-plugin install <name>now resolves a plain name against the configured registry instead of failing with a 404 hint, and new catalog routes let you browse and search plugins in a registry (#6135)- Project-scoped skill installs now verify Sigstore signatures before anything is extracted or recorded, recording the signer identity as lock-file
provenance:on first use and rejecting unsigned artifacts unless you pass--allow-unsigned(#6129) thv skill syncre-verifies each managed skill's stored Sigstore bundle offline against the lock file's recorded identity, treating a failed re-verification as drift so a CI gate catches signature changes exactly like content changes (#6131)thv skill upgraderefuses to move a skill to an artifact signed by a different identity — or to an unsigned one — reportingsigner-change-blockedunless you explicitly rotate trust with--allow-signer-change(#6132)- Git-installed skills get full gitsign commit-signature verification, with the chain of trust checked against embedded Fulcio roots and no network access (#6121, #6091)
The skills signing features above are all behind the experimental
TOOLHIVE_SKILLS_LOCK_ENABLEDgate and apply only to project-scoped installs. With the gate unset,thv skill installbehaves exactly as in v0.41.0. Note that git (gitsign) provenance is recorded asprovisional: truebecause the embedded Rekor transparency-log proof is not yet validated — signing time is checked only against the Fulcio certificate's own ~10-minute validity window. OCI provenance is not provisional.🐛 Bug Fixes
- Multiple MCP clients can now connect to a single stdio MCP server through ToolHive — the first handshake is cached and replayed instead of every client after the first getting
duplicate "initialize" received, which also unblocks vMCP aggregating stdio backends (#6153) - A client that retries
initializeon a live connection behind the transparent proxy now receives a fresh session instead of a hard failure, because the proxy no longer forwards a session ID oninitialize(#6152) - vMCP gateways aggregating a dual-era backend such as
github-mcp-serverv1.6.0 no longer oscillate between the Modern and Legacy revisions and fail roughly half their health checks — a Modern promotion must now win a confirmingserver/discoverprobe rather than trusting the negotiated version alone (#6158) - A vMCP backend redeployed from a hint-lying Legacy server to a genuinely Modern one now corrects its reported MCP revision within ~5 minutes instead of staying Legacy until the pod restarts (#6185)
- vMCP now relays backend log and progress notifications from Modern (2026-07-28) backends to the downstream client, opting in through the per-request
io.modelcontextprotocol/logLevel_metakey that replaced the removedlogging/setLevelRPC (#6140) - vMCP clients on the Legacy revision now receive non-reserved backend
_meta(trace ids, custom fields) onresources/readresults, matching what the Modern path already delivered (#6180) - A vMCP pod with the optimizer enabled no longer permanently loses its tool index while continuing to report itself healthy — the in-memory SQLite database is now pinned alive by a dedicated connection, so one cancelled request can't destroy it for the life of the process (#6157)
find_tool'stool_keywordsinput now actually affects results instead of being decoded and dropped, and it drives the lexical BM25 arm whiletool_descriptiondrives semantic matching (#6124)call_toolnow accepts the common LLM malformation wheretool_nameis nested insideparameters, and a genuinely missingtool_nameproduces an error that states the expected shape and lists the parameter names received (#6150)- On Windows, the discovery directory and
server.jsonunder%LOCALAPPDATA%are now protected with an explicit DACL granting only the ToolHive user and SYSTEM, and are ownership-validated before being trusted — POSIX mode bits are advisory on NTFS, so any local account with Modify could previously rewrite thenpipe://discovery URL and redirect the next MCP client (#5951) - The authorization middleware now resolves a
call_tooltarget through the same decoder dispatch uses, closing three case-sensitivity divergences that could skip a policy check or drop arguments (#6175)
🧹 Misc
pkg/telemetry/providers(~2,900 LOC) is deleted in favour of the verbatim graduation intoolhive-core, with no change to emitted telemetry (#6146)pkg/recoverybecomes a thin shim overtoolhive-core/recovery, keeping ToolHive's OTel and Sentry wiring through a panic-handler hook (#6145)- MCP histogram buckets are sourced from
toolhive-core's semconv preset instead of a local literal; the boundaries are unchanged (#6144) - Audit event constants,
LevelAudit, andNewAuditLoggerbecome aliases overtoolhive-core/auditwith byte-identical values, so the audit wire format is untouched (#6148) - Pinned a regression test for vMCP elicitation failing fast when the client advertised the capability but holds no standalone SSE stream, and documented both delivery constraints (#6182)
- Fixed a port-selection TOCTOU race that flaked e2e tests under sharded CI by having the OIDC and LLM gateway mocks hold their own listener from construction (#6142)
- Pinned the
ida-pro-mcpe2e image by digest after an upstream rebuild pulled in the breaking mcp Python SDK 2.0.0, and addedtest/e2e/images/**to the lifecycle suite's trigger filter so an image change can no longer skip the tests that consume it (#6159) - Pinned the
mcp-server-timee2e image by digest for the same upstream breakage, unblocking the proxy suites (#6160) - Fixed the operator integration suites'
timeout waiting for process kube-apiserver to stopflake — 32 of the job's last 51 failures — by awaiting manager shutdown before tearing down envtest (#6179)
📦 Dependencies
Module Version github.com/stacklok/toolhive-corev0.0.35 → v0.0.38 github.com/stacklok/toolhive-catalogv0.20260804.0 github.com/tailscale/hujsonb80ff77 coverallsapp/github-action8d6379e github/codeql-actionf205ea1 anthropics/claude-code-actionv1.0.183 toolhive-corewas bumped across #6144, #6146, and #6180 rather than by a dependency PR; v0.0.38 also carries transitive bumps to aws-sdk-go-v2, go-containerregistry, moby/client, prometheus, and otel.👋 Welcome to our newest contributor: @Tanguille 🎉
Full commit logWhat's Changed
- Add plugin REST API, thv ai-plugin CLI, and HTTP client (Phase 4) by @JAORMX in #5782
- Remove referencingWorkloads/referenceCount from config CRD statuses by @ChrisJBurns in #5631
- Wire tool_keywords into the BM25 search arm by @aponcedeleonch in #6124
- Refresh the cached MCP parse after webhook mutation by @jhrozek in #6136
- Add plugin registry catalog surface and name-based install by @JAORMX in #6135
- Fix port-selection TOCTOU race in e2e tests by @jhrozek in #6142
- Adopt core MCP semconv bucket preset in telemetry by @JAORMX in #6144
- Delegate recovery middleware to toolhive-core with observability hooks by @JAORMX in #6145
- Alias audit event constants and logger to toolhive-core by @JAORMX in #6148
- Pin ida-pro-mcp e2e image by digest by @aponcedeleonch in #6159
- Pin mcp-server-time e2e image by digest by @amirejaz in #6160
- Confirm Modern promotion with a discover probe by @amirejaz in #6158
- Strip session ID from proxied initialize requests by @amirejaz in #6152
- Update github.com/tailscale/hujson digest to b80ff77 by @renovate[bot] in #6166
- Update coverallsapp/github-action digest to 8d6379e by @renovate[bot] in #6165
- Update github/codeql-action digest to f205ea1 by @renovate[bot] in #6167
- Authorize and accept call_tool with nested tool_name by @Tanguille in #6150
- Keep optimizer store's in-memory database alive by @aponcedeleonch in #6157
- Decode the call_tool target as dispatch does by @aponcedeleonch in #6175
- Serve repeat initialize from a cached result by @amirejaz in #6153
- Await manager shutdown before stopping envtest by @aponcedeleonch in #6179
- Add skills verifier wrapping toolhive-core Sigstore exports by @samuv in #6091
- Add gitsign commit-signature verification by @samuv in #6121
- Verify skill signatures at install time by @samuv in #6129
- Adopt telemetry providers from toolhive-core by @JAORMX in #6146
- Opt Modern backends into log notifications via logLevel _meta by @JAORMX in #6140
- Re-verify stored signatures offline during sync by @samuv in #6131
- Update anthropics/claude-code-action action to v1.0.183 by @renovate[bot] in #6173
- Block upgrades that change the signer identity by @samuv in #6132
- Set Windows DACL on discovery directory under LOCALAPPDATA by @stantheman0128 in #5951
- Pin elicitation fail-fast without a standalone stream by @JAORMX in #6182
- Expire the refuted-Modern-hint memory after a TTL by @JAORMX in #6185
- Forward resource-read _meta in vMCP and the transport bridge by @JAORMX in #6180
- Update module github.com/stacklok/toolhive-catalog to v0.20260804.0 by @renovate[bot] in #6193
- Release v0.42.0 by @toolhive-release-app[bot] in #6207
New Contributors
- @Tanguille made their first contribution in #6150
Full Changelog: v0.41.0...v0.42.0
🔗 Full changelog: v0.41.0...v0.42.0
- Config CRD status fields removed —
-
v0.41.1-0.20260803151503-1f50e2b4549a03 Aug 2026 pre-releaseNothing published for this version
-
v0.41.028 Jul 2026Release notes
Open source →🚀 Toolhive v0.41.0 is live!
This release delivers first-class support for the new MCP 2026-07-28 ("Modern") specification revision across every ToolHive surface — the transport proxies, the transparent proxy, and Virtual MCP now recognize, serve, and bridge both the session-based 2025-11-25 revision and the new stateless revision, including mixed client×backend combinations. It also ships a reproducible project-skills workflow (
thv skill sync/upgradewith a lock file and Sigstore groundwork), an opt-in Envoy network-isolation backend, and RFC 8693 token exchange with full delegation-chain auditing.⚠️ Breaking Changes
- Storage version migrator is now enabled by default in the operator Helm chart — namespace-scoped installs (
operator.rbac.scope=namespace) failhelm upgradeat render time unless they setoperator.features.storageVersionMigrator: false; cluster-scoped installs need no action (#5603) - JSON-RPC batch requests are now rejected — top-level arrays get HTTP 400 /
-32600instead of being executed; batches previously bypassed authorization, tool filtering, and audit, and MCP removed batching in 2025-06-18, so send individual requests (#5931) - Rate-limit JSON-RPC error code moved from
-32029to429— the MCP 2026-07-28 spec reserves-32020..-32099; clients branching onerror.code == -32029must match429(the HTTP 429 status,Retry-Afterheader, anddata.retryAfterSecondsare unchanged) (#6120)
The chart now enables the StorageVersionMigrator controller by default (
operator.features.storageVersionMigrator: true), and a new chart validation rejects that combination withoperator.rbac.scope=namespace— the controller cannot sync its cluster-scoped CRD informer under namespace RBAC. Affected users seehelm install/helm upgradefail with:operator.features.storageVersionMigrator requires operator.rbac.scope=clusterCluster-scoped installs (the chart default) need no action: the operator pod restarts once with the migrator enabled and begins automatically trimming
status.storedVersionson ToolHive CRDs — the precondition for a future release to drop deprecated API versions. No new pods or RBAC objects are created. To opt out anyway, setoperator.features.storageVersionMigrator: false.Namespace-scoped installs must opt out explicitly:
Before
operator: rbac: scope: namespace
After
operator: rbac: scope: namespace features: storageVersionMigrator: false
Migration steps
- Check whether you are affected:
helm get values <release> -n <ns>— you are affected ifoperator.rbac.scopeisnamespaceandoperator.features.storageVersionMigratoris unset ortrue. - Add
operator.features.storageVersionMigrator: falseto your values (or pass--set operator.features.storageVersionMigrator=false). - Run
helm upgradeas usual. - Since namespace-scoped installs cannot run the migrator, plan to clean CRD
status.storedVersionsby other means (e.g. a one-off run of kube-storage-version-migrator) before any future release drops a deprecated CRD version. Seedocs/operator/storage-version-migration.md.
PR: #5603
Migration guide: JSON-RPC batch rejection and rate-limit error codeBatch requests (#5931) — affects only clients sending JSON-RPC batches (removed from MCP in 2025-06-18); no conformant 2025-11-25 or 2026-07-28 client emits them.
Before
[{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{...}}, {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{...}}]After
Each request must be its own POST; a batch now returns:
{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid Request: batch requests are not supported"}}Rate-limit error code (#6120) — affects only clients branching on the JSON-RPC
error.codefor backoff.Before
{"jsonrpc":"2.0","error":{"code":-32029,"message":"rate limited","data":{"retryAfterSeconds":30}}}After
{"jsonrpc":"2.0","error":{"code":429,"message":"rate limited","data":{"retryAfterSeconds":30}}}Migration steps
- Replace any JSON-RPC batch sends with sequential or concurrent single requests.
- Update rate-limit handling to match
error.code == 429— or, better, key off the HTTP 429 status ordata.retryAfterSeconds, which are stable across versions.
Upgrade notes (not breaking, worth knowing)
- HMAC secrets are now used byte-for-byte (#6067): if your
HMACSecretRefssecret file carried leading/trailing whitespace bytes, previously-minted authorization codes and refresh tokens fail validation once after upgrade and clients re-authenticate; ensure the mounted secret is exactly the raw random bytes (no trailing newline). - Denial/error responses are now spec-conformant JSON-RPC (#5944, #6055, #6066, #6068): correct envelope, standard error codes instead of HTTP statuses (500→
-32603),idomitted rather thannull, generic denial messages, and filtered tool calls answered with-32602over HTTP 200 instead of a bodyless 400 — monitoring or scripts keyed to the old malformed shapes need updating.
🆕 New Features
MCP 2026-07-28 ("Modern") spec support
- ToolHive now speaks the MCP 2026-07-28 stateless revision end to end, negotiating both revisions per backend via go-sdk v1.7 (#5993)
- The streamable and transparent proxies classify and serve Modern stateless requests alongside Legacy session traffic (#5839, #5884)
- Virtual MCP serves Modern clients directly (#5953), classifies request revisions at the client edge (#5913), and resolves each backend's revision independently (885e8b1)
- Era-mismatched client×backend combinations are bridged in vMCP, so a Modern client can use Legacy backends and vice versa (#6006)
- Modern client-facing dispatch is complete with listen-stream support and pagination (#6050), gated per capability instead of a global kill-switch (#6033)
- vMCP falls back to Legacy when a backend's revision probe is inconclusive, keeping health checks and aggregation resilient (#6001)
- Mid-call elicitation/sampling refusals to Modern clients are classified per spec with the new error codes (#6061), and the typed
input_requiredseam lands as groundwork for MRTR (SEP-2322) (#6074) - Reserved
io.modelcontextprotocol/*keys are stripped from backend response_metaso backends cannot spoof protocol metadata (#6024) - W3C trace context now propagates through outbound MCP
_meta(SEP-414), joining backend spans to the client→proxy→server trace (#5964) - Tool definitions carrying invalid
x-mcp-headerannotations (SEP-2243) are rejected as the spec requires (#6013) - The readiness probe sends the current MCP protocol version instead of a hardcoded 2024-11-05 (#5940)
- Opt-in strict
MCP-Protocol-Versionheader validation for the streamable proxy (#5957)
Virtual MCP conformance and aggregation
- vMCP is now MCP-conformant: completions, resource templates, subscriptions, and mid-call server→client forwarding all work (#5875)
- The streamable HTTP proxy supports GET listen streams and routes server-to-client messages per session, so progress, sampling, and elicitation reach clients (#5934)
- Backend
list_changednotifications are consumed and propagated to clients for tools (#5965) and for resources and prompts (#5971) - The aggregator resolves cross-backend name conflicts for resources, resource templates, and prompts, not just tools (#6075), and drops ambiguous prompt names instead of failing aggregation (#6099)
- tools/list pagination completeness is guaranteed for >1000-tool aggregated sets (#6021)
Reproducible project skills
- Project-scoped skill installs are pinned in a
toolhive.lock.yamllock file (#5892, #5893, #5894) thv skill syncrestores a project's pinned skill set on any machine and verifies on-disk content in CI (#5895)thv skill upgradere-resolves pinned skills to newer content without silent lock drift (#5896)- Sync and upgrade gain typed exit codes and a pre-install confirmation gate for scriptable CI use (#5897)
- Sigstore signing groundwork for skill OCI artifacts: provenance fields in the lock schema (#6015), a signer package (#6023), and commit-signature plumbing with bundle storage (#6084)
Authentication and token exchange
- RFC 8693 token exchange is wired into the embedded authorization server's token endpoint (#5881), with delegated token audience bounded by the subject token (#5882)
- Subject tokens from trusted external OIDC issuers (Keycloak, Entra, Okta) can be validated for token exchange (#5995)
- Audit logs capture the RFC 8693
actclaim and full delegation chain, so "agent X acting for Alice" is distinguishable from Alice (#6046, refined in #6096 and adopted as the toolhive-core canonical schema in #6107) - Malformed prior
actclaims are rejected at token exchange per RFC 8693 §4.1 (#6114) - Dynamically registered OAuth clients renew expiring client secrets automatically (RFC 7591/7592) (#5377)
- Multi-valued JWT claims can be normalized to canonical space-delimited form for Cedar policies, unifying Entra/Keycloak/Okta scope shapes (#5880)
- Authentication failures, webhook denials, and rate-limit rejections now produce audit events (#5874, #6005)
Network isolation
- An opt-in Envoy network-proxy backend (
TOOLHIVE_NETWORK_PROXY=envoy) consolidates egress and ingress into one container (#5907, seam extracted in #5906) AllowPortpermissions translate into Envoy egress RBAC policy, matching Squid's port ACLs (#5927)- The Envoy backend is hardened with a digest-pinned image and disabled admin interface (#5949), fixed for Linux Docker Engine readiness (#5926), and covered by e2e tests (#5918)
Other
thv runandthv buildgain a repeatable--build-withflag to constrain build-time dependencies (e.g.--build-with 'mcp<2'foruvx://packages) (#6111, #6116)- The operator Helm chart gains an opt-in image-discovery Deployment so air-gapped installs can statically discover runtime images (#6047)
🐛 Bug Fixes
- The authz response filter can no longer be bypassed via media-type or status-code tricks by a malicious backend (#6092)
- SSE responses are parsed per event rather than per line, closing a tool-list smuggling vector (#6088); filter failures are framed as proper SSE events instead of being silently ignored (#6087); and raw SSE buffers are no longer spliced into error text (#6079)
- Cedar authorization no longer denies everything when an upstream IdP issues JWT access tokens without referenced identity claims like
email(#6022) - VirtualMCPServer now honors
MCPOIDCConfig.caBundleRef, fixing OIDC discovery against self-signed issuers (#4923) - Denial and error paths emit conformant JSON-RPC 2.0 with standard error codes, scrubbed error text, and omitted (not null) ids (#6055, #6066, #6068, #6086)
- Filtered tool calls return a JSON-RPC error over HTTP 200 instead of a bodyless HTTP 400 (#5944), and session-not-found 404s echo the request id (#6031)
- Streamable-http SSE frames carry
event: message, unbreaking spec-lenient clients such as@ai-sdk/mcp(#5954) - Binary HMAC secrets are used as-is instead of being whitespace-trimmed (#6067)
- Digest-pinned image pulls (
tag@digest) no longer fail at container create (#5978) thv listno longer poisons workload status when transient Docker errors occur (#6076), the ingress proxy port is no longer derived from a fixed upstream port (#6069), and concurrent isolated-workload startups no longer race on network creation and ingress DNS (#6071, #6083)- Ingress Squid warms upstream connections at startup, removing cold-start latency skew (#5887)
- TUF outages no longer hard-fail
thv runin warn mode (#6063) - Backend tool schemas keep top-level
oneOf/anyOf/enumkeywords through vMCP ingestion (#5990)
🧹 Misc
- New architecture documentation for vMCP backend revision classification and the negotiate-down rationale (#6016, #6017, #6025)
- Extensive regression pins guarding the mcp-go → go-sdk migration: per-session security, TTL/DNS-rebind/bridge/schema behaviors, and Redis-backed mixed-era coverage (#5974, #5977, #5981, #6056)
- Large e2e reliability and speed investment: parallel Ginkgo procs, reliable readiness waits, and deflaked forwarding/group/dual-era suites (#6070, #6012, #6063, and others)
- Deterministic swagger docgen and regenerated OpenAPI docs (9ef27fa, #5970)
- Shared source-dispatch skeleton for skill sources and preserved inner error codes in the skills API (#5967, #5966)
- WorkflowAuditor event prologue extracted to remove eight-way duplication (#6122)
📦 Dependencies
Module Version google.golang.org/grpcv1.82.1 (security) github.com/klauspost/compressv1.18.7 (security, #6041) github.com/stacklok/toolhive-catalogv0.20260727.0 actions/checkoutdigest d23441a github/codeql-actiondigest e4fba86 golang/govulncheck-actiondigest 032d455 anthropics/claude-code-actiondigest be7b93b 👋 Welcome to our newest contributors: @amir-rezaei and @stantheman0128 🎉
Full commit logWhat's Changed
- Add stateless routing to streamable MCP proxy by @jhrozek in #5839
- Normalize opt-in multi-valued JWT claims for Cedar by @jhrozek in #5880
- Bound delegated token audience by subject token by @jhrozek in #5882
- Wire token exchange handler into fosite by @jhrozek in #5881
- Implement Client Secret Expiry and Renewal for Dynamic Client Registration by @Sanskarzz in #5377
- Serve MCP 2026-07-28 stateless requests through the transparent proxy by @jhrozek in #5884
- Warm ingress Squid peer connections with standby=2 by @jhrozek in #5887
- Ignore flaky tools-call-sampling in conformance CI by @jhrozek in #5888
- Make vMCP MCP-conformant: completions, templates, subscribe, forwarding by @JAORMX in #5875
- Extract a swappable networkProxy seam from deployOps by @ChrisJBurns in #5906
- Isolate HTTP transport in vmcp backend tests by @JAORMX in #5919
- Add Envoy network-proxy backend behind TOOLHIVE_NETWORK_PROXY=envoy by @ChrisJBurns in #5907
- Add Envoy network-isolation e2e tests by @ChrisJBurns in #5918
- Default storage version migrator to enabled in operator chart by @ChrisJBurns in #5603
- Wire MCP 2026-07-28 revision classification into vMCP by @jhrozek in #5913
- Reject JSON-RPC batches to close authz blind spot by @JAORMX in #5931
- Update batch e2e test to expect rejection by @JAORMX in #5933
- Add project-level skills lock file schema package by @samuv in #5892
- Fix Envoy isolated-server readiness and enable e2e tests by @ChrisJBurns in #5926
- Deflake vMCP forwarding notification wait by @JAORMX in #5941
- Send current MCP protocol version in readiness probe by @JAORMX in #5940
- Translate AllowPort into Envoy egress RBAC policy by @ChrisJBurns in #5927
- Route server-to-client MCP messages per session by @JAORMX in #5934
- Return JSON-RPC error for filtered tool calls by @JAORMX in #5944
- Update standalone-SSE test for filtered-tool error by @JAORMX in #5958
- Add opt-in strict MCP-Protocol-Version validation by @JAORMX in #5957
- Add SkillLockService interface and managed install flag by @samuv in #5893
- Drain backend notifications before per-call client close by @JAORMX in #5960
- Deflake real-backend forwarding tests by @JAORMX in #5963
- Record project-scope skill installs in the lock file by @samuv in #5894
- Add thv skill sync: restore project skills from the lock file by @samuv in #5895
- Propagate W3C trace context in outbound MCP _meta by @JAORMX in #5964
- Add thv skill upgrade: re-resolve pinned skills to newer content by @samuv in #5896
- Add typed exit codes and confirmation gate to skill sync/upgrade by @samuv in #5897
- Consume backend list_changed in vMCP and propagate to clients by @JAORMX in #5965
- Regenerate swagger docs to drop duplicate enums by @JAORMX in #5970
- Propagate MCPOIDCConfig CA bundle to vmcp OIDC config by @ChrisJBurns in #4923
- Harden Envoy backend: pin image digest, disable admin interface by @ChrisJBurns in #5949
- Serve MCP 2026-07-28 Modern stateless requests through vMCP by @jhrozek in #5953
- Propagate backend resources/prompts list_changed in vMCP by @JAORMX in #5971
- Add vMCP per-session security regression tests by @JAORMX in #5974
- Fix digest-pinned image pulls failing at container create by @ChrisJBurns in #5978
- Add regression pins for TTL, DNS-rebind, bridge, schema by @JAORMX in #5977
- Update module google.golang.org/grpc to v1.82.1 [SECURITY] by @renovate[bot] in #5943
- Update golang/govulncheck-action digest to 032d455 by @renovate[bot] in #5869
- Update anthropics/claude-code-action digest to be7b93b by @renovate[bot] in #5983
- Make swagger docgen deterministic (dedupe swag's doubled enums) by @jhrozek in #5987
- Handle per-backend MCP revision in vMCP (client-side dual protocol) by @jhrozek in #5980
- Bump toolhive-core for tool-schema ingestion fix by @JAORMX in #5990
- Unblock main CI: fix vMCP nil-registry panic and CRD docs drift by @jhrozek in #5999
- Fall back to Legacy on an inconclusive vMCP revision probe by @jhrozek in #6001
- Preserve inner error codes through the skills lock-state wrap by @samuv in #5966
- Extract shared source-dispatch skeleton for skill sources by @samuv in #5967
- Add live e2e tests for dual-era stateless proxy behavior by @jhrozek in #5981
- Validate subject tokens from external OIDC issuers by @jhrozek in #5995
- Support MCP 2026-07-28 spec (go-sdk v1.7 via toolhive-core) by @JAORMX in #5993
- Unify e2e yardstick backend on 1.2.0 by @jhrozek in #6004
- Update module github.com/stacklok/toolhive-catalog to v0.20260727.0 by @renovate[bot] in #6003
- Audit authentication failures and webhook denials by @JAORMX in #5874
- Audit stream opens on flush and pre-write panics by @JAORMX in #6005
- Pin forwarding tools/call against a stale-Legacy stateless backend by @JAORMX in #6010
- Make e2e workload readiness waits reliable by @JAORMX in #6012
- Add provenance and unsigned fields to the skills lock schema by @samuv in #6015
- Reject backend tools with invalid x-mcp-header by @JAORMX in #6013
- Document vMCP backend revision classification by @JAORMX in #6016
- Consolidate the negotiate-down rationale by @JAORMX in #6017
- Strip reserved io.modelcontextprotocol/* keys from responses by @jhrozek in
Note truncated.
- Storage version migrator is now enabled by default in the operator Helm chart — namespace-scoped installs (
-
v0.40.2-0.20260724105753-8d00a6b090f424 Jul 2026 pre-releaseNothing published for this version
-
v0.40.2-0.20260724093235-52bc0638f79b24 Jul 2026 pre-releaseNothing published for this version
-
v0.40.2-0.20260720190512-db4ecb1e7e5020 Jul 2026 pre-releaseNothing published for this version
-
v0.40.120 Jul 2026Release notes
Open source →What's Changed
- Serve prompts per-session in vMCP by @JAORMX in #5857
- Strip hop-by-hop headers before SigV4 signing by @Yanhaoxi in #5836
- Stop auto-requesting changes on XL PRs by @jhrozek in #5864
- Capture proxy logs in conformance CI runs by @jhrozek in #5863
- Respect DCR-negotiated token endpoint auth method by @tgrunnagle in #5866
- Stop VirtualMCPServer hot-reconcile on cleared podTemplateSpec by @jhrozek in #5846
- Audit authorization denials on the proxy runner path by @JAORMX in #5872
- Update anthropics/claude-code-action digest to af0559e by @renovate[bot] in #5868
- Fix isolation error wording and log-level rationale from #5794 review by @aponcedeleonch in #5853
- Update module github.com/stacklok/toolhive-catalog to v0.20260717.0 by @renovate[bot] in #5858
- Update module github.com/stacklok/toolhive-catalog to v0.20260720.0 by @renovate[bot] in #5876
- Release v0.40.1 by @toolhive-release-app[bot] in #5877
New Contributors
Full Changelog: v0.40.0...v0.40.1
-
v0.40.1-0.20260718093123-1c60f0faf53518 Jul 2026 pre-releaseNothing published for this version
-
v0.40.017 Jul 2026Release notes
Open source →🚀 Toolhive v0.40.0 is live!
This release hardens Virtual MCP authorization end-to-end — explicit HTTP 403 denials, a unified authz gate, and complete capability pagination — while laying the groundwork for agentic auth (RFC 8693 token exchange, the MCP 2026-07-28 revision) and moving MCP protocol handling onto the official
modelcontextprotocol/go-sdk. It also fixes network isolation silently breaking--network hostworkloads and closes an SSRF gap in upstream Dynamic Client Registration.🆕 New Features
- The embedded authorization server gains an RFC 8693 OAuth 2.0 Token Exchange grant handler, so an agent can exchange a user's token for a delegated token carrying both the user (
sub) and the acting agent (act.sub) — the foundation for agentic delegation (not yet wired into the server) (#5822). - ToolHive's request-parsing layer now understands the upcoming stateless MCP 2026-07-28 ("Modern") revision — a revision classifier, the
Mcp-Method/Mcp-Nameheader and_metavocabulary, andserver/discover/subscriptions/listenauthz registration — dormant until later slices wire it into proxy routing, with no change to existing traffic (#5834).
🐛 Bug Fixes
- Virtual MCP now returns an explicit HTTP 403 (with a JSON-RPC error code 403 and a "denied by authorization policy" message) when a Cedar policy denies a
tools/call,resources/read, orprompts/get, instead of a misleading-32602"not found" at HTTP 200 — and records the denial asdeniedin the audit log (#5841). thv run --network hostno longer silently loses outbound connectivity: network isolation (on by default) is dropped for host/none networking with a warning, and explicitly combining--isolate-network=truewith--network hostnow fails fast with an actionable error instead of starting a broken workload (#5794).- The Virtual MCP authz gate is hardened so the gate decision, the enforced decision, and the backend forward all derive from a single argument decode, and a backend tool named
execute_tool_scriptnow fails loudly instead of being silently shadowed by the code-mode virtual tool (#5850). - Virtual MCP capability discovery now follows list pagination cursors to exhaustion, so backends advertising more than one page (>1000 tools, resources, or prompts) no longer have their extra capabilities silently dropped from routing and clients (#5851).
- Upstream Dynamic Client Registration (DCR) discovery and registration calls are now routed through a private-IP-guarded HTTP client, closing an SSRF vector (CWE-918) — safe by default and honoring the upstream's existing
allow_private_ipssetting (#5826). - Multi-upstream embedded authservers with two or more OAuth2 DCR upstreams requesting the same scopes now register a distinct client per upstream instead of silently reusing the first upstream's credentials, fixing failed or misattributed authorization (#5824).
🧹 Misc
- Migrated MCP protocol handling from
mark3labs/mcp-goto the go-sdk-backedtoolhive-core/mcpcompatcompatibility shim (a pure, atomic import swap with no call-site logic changes), moving ToolHive onto the officialmodelcontextprotocol/go-sdk. Note: the stdio bridge currently forwards only progress/message notifications, sotools/list_changedand similar notifications are dropped — dynamic-capability servers may show stale lists until clients re-list (#5729). - Resolved temp-dir symlinks in the plugin adapter tests so
task testpasses on macOS (/var→/private/var); product code is unchanged (#5849).
📦 Dependencies
Full commit logModule Version github.com/stacklok/toolhive-corev0.0.29 github.com/stacklok/toolhive-catalogv0.20260716.0 github/codeql-action7188fc3 golang.org/x/exp/jsonrpc29ea1abe What's Changed
- Add upstream identity to DCR credential cache key by @tgrunnagle in #5824
- Update github/codeql-action digest to 7188fc3 by @renovate[bot] in #5778
- Guard upstream-DCR HTTP calls against SSRF by @tgrunnagle in #5826
- Update module github.com/stacklok/toolhive-core to v0.0.29 by @renovate[bot] in #5840
- feat: migrate from mark3labs/mcp-go to toolhive-core/mcpcompat (go-sdk) by @JAORMX in #5729
- Update module github.com/stacklok/toolhive-catalog to v0.20260716.0 by @renovate[bot] in #5843
- Return HTTP 403 for authz-denied vMCP calls (#5827) by @JAORMX in #5841
- Update golang.org/x/exp/jsonrpc2 digest to 9ea1abe by @renovate[bot] in #5780
- Reconcile network isolation with network mode by @JAORMX in #5794
- Resolve temp dirs in plugin adapter tests by @jhrozek in #5849
- Add MCP 2026-07-28 revision classification and method vocabulary by @jhrozek in #5834
- Harden vMCP authz gate: single parse source and reserved-name check by @JAORMX in #5850
- Add RFC 8693 token exchange handler to the embedded AS by @jhrozek in #5822
- Follow list pagination cursors in vMCP capability discovery by @JAORMX in #5851
- Release v0.40.0 by @toolhive-release-app[bot] in #5855
Full Changelog: v0.39.0...v0.40.0
- The embedded authorization server gains an RFC 8693 OAuth 2.0 Token Exchange grant handler, so an agent can exchange a user's token for a delegated token carrying both the user (
-
v0.39.016 Jul 2026Release notes
Open source →What's Changed
- Stop VirtualMCPServer hot-reconcile on cleared imagePullSecrets by @jhrozek in #5821
- Update module github.com/stacklok/toolhive-core to v0.0.28 by @renovate[bot] in #5798
- Add Bedrock compatibility flag to thv llm setup by @aponcedeleonch in #5832
- Release v0.39.0 by @toolhive-release-app[bot] in #5833
Full Changelog: v0.38.0...v0.39.0
-
v0.38.015 Jul 2026Release notes
Open source →What's Changed
- Fix tool-filter terminal drain dropping error bodies by @aponcedeleonch in #5816
- Add Codex App LLM setup support by @JAORMX in #5810
- Release v0.38.0 by @toolhive-release-app[bot] in #5820
Full Changelog: v0.37.0...v0.38.0
-
v0.37.015 Jul 2026Release notes
Open source →What's Changed
- Document operator phase conventions by @buyicoder in #5766
- Add runtime-stage environment variables to protocol Dockerfiles by @danbarr in #5801
- Add MCP conformance CI job for thv run proxy by @jhrozek in #5806
- Prefer context7 for MCP spec lookups, drop hardcoded version by @jhrozek in #5805
- Expose --allow-docker-gateway in workload API by @kantord in #5799
- Added spec.podTemplateSpec support to MCPRemoteProxy by @Sanskarzz in #5531
- Stop SSE filter from leaking tools/list on undecodable lines by @saivedant169 in #5304
- Validate aud and resource claims in returned ID-JAG JWT by @jhrozek in #5716
- Flush tool-filter buffer after handler returns by @aponcedeleonch in #5809
- Release v0.37.0 by @toolhive-release-app[bot] in #5811
New Contributors
- @buyicoder made their first contribution in #5766
- @saivedant169 made their first contribution in #5304
Full Changelog: v0.36.0...v0.37.0
-
v0.36.1-0.20260715103409-24cbd8e9788c15 Jul 2026 pre-releaseNothing published for this version
-
v0.36.014 Jul 2026Nothing published for this version
-
v0.35.013 Jul 2026Nothing published for this version
-
v0.34.1-0.20260707105509-9662681329fd07 Jul 2026 pre-releaseNothing published for this version
-
v0.34.007 Jul 2026Nothing published for this version
-
v0.33.1-0.20260707083519-61655d18b6f007 Jul 2026 pre-releaseNothing published for this version
-
v0.33.1-0.20260706184833-609c0ad2f6bc06 Jul 2026 pre-releaseNothing published for this version
-
v0.33.001 Jul 2026Nothing published for this version
-
v0.32.030 Jun 2026Nothing published for this version
-
v0.31.1-0.20260624195959-6132d91b8b5d24 Jun 2026 pre-releaseNothing published for this version
-
v0.31.024 Jun 2026Nothing published for this version
-
v0.30.2-0.20260624163502-baa8bb4542c624 Jun 2026 pre-releaseNothing published for this version
-
v0.30.123 Jun 2026Nothing published for this version
-
v0.30.1-0.20260621225126-21b45bac7ab521 Jun 2026 pre-releaseNothing published for this version
-
v0.30.1-0.20260617171315-4cc44504a26017 Jun 2026 pre-releaseNothing published for this version
-
v0.30.1-0.20260617144304-1f4b7e7ce13f17 Jun 2026 pre-releaseNothing published for this version
-
v0.30.016 Jun 2026Nothing published for this version
-
v0.29.310 Jun 2026Nothing published for this version
-
v0.29.3-0.20260610184645-0c62d1c53d8e10 Jun 2026 pre-releaseNothing published for this version
-
v0.29.210 Jun 2026Nothing published for this version
-
v0.29.104 Jun 2026Nothing published for this version
-
v0.29.003 Jun 2026Nothing published for this version
-
v0.28.4-0.20260603102814-535e8084bd6e03 Jun 2026 pre-releaseNothing published for this version
-
v0.28.4-0.20260522162542-4587fc8b90b222 May 2026 pre-releaseNothing published for this version
-
v0.28.322 May 2026Nothing published for this version
-
v0.28.221 May 2026Nothing published for this version
-
v0.28.2-0.20260520211048-bd73817eee7920 May 2026 pre-releaseNothing published for this version
-
v0.28.120 May 2026Nothing published for this version
-
v0.28.019 May 2026Nothing published for this version
-
v0.27.3-0.20260519081350-0a741f74873419 May 2026 pre-releaseNothing published for this version
-
v0.27.3-0.20260518210916-4bc4624708e318 May 2026 pre-releaseNothing published for this version
-
v0.27.212 May 2026Nothing published for this version
-
v0.27.2-0.20260511140512-c68fdb28f0f811 May 2026 pre-releaseNothing published for this version
-
v0.27.111 May 2026Nothing published for this version
-
v0.27.007 May 2026Nothing published for this version
-
v0.26.130 Apr 2026Nothing published for this version
-
v0.26.029 Apr 2026Nothing published for this version
-
v0.25.028 Apr 2026Nothing published for this version
-
v0.24.123 Apr 2026Nothing published for this version
-
v0.24.022 Apr 2026Nothing published for this version
-
v0.23.121 Apr 2026Nothing published for this version