PackageTrack
Sign in Get early access

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 2026
2026
Release Pre-release

Releases

latest 60 of 447
  1. v0.44.1-0.20260821102605-e25de16cf972 21 Aug 2026 pre-release

    Nothing published for this version

  2. v0.44.1-0.20260819153858-0b8acf26421e 19 Aug 2026 pre-release

    Nothing published for this version

  3. v0.44.0 18 Aug 2026
    Release notes

    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

    Open source →
  4. v0.43.1-0.20260814175116-8343851e8a58 14 Aug 2026 pre-release

    Nothing published for this version

  5. v0.43.0 14 Aug 2026
    Release notes

    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

    Open source →
  6. v0.42.2-0.20260812124719-52ad2932e0b4 12 Aug 2026 pre-release

    Nothing published for this version

  7. v0.42.1 10 Aug 2026
    Release notes

    🚀 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 POST requests are now rejected instead of skipping authorization — with Cedar authorization enabled, a POST without Content-Type: application/json (including a missing header) returns 400 rather than being forwarded unauthorized; set the header on all MCP POSTs (migration guide)
    • vMCP tools hidden from tools/list are no longer directly callable — a tool excluded via filter / excludeAll / excludeAllTools now returns -32602 on the Modern (2026-07-28) path instead of executing; un-filter it or reach it through a composite tool (migration guide)
    • MCPOIDCConfig inline issuer and JWKS URLs are now validated — stored inline configs with a malformed or plain-HTTP URL flip to Valid=False on their next reconcile and block reconciliation of every workload referencing them; add insecureAllowHTTP: true or switch to HTTPS (migration guide)
    Migration guide: non-JSON POSTs are rejected when authorization is enabled

    Who is affected: only deployments that configure Cedar authorization (--authz-config, or authzConfig in the CRD). Deployments without an authorization config are entirely unaffected.

    Previously, shouldSkipInitialAuthorization skipped Cedar evaluation for any POST whose Content-Type was not application/json — but skipping authorization did not stop the request. The proxy forwarded the body verbatim and MCP backends parse JSON-RPC without checking Content-Type, so a tools/call smuggled under text/plain executed 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/plain form now returns 400 Invalid or malformed MCP request.

    Migration steps

    1. Ensure every MCP client, script, and curl invocation sends Content-Type: application/json on POST requests. A missing Content-Type header is also now rejected. Spec-conformant MCP Streamable HTTP clients already comply.
    2. Media-type matching is now case-insensitive and parameter-aware, so Application/JSON and application/json; charset=utf-8 are accepted. Near-miss types that previously prefix-matched — application/json-rpc, application/jsonx — are not.
    3. 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.
    4. Alerting keyed on audit outcomes may see new denied events, since these refusals are now audited as denials rather than generic failures.

    PR: #6234

    Migration guide: hidden vMCP tools are no longer directly callable

    Who is affected: vMCP operators using aggregation.tools filter, per-workload excludeAll, or global excludeAllTools, 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/call straight 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.CallTool now 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
    // Modern client, tools/call { "name": "github_create_issue" }
    // → executed on the backend (Legacy answered -32602 for the same call)

    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

    1. If you relied on calling a filtered tool directly by name, remove it from filter / drop excludeAll for that workload so it appears in tools/list — advertised now means callable, and only advertised is callable.
    2. 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.
    3. If a client called a tool by its {workloadID}.{toolName} alias, switch to the exact conflict-resolved name shown in tools/list (e.g. github_create_issue). The dotted alias remains valid inside composite workflow step definitions — only direct tools/call rejects it.
    4. tools/call for an unknown or hidden tool now answers -32602 at HTTP 400 (previously -32603 at 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.

    PR: #6216 — Fixes #6217

    Migration guide: MCPOIDCConfig URL validation

    Who is affected: clusters with MCPOIDCConfig resources of spec.type: inline whose issuer or jwksUrl is 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. kubernetesServiceAccount configs 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 every MCPServer, MCPRemoteProxy, and VirtualMCPServer referencing it gets OIDCConfigRefValidated=False and 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

    1. 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}'
    2. Flag any entry whose issuer or jwksUrl is http://, has no scheme, or is otherwise malformed. An empty jwksUrl is fine — it falls back to discovery.
    3. For production, change both URLs to https://. For dev/test only, add insecureAllowHTTP: true under spec.inline.
    4. After upgrading, verify: kubectl get mcpoidcconfig <name> -o jsonpath='{.status.conditions[?(@.type=="Valid")]}'. The failure message names the offending URL.
    5. If a workload stalls, check OIDCConfigRefValidated on the referencing MCPServer / MCPRemoteProxy / VirtualMCPServer.

    PR: #5936 — Fixes #4823

    🔄 Deprecations

    • pkg/container/images.NewCompositeKeychain deprecated in favour of github.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: deny so that only workloads explicitly listed in aggregation.tools have 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_TIMEOUT overrides the CLI's API client timeout for thv skill and thv 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/setLevel RPC 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 initialize latency 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 proxy now 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 for thv llm setup --lazy (#6229)
    • A slow or failed first JWKS fetch no longer permanently disables token validation for the life of the process; ErrNotReady and ErrResourceAlreadyExists are 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 jwx bump 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 upgrade no longer requires --allow-ref-change for 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 skill commands accept a relative --project-root such as ., resolving it against the working directory instead of failing with project_root must be absolute (#6223)
    • thv ai-plugin commands accept a relative --project-root the same way, matching thv skill (#6226)

    🧹 Misc

    • The defaultToolVisibility CRD reference no longer carries maintainer-internal defaulting rationale, and an unreachable nil-check was removed from the deny-visibility validator (#6233)
    • pkg/container/images keychain logic is delegated to toolhive-core v0.0.37, with the local file reduced to a deprecated wrapper (#6147)

    📦 Dependencies

    Module Version
    github.com/go-git/go-git/v5 v5.19.2 (fixes CVE-2026-71556 — worktree operations may follow symlinks)

    📝 Upgrade notes

    • Apply the CRDs before the operator. aggregation.defaultToolVisibility requires the v0.42.1 CRDs. The aggregation subtree 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 to allow — every workload in the group has its tools advertised. Verify with kubectl get virtualmcpserver <name> -o jsonpath='{.spec.config.aggregation.defaultToolVisibility}'.
    • defaultToolVisibility gates tools only. Resources, resource templates, and prompts from unlisted backends are still advertised.
    • Composite tools now advertise derived annotations. When annotations is not set, a conservative floor is derived from the workflow's step tools; because most backends declare no annotations today, composite tools typically now advertise destructiveHint: 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 by thv vmcp validate or 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 log

    What'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

    Full Changelog: v0.42.0...v0.42.1

    🔗 Full changelog: v0.42.0...v0.42.1

    Open source →
  8. v0.42.0 05 Aug 2026
    Release notes

    🚀 Toolhive v0.42.0 is live!

    AI-tool plugin management goes end to end — thv ai-plugin gains 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 removedstatus.referencingWorkloads and status.referenceCount (and the References printer 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 importerspkg/telemetry/providers was deleted and two long-published optimizerdec constants were removed (migration guide)
    Migration guide: Config CRD status fields removed

    Who is affected: anyone reading status.referencingWorkloads or status.referenceCount from MCPOIDCConfig, MCPAuthzConfig, MCPExternalAuthConfig, MCPToolConfig, MCPWebhookConfig, or MCPTelemetryConfigkubectl users relying on the REFERENCES column, scripts and GitOps assertions using jsonpath/jq on 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.

    MCPWebhookConfig and MCPTelemetryConfig only ever had referencingWorkloads. MCPTelemetryConfig never had a References printer column, so its kubectl get output is unchanged.

    Upgrade safety: these were derived values computed from workload specs — the source of truth (spec.*ConfigRef on 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 sets DeletionBlocked=True with reason ReferencedByWorkloads.

    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
    MCPOIDCConfig MCPServer, MCPRemoteProxy, VirtualMCPServer spec.oidcConfigRef.name; spec.incomingAuth.oidcConfigRef.name (vMCP)
    MCPAuthzConfig MCPServer, MCPRemoteProxy, VirtualMCPServer spec.authzConfigRef.name; spec.incomingAuth.authzConfigRef.name (vMCP)
    MCPTelemetryConfig MCPServer, MCPRemoteProxy, VirtualMCPServer spec.telemetryConfigRef.name
    MCPExternalAuthConfig MCPServer, MCPRemoteProxy spec.externalAuthConfigRef.name, or spec.authServerRef.name when spec.authServerRef.kind == "MCPExternalAuthConfig"
    MCPToolConfig MCPServer spec.toolConfigRef.name
    MCPWebhookConfig MCPServer spec.webhookConfigRef.name

    Note kubectl --field-selector will not work for these paths — the operator's indexes are controller-runtime cache indexes, not API-server field selectors. Use -o json | jq or -o custom-columns.

    Migration steps

    1. 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
    2. Grep your automation for referenceCount, referencingWorkloads, and the References/REFERENCES column — shell scripts, kubectl wait --for=jsonpath=, Chainsaw/kuttl assertions, Argo CD/Flux health checks, kube-state-metrics configs, Grafana panels, Kyverno/Gatekeeper rules.
    3. 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}'
    4. helm upgrade the operator-crds chart, then the operator chart. No pre/post hooks needed.
    5. Verify: kubectl -n toolhive-system get mcpoidcconfig shows NAME SOURCE VALID AGE, and deletion of a referenced config still leaves it with DeletionBlocked=True.
    6. Go consumers: drop .Status.ReferencingWorkloads / .Status.ReferenceCount reads. The WorkloadReference type (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 request

    Who 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>, or MCPWebhookConfig.spec.mutating in the operator. Workloads with no mutating webhook see zero change; the republish is gated on the body actually having changed.

    What was wrong: ParsingMiddleware parses the request body once and refuses to parse again. The mutating webhook replaced r.Body but 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 and target.name resolve through the parsed-request holder regardless of includeRequestData (which defaults to false), 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.ContentLength was not refreshed alongside r.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 stale Content-Length is also fixed here.

    Before

    client request  ──► ParsingMiddleware ──► parse cached ──► mutating webhook rewrites body
                                                    │                      │
                                              Cedar reads ◄────────────────┘  (pre-mutation)
                                              audit reads                     backend runs post-mutation
    

    After

    client request  ──► ParsingMiddleware ──► parse cached ──► mutating webhook rewrites body
                                                                            │
                                                           RepublishParsedMCPRequest (body changed)
                                                                            │
                                              Cedar reads ◄────────────────┘  (post-mutation)
                                              audit reads                     backend runs post-mutation
    

    Migration steps

    1. Check whether you set --webhook-config with a mutating: entry (or MCPWebhookConfig.spec.mutating). If not, stop — no action needed.
    2. Read each mutating webhook's patch and enumerate what it rewrites: the JSON-RPC method, params.name, and/or params.arguments.
    3. Re-check your Cedar policies against the post-mutation shape — resource names (MCP::Tool::"<name>") and every when { context.arg_* } clause. Policies that were passing only because they never saw the rewrite will now deny, and vice versa.
    4. Update SIEM rules, dashboards, and saved queries keyed on audit type or target.name — for mutated requests those values change on upgrade.
    5. 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 reads r.Body before 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-Name headers 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 ParsingMiddleware and still decide against the request as received, so --tools filtering remains bypassable by a webhook rename. Tracked in #6134.

    PR: #6136 — Fixes #6133

    Migration guide: Recovered panics are no longer logged

    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/recovery became a thin shim over toolhive-core/recovery. Core's Middleware recovers panics silently unless a logger is injected via WithLogger, and ToolHive's shim passes only WithPanicHandler. 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 the slog.Error line 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

    1. 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.
    2. Configure Sentry if you have not already; ReportPanic still sends the raw panic value, so panics remain visible as Sentry Issues with full context.
    3. Traces are unaffected — the request span still carries RecordError plus an error status, so OTel-based panic detection keeps working.
    4. When the fix lands, the restored line will be structured (msg="panic recovered" with panic, method, path, and stack attributes) rather than the old single formatted string, so write any new log parser against that shape.

    PR: #6145

    Migration guide: Go API changes

    Who 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/providers was deleted (#6146)

    The package and its /otlp and /prometheus subpackages were removed and consumed from toolhive-core instead. 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) plus NewCompositeProvider, ProviderOption, and CompositeProvider keep 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 optimizerdec constants were removed (#6175)

    pkg/vmcp/session/optimizerdec no longer exports CallToolArgToolName or CallToolArgParameters. Both have been part of the published API since v0.15.0. They existed to read the call_tool target out of a raw arguments map, a pattern that is now known-unsafe: encoding/json falls 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.Provider gained three methods (#6135)

    ListAvailablePlugins(), GetPlugin(namespace, name), and SearchPlugins(query) were added to the interface. Implementations that embed registry.BaseProvider pick up no-op defaults and need no change; anything satisfying the old method set directly will no longer compile.

    Migration: embed registry.BaseProvider in your provider struct, or implement the three methods.

    🔄 Deprecations

    • pkg/audit's MCP event constants, LevelAudit, and NewAuditLogger are now transitional aliases for github.com/stacklok/toolhive-core/audit and will be removed once the migration's cleanup wave rewrites imports per subtree — prefer the toolhive-core/audit symbols in new code (#6148)

    🆕 New Features

    • Manage plugins for AI coding tools with the new thv ai-plugin command group — build, validate, push, install, list, info, uninstall, plus local build management via builds and builds 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 in pkg/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 sync re-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 upgrade refuses to move a skill to an artifact signed by a different identity — or to an unsigned one — reporting signer-change-blocked unless 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_ENABLED gate and apply only to project-scoped installs. With the gate unset, thv skill install behaves exactly as in v0.41.0. Note that git (gitsign) provenance is recorded as provisional: true because 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 initialize on 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 on initialize (#6152)
    • vMCP gateways aggregating a dual-era backend such as github-mcp-server v1.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 confirming server/discover probe 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 _meta key that replaced the removed logging/setLevel RPC (#6140)
    • vMCP clients on the Legacy revision now receive non-reserved backend _meta (trace ids, custom fields) on resources/read results, 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's tool_keywords input now actually affects results instead of being decoded and dropped, and it drives the lexical BM25 arm while tool_description drives semantic matching (#6124)
    • call_tool now accepts the common LLM malformation where tool_name is nested inside parameters, and a genuinely missing tool_name produces an error that states the expected shape and lists the parameter names received (#6150)
    • On Windows, the discovery directory and server.json under %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 the npipe:// discovery URL and redirect the next MCP client (#5951)
    • The authorization middleware now resolves a call_tool target 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 in toolhive-core, with no change to emitted telemetry (#6146)
    • pkg/recovery becomes a thin shim over toolhive-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, and NewAuditLogger become aliases over toolhive-core/audit with 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-mcp e2e image by digest after an upstream rebuild pulled in the breaking mcp Python SDK 2.0.0, and added test/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-time e2e 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 stop flake — 32 of the job's last 51 failures — by awaiting manager shutdown before tearing down envtest (#6179)

    📦 Dependencies

    Module Version
    github.com/stacklok/toolhive-core v0.0.35 → v0.0.38
    github.com/stacklok/toolhive-catalog v0.20260804.0
    github.com/tailscale/hujson b80ff77
    coverallsapp/github-action 8d6379e
    github/codeql-action f205ea1
    anthropics/claude-code-action v1.0.183

    toolhive-core was 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 log

    What'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

    Full Changelog: v0.41.0...v0.42.0

    🔗 Full changelog: v0.41.0...v0.42.0

    Open source →
  9. v0.41.1-0.20260803151503-1f50e2b4549a 03 Aug 2026 pre-release

    Nothing published for this version

  10. v0.41.0 28 Jul 2026
    Release notes

    🚀 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/upgrade with 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) fail helm upgrade at render time unless they set operator.features.storageVersionMigrator: false; cluster-scoped installs need no action (#5603)
    • JSON-RPC batch requests are now rejected — top-level arrays get HTTP 400 / -32600 instead 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 -32029 to 429 — the MCP 2026-07-28 spec reserves -32020..-32099; clients branching on error.code == -32029 must match 429 (the HTTP 429 status, Retry-After header, and data.retryAfterSeconds are unchanged) (#6120)
    Migration guide: storage version migrator default

    The chart now enables the StorageVersionMigrator controller by default (operator.features.storageVersionMigrator: true), and a new chart validation rejects that combination with operator.rbac.scope=namespace — the controller cannot sync its cluster-scoped CRD informer under namespace RBAC. Affected users see helm install/helm upgrade fail with:

    operator.features.storageVersionMigrator requires operator.rbac.scope=cluster
    

    Cluster-scoped installs (the chart default) need no action: the operator pod restarts once with the migrator enabled and begins automatically trimming status.storedVersions on 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, set operator.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

    1. Check whether you are affected: helm get values <release> -n <ns> — you are affected if operator.rbac.scope is namespace and operator.features.storageVersionMigrator is unset or true.
    2. Add operator.features.storageVersionMigrator: false to your values (or pass --set operator.features.storageVersionMigrator=false).
    3. Run helm upgrade as usual.
    4. Since namespace-scoped installs cannot run the migrator, plan to clean CRD status.storedVersions by other means (e.g. a one-off run of kube-storage-version-migrator) before any future release drops a deprecated CRD version. See docs/operator/storage-version-migration.md.

    PR: #5603

    Migration guide: JSON-RPC batch rejection and rate-limit error code

    Batch 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.code for 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

    1. Replace any JSON-RPC batch sends with sequential or concurrent single requests.
    2. Update rate-limit handling to match error.code == 429 — or, better, key off the HTTP 429 status or data.retryAfterSeconds, which are stable across versions.

    Upgrade notes (not breaking, worth knowing)

    • HMAC secrets are now used byte-for-byte (#6067): if your HMACSecretRefs secret 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), id omitted rather than null, generic denial messages, and filtered tool calls answered with -32602 over 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_required seam lands as groundwork for MRTR (SEP-2322) (#6074)
    • Reserved io.modelcontextprotocol/* keys are stripped from backend response _meta so 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-header annotations (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-Version header 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_changed notifications 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.yaml lock file (#5892, #5893, #5894)
    • thv skill sync restores a project's pinned skill set on any machine and verifies on-disk content in CI (#5895)
    • thv skill upgrade re-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 act claim 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 act claims 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)
    • AllowPort permissions 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 run and thv build gain a repeatable --build-with flag to constrain build-time dependencies (e.g. --build-with 'mcp<2' for uvx:// 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 list no 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 run in warn mode (#6063)
    • Backend tool schemas keep top-level oneOf/anyOf/enum keywords 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/grpc v1.82.1 (security)
    github.com/klauspost/compress v1.18.7 (security, #6041)
    github.com/stacklok/toolhive-catalog v0.20260727.0
    actions/checkout digest d23441a
    github/codeql-action digest e4fba86
    golang/govulncheck-action digest 032d455
    anthropics/claude-code-action digest be7b93b

    👋 Welcome to our newest contributors: @amir-rezaei and @stantheman0128 🎉

    Full commit log

    What'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.

    Open source →
  11. v0.40.2-0.20260724105753-8d00a6b090f4 24 Jul 2026 pre-release

    Nothing published for this version

  12. v0.40.2-0.20260724093235-52bc0638f79b 24 Jul 2026 pre-release

    Nothing published for this version

  13. v0.40.2-0.20260720190512-db4ecb1e7e50 20 Jul 2026 pre-release

    Nothing published for this version

  14. v0.40.1 20 Jul 2026
    Release notes

    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

    Open source →
  15. v0.40.1-0.20260718093123-1c60f0faf535 18 Jul 2026 pre-release

    Nothing published for this version

  16. v0.40.0 17 Jul 2026
    Release notes

    🚀 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 host workloads 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-Name header and _meta vocabulary, and server/discover/subscriptions/listen authz 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, or prompts/get, instead of a misleading -32602 "not found" at HTTP 200 — and records the denial as denied in the audit log (#5841).
    • thv run --network host no longer silently loses outbound connectivity: network isolation (on by default) is dropped for host/none networking with a warning, and explicitly combining --isolate-network=true with --network host now 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_script now 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_ips setting (#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-go to the go-sdk-backed toolhive-core/mcpcompat compatibility shim (a pure, atomic import swap with no call-site logic changes), moving ToolHive onto the official modelcontextprotocol/go-sdk. Note: the stdio bridge currently forwards only progress/message notifications, so tools/list_changed and 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 test passes on macOS (/var/private/var); product code is unchanged (#5849).

    📦 Dependencies

    Module Version
    github.com/stacklok/toolhive-core v0.0.29
    github.com/stacklok/toolhive-catalog v0.20260716.0
    github/codeql-action 7188fc3
    golang.org/x/exp/jsonrpc2 9ea1abe
    Full commit log

    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

    Open source →
  17. v0.39.0 16 Jul 2026
    Release notes

    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

    Open source →
  18. v0.38.0 15 Jul 2026
    Release notes

    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

    Open source →
  19. v0.37.0 15 Jul 2026
    Release notes

    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

    Full Changelog: v0.36.0...v0.37.0

    Open source →
  20. v0.36.1-0.20260715103409-24cbd8e9788c 15 Jul 2026 pre-release

    Nothing published for this version

  21. v0.36.0 14 Jul 2026

    Nothing published for this version

  22. v0.35.0 13 Jul 2026

    Nothing published for this version

  23. v0.34.1-0.20260707105509-9662681329fd 07 Jul 2026 pre-release

    Nothing published for this version

  24. v0.34.0 07 Jul 2026

    Nothing published for this version

  25. v0.33.1-0.20260707083519-61655d18b6f0 07 Jul 2026 pre-release

    Nothing published for this version

  26. v0.33.1-0.20260706184833-609c0ad2f6bc 06 Jul 2026 pre-release

    Nothing published for this version

  27. v0.33.0 01 Jul 2026

    Nothing published for this version

  28. v0.32.0 30 Jun 2026

    Nothing published for this version

  29. v0.31.1-0.20260624195959-6132d91b8b5d 24 Jun 2026 pre-release

    Nothing published for this version

  30. v0.31.0 24 Jun 2026

    Nothing published for this version

  31. v0.30.2-0.20260624163502-baa8bb4542c6 24 Jun 2026 pre-release

    Nothing published for this version

  32. v0.30.1 23 Jun 2026

    Nothing published for this version

  33. v0.30.1-0.20260621225126-21b45bac7ab5 21 Jun 2026 pre-release

    Nothing published for this version

  34. v0.30.1-0.20260617171315-4cc44504a260 17 Jun 2026 pre-release

    Nothing published for this version

  35. v0.30.1-0.20260617144304-1f4b7e7ce13f 17 Jun 2026 pre-release

    Nothing published for this version

  36. v0.30.0 16 Jun 2026

    Nothing published for this version

  37. v0.29.3 10 Jun 2026

    Nothing published for this version

  38. v0.29.3-0.20260610184645-0c62d1c53d8e 10 Jun 2026 pre-release

    Nothing published for this version

  39. v0.29.2 10 Jun 2026

    Nothing published for this version

  40. v0.29.1 04 Jun 2026

    Nothing published for this version

  41. v0.29.0 03 Jun 2026

    Nothing published for this version

  42. v0.28.4-0.20260603102814-535e8084bd6e 03 Jun 2026 pre-release

    Nothing published for this version

  43. v0.28.4-0.20260522162542-4587fc8b90b2 22 May 2026 pre-release

    Nothing published for this version

  44. v0.28.3 22 May 2026

    Nothing published for this version

  45. v0.28.2 21 May 2026

    Nothing published for this version

  46. v0.28.2-0.20260520211048-bd73817eee79 20 May 2026 pre-release

    Nothing published for this version

  47. v0.28.1 20 May 2026

    Nothing published for this version

  48. v0.28.0 19 May 2026

    Nothing published for this version

  49. v0.27.3-0.20260519081350-0a741f748734 19 May 2026 pre-release

    Nothing published for this version

  50. v0.27.3-0.20260518210916-4bc4624708e3 18 May 2026 pre-release

    Nothing published for this version

  51. v0.27.2 12 May 2026

    Nothing published for this version

  52. v0.27.2-0.20260511140512-c68fdb28f0f8 11 May 2026 pre-release

    Nothing published for this version

  53. v0.27.1 11 May 2026

    Nothing published for this version

  54. v0.27.0 07 May 2026

    Nothing published for this version

  55. v0.26.1 30 Apr 2026

    Nothing published for this version

  56. v0.26.0 29 Apr 2026

    Nothing published for this version

  57. v0.25.0 28 Apr 2026

    Nothing published for this version

  58. v0.24.1 23 Apr 2026

    Nothing published for this version

  59. v0.24.0 22 Apr 2026

    Nothing published for this version

  60. v0.23.1 21 Apr 2026

    Nothing published for this version

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive