Skip to content

fix(deps): update toolhive - #867

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/toolhive
Open

fix(deps): update toolhive#867
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/toolhive

Conversation

@renovate

@renovate renovate Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
github.com/stacklok/toolhive v0.41.0v0.42.1 age confidence
github.com/stacklok/toolhive-core v0.0.37v0.0.38 age confidence

Warning

Some dependencies could not be looked up. Check the Dependency Dashboard for more information.


Release Notes

stacklok/toolhive (github.com/stacklok/toolhive)

v0.42.1

Compare 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 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

New Contributors

Full Changelog: stacklok/toolhive@v0.42.0...v0.42.1

🔗 Full changelog: stacklok/toolhive@v0.42.0...v0.42.1

v0.42.0

Compare Source

🚀 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

New Contributors

Full Changelog: stacklok/toolhive@v0.41.0...v0.42.0

🔗 Full changelog: stacklok/toolhive@v0.41.0...v0.42.0

stacklok/toolhive-core (github.com/stacklok/toolhive-core)

v0.0.38

Compare Source

  • mcpcompat: let resource handlers return _meta (#​211)
  • Dependency updates (aws-sdk-go-v2, go-containerregistry, sigstore-go, prometheus-client_golang, opentelemetry-go)

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • Between 12:00 AM and 03:59 AM, only on Monday (* 0-3 * * 1)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

ℹ️ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 48 additional dependencies were updated

Details:

Package Change
cel.dev/expr v0.25.1 -> v0.25.2
github.com/aws/aws-sdk-go-v2 v1.43.2 -> v1.43.3
github.com/aws/aws-sdk-go-v2/config v1.32.33 -> v1.32.34
github.com/aws/aws-sdk-go-v2/credentials v1.19.32 -> v1.19.33
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.33 -> v1.18.34
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.33 -> v1.4.34
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.33 -> v2.7.34
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.34 -> v1.4.35
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.14 -> v1.13.15
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.33 -> v1.13.34
github.com/aws/aws-sdk-go-v2/service/signin v1.5.2 -> v1.5.3
github.com/aws/aws-sdk-go-v2/service/sso v1.33.2 -> v1.33.3
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.2 -> v1.38.3
github.com/aws/aws-sdk-go-v2/service/sts v1.45.2 -> v1.45.3
github.com/aws/smithy-go v1.27.5 -> v1.27.6
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 -> v0.0.0-20250730155240-ffadbf3f398c
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 -> v0.0.0-20250524132541-c45532741eea
github.com/docker/cli v29.6.0+incompatible -> v29.6.2+incompatible
github.com/docker/docker-credential-helpers v0.9.3 -> v0.9.5
github.com/emicklei/go-restful/v3 v3.12.2 -> v3.13.0
github.com/go-ole/go-ole v1.2.6 -> v1.3.0
github.com/google/gnostic-models v0.7.0 -> v0.7.1
github.com/google/go-containerregistry v0.21.7 -> v0.21.8
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 -> v0.0.0-20250317134145-8bc96cf8fc35
github.com/moby/moby/api v1.54.2 -> v1.55.0
github.com/moby/moby/client v0.4.1 -> v0.5.1
github.com/prometheus/client_golang v1.23.2 -> v1.24.1
github.com/prometheus/common v0.67.5 -> v0.70.1
github.com/prometheus/procfs v0.20.1 -> v0.21.1
github.com/sagikazarmark/locafero v0.11.0 -> v0.12.0
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd -> v0.0.0-20260727124030-b80ff77dac4f
go.opentelemetry.io/contrib/propagators/b3 v1.21.0 -> v1.40.0
go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 -> v1.40.0
go.opentelemetry.io/otel v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/exporters/prometheus v0.66.0 -> v0.67.0
go.opentelemetry.io/otel/metric v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/sdk v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/sdk/metric v1.44.0 -> v1.45.0
go.opentelemetry.io/otel/trace v1.44.0 -> v1.45.0
go.opentelemetry.io/proto/otlp v1.10.0 -> v1.11.0
golang.org/x/tools v0.47.0 -> v0.48.0
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa -> v0.0.0-20260803160001-6ac0973c030d
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa -> v0.0.0-20260803160001-6ac0973c030d
google.golang.org/grpc v1.82.1 -> v1.83.0
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 -> v0.0.0-20251125145642-4e65d59e963e

@toolhive-release-app

Copy link
Copy Markdown
Contributor

🛡️ Skill Security Scan Results

⚠️ No skills were scanned in this PR.

@toolhive-release-app

Copy link
Copy Markdown
Contributor

🔒 MCP Security Scan Results

⚠️ No MCP servers were scanned in this PR.

@renovate
renovate Bot force-pushed the renovate/toolhive branch from 666c3ec to b576227 Compare August 10, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants