Repository navigation
Merge upstream v0.23.1 into dp-stable - #9
Merged
Merged
Conversation
* Add Kimi Code CLI as a supported MCP client * Regenerate CLI and API docs for kimi-cli * Fix indentation in AddAndVerifyMCPServer switch block * Add kimi-cli to swagger.yaml ClientApp enum
* Support stateless streamable-HTTP remote servers Remote MCP servers implementing the streamable-HTTP transport in stateless mode (POST-only, no SSE) show zero tools when proxied by ToolHive. The transparent proxy forwards all HTTP methods including GET; stateless servers don't serve SSE on GET, so clients never complete tool discovery. Add a --stateless flag to thv run that gates GET, HEAD, and DELETE requests at the proxy with 405, and switches the health check to a POST-based JSON-RPC ping appropriate for servers without an SSE endpoint. Fixes stacklok#4514 Signed-off-by: Greg Katz <gkatz@indeed.com> * Add --stateless flag to generated docs Regenerate CLI and Swagger docs to include the new --stateless flag added to the run command. Signed-off-by: Greg Katz <gkatz@indeed.com> * Apply stateless mode to all transports Move stateless configuration outside the remote-URL guard so --stateless works for both remote URLs and local container workloads. Add Allow header to 405 responses per RFC 9110. Update comments and flag text to remove remote-only language. Signed-off-by: Greg Katz <gkatz@indeed.com> * Add E2E test for stateless proxy mode Verify that --stateless makes the proxy reject GET and DELETE with 405 and forward POST to the upstream MCP server. Uses an in-process mock server to avoid external dependencies. Signed-off-by: Greg Katz <gkatz@indeed.com> --------- Signed-off-by: Greg Katz <gkatz@indeed.com>
Accept the caller's metadata map directly rather than re-loading it from storage. Any caller that detects a backend expiry already holds the session metadata (e.g. from MultiSession.GetMetadata), so the extra round-trip is unnecessary. Passing nil is treated as a silent no-op. Safety is preserved: updateMetadata uses SET XX, so a concurrently terminated (deleted) session is never resurrected. Closes stacklok#4667 Co-authored-by: taskbot <taskbot@users.noreply.github.com>
* Add more claude rules Generate more rules about style and testing, extrapolated from the comments and bugs in latest prs and reviews * changes from review --------- Co-authored-by: taskbot <taskbot@users.noreply.github.com>
…k#4773) * Add VS Code as a skill-supporting client * Add VS Code Insiders as a skill-supporting client
…ory (stacklok#4796) * Deduplicate client directories during skill extraction VS Code and VS Code Insiders both resolve to ~/.copilot/skills/ for user-scoped skills. When installing without --clients (the default), the system expands to all skill-supporting clients and attempts to extract to the same directory twice — the second extraction fails because the directory already exists. Add uniqueDirClients() to skip duplicate directories during extraction, pre-checks, and rollback while still recording all client names in the DB. Applied to all five extraction paths: fresh OCI, same-digest new clients, digest upgrade, fresh git, and existing git. Made-with: Cursor * Seed uniqueDirClients with existing client directories When a user first installs with --clients vscode and later re-installs without --clients (expanding to both vscode and vscode-insider which share ~/.copilot/skills/), toWrite contains only vscode-insider. The previous uniqueDirClients had nothing to deduplicate, so the stat check fired on the directory that vscode already created, returning a false StatusConflict. Add an occupiedDirs parameter to uniqueDirClients and a companion existingClientDirs helper that builds the seed set from the existing installed clients. The two affected paths (installExtractionSameDigest NewClients and applyGitInstallExisting new-clients branch) now pass the existing directories so that new clients sharing an already-owned directory are skipped. When all new clients are skipped, the DB record is still updated to include the new client names.
* Add Factory.ai as a supported MCP client * Add Factory.ai as a supported MCP client * Add Factory.ai as a supported MCP client
…acklok#4781) Adds an integration test for the ListCapabilities → Close() path that would fail if the context.Background() regression from stacklok#4613 were reintroduced. Uses a proper streamable-HTTP server (NewStreamableHTTPServer) so mcp-go issues a session ID and actually sends a DELETE on Close(). The assertion is a server-side atomic flag: if auth fails for the DELETE, authRoundTripper drops the request before it reaches the server, the flag stays false, and the test fails. Closes stacklok#4725 Co-authored-by: taskbot <taskbot@users.noreply.github.com>
…onverter (stacklok#4784) * Propagate protectedResourceAllowPrivateIP through OIDC resolver and converter The protectedResourceAllowPrivateIP CRD field was defined but never propagated through the OIDC resolver, and the vmcpconfig converter incorrectly mapped it from jwksAllowPrivateIP. This meant the field had no effect and could not be controlled independently. Add ProtectedResourceAllowPrivateIP to the resolver OIDCConfig struct, propagate it through inline, shared inline, and ConfigMap resolve paths, fix the converter to use the correct source field, and add the missing JwksAllowPrivateIP mapping. Fixes bugs 2 and 3 from stacklok#3142. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add ConfigMap independence test for protectedResourceAllowPrivateIP Address review feedback (F3): add a ConfigMap test case where jwksAllowPrivateIP is true but protectedResourceAllowPrivateIP is absent, verifying they resolve independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Document runtime OR behavior on private IP fields and fix missing converter mappings Add doc comments to JWKSAllowPrivateIP and ProtectedResourceAllowPrivateIP on both InlineOIDCSharedConfig and InlineOIDCConfig noting that at runtime either field being true enables private IPs for all OIDC HTTP requests. Also add missing JWKSURL and IntrospectionURL mappings in mapResolvedOIDCToVmcpConfigFromRef, which silently dropped these fields for the MCPOIDCConfig reference path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Regenerate CRD manifests after doc comment updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Regenerate CRD reference docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The constructor and functional options guarantee positive values for healthCheckInterval and healthCheckPingTimeout, making two zero-value guards unreachable. The redundant context timeout in checkMCPStatus capped the configurable ping timeout at 5s; the MCPPinger already enforces its own timeout internally. Fixes stacklok#4790 Signed-off-by: Greg Katz <gkatz@indeed.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…4799) * Add Goose, Gemini CLI, and Amp as skill-supporting clients Enable skill installation for three additional AI clients, all using the .agents/skills/ convention for both user and project scope. Each client also discovers skills from its native path (.gemini/skills/, ~/.config/agents/skills/, etc.) but .agents/ is the cross-client standard. * Fix --client flag name and default behavior in skills docs The CLI uses --clients (plural), and the default targets all skill-supporting clients, not just the first one.
) Add OpenAPI annotations to v0.1 skills API endpoints Add swag annotations to the two registry skills handlers introduced in stacklok#4753 so they appear in the generated OpenAPI spec: - GET /registry/{name}/v0.1/x/dev.toolhive/skills (list, paginated) - GET /registry/{name}/v0.1/x/dev.toolhive/skills/{ns}/{skill} (get) Add @description doc comments to skillsV01Response and skillsV01Metadata so swag emits them as named schema components. Regenerate docs/server/ to include the new paths and schemas.
…metrichttp to v1.43.0 [SECURITY] (stacklok#4683) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…p from 1.41.0 to 1.43.0 (stacklok#4684) Bump go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) from 1.41.0 to 1.43.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](open-telemetry/opentelemetry-go@v1.41.0...v1.43.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp dependency-version: 1.43.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
… skill-supporting clients (stacklok#4804)
* Add TelemetryConfigRef support to VirtualMCPServer CRD VirtualMCPServer can now reference shared MCPTelemetryConfig resources via spec.telemetryConfigRef, following the same pattern used by MCPServer and MCPRemoteProxy. This enables shared telemetry configuration, Kubernetes-native secret references for OTLP auth headers, CA bundle ConfigMap references, and per-server serviceName overrides. Implements changes for issue stacklok#4792: - Add TelemetryConfigRef field and CEL mutual exclusivity validation - Add TelemetryConfigHash to status for change detection - Add telemetry handler with batched statusManager pattern - Add MCPTelemetryConfig watch in SetupWithManager - Update converter to prefer TelemetryConfigRef over inline telemetry - Add CA bundle volumes and sensitive header env vars to deployment - Regenerate CRD manifests, RBAC, deepcopy, and mocks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Deprecate inline config.telemetry in favor of telemetryConfigRef Add deprecation comments to config.Config.Telemetry field (for operator context) and emit a warning log when inline telemetry is used during conversion, guiding operators to migrate to spec.telemetryConfigRef. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add integration tests, docs, and example for TelemetryConfigRef Add envtest-based integration tests for VirtualMCPServer TelemetryConfigRef: hash tracking, config change detection, missing ref condition, and CEL mutual exclusivity validation. Update observability docs to show the preferred telemetryConfigRef pattern for VirtualMCPServer and mark inline config.telemetry as deprecated. Add example manifest demonstrating shared MCPTelemetryConfig with VirtualMCPServer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address code review feedback for VirtualMCPServer TelemetryConfigRef Fixes from code review on PR stacklok#4801 for issue stacklok#4792: - Add VirtualMCPServer to MCPTelemetryConfig controller's findReferencingWorkloads, watch handler, and RBAC markers for deletion protection parity - Eliminate redundant MCPTelemetryConfig API calls (3x per reconcile) by fetching once in handleTelemetryConfig and threading the config through to converter and deployment builder - Change normalizeTelemetry to accept pre-fetched config instead of re-fetching - Fix deprecation log level from Info to V(1) debug to avoid log flooding - Remove stale TelemetryConfigRefValidated condition when TelemetryConfigRef is nil - Update architecture docs with VirtualMCPServer telemetryConfigRef edges Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Regenerate CRD API docs for TelemetryConfigRef Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The operator Helm chart was pinned to thv-registry-api v1.0.1. Bump to the latest release v1.1.1. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Grant default `aud` when `resource` is absent The `TokenHandler` only granted an audience claim when the client included the RFC 8707 `resource` parameter. Clients that omit this optional parameter received a token with an empty `aud`, which caused `VirtualMCPServer` to reject every token its own auth server issued: the incoming OIDC validator requires `audience` to be set and rejects tokens that do not carry a matching claim. When no `resource` parameter is present and `AllowedAudiences` contains exactly one entry, the handler now defaults to granting that audience. The intended audience is unambiguous in that case, and `AllowedAudiences` is always non-empty (enforced at config validation time). The defaulting is restricted to `authorization_code` grants; for `refresh_token` grants fosite carries the originally-granted audience forward through the stored session automatically, so re-granting would conflict with its matching strategy. Fixes stacklok#4794
Fosite validates that a granted audience appears in the client's own `GetAudience()` list when handling refresh tokens. The main `setupTestServer` helper registered the test client without an `Audience` field, so the check failed with HTTP 400 after the token handler began defaulting to the sole `AllowedAudience` on the initial `authorization_code` exchange. DCR registration always sets `Audience` to `AllowedAudiences`; align the test fixture with that production behaviour.
The subtests failed when `TOOLHIVE_DEV=true` was set in the shell
environment. `isDevMode()` reads that variable, causing the function to
use `http://` instead of `https://` and to skip SSRF rejection -- the
opposite of what the non-dev test cases assert.
Fix: remove `t.Parallel()` from `TestParseGitReference` and add
`t.Setenv("TOOLHIVE_DEV", "")` so the test always runs in a clean
environment, matching the pattern used by `TestParseGitReferenceDevMode`.
…tacklok#4807) Add ConfigMap content assertions to telemetry integration tests Verify the vmcp-config ConfigMap contains the correct telemetry settings from the referenced MCPTelemetryConfig: endpoint (with https:// prefix stripped), tracing/metrics enabled flags, and that the ConfigMap updates when the MCPTelemetryConfig endpoint changes. This ensures the full data path is tested end-to-end: CRD spec → converter → ConfigMap → telemetry config, not just status conditions. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…k#4809) * Change GroupRef from bare string to typed MCPGroupRef struct GroupRef was a bare string while every other cross-CRD reference (ExternalAuthConfigRef, ToolConfigRef, AuthServerRef, EmbeddingServerRef) uses a typed struct. This inconsistency prevented extending GroupRef with additional fields (like namespace) without a breaking change. Define MCPGroupRef struct with Name field and nil-safe GetName() helper. Replace GroupRef string with *MCPGroupRef on MCPServerSpec, MCPRemoteProxySpec, MCPServerEntrySpec, and add a new top-level GroupRef field on VirtualMCPServerSpec that takes precedence over the deprecated config.groupRef string path. Internal types (vmcpserver.Config, StatusResponse, BackendReconciler, config.Config) remain as strings since they are not CRD API types. This is a breaking wire-format change (groupRef: "name" becomes groupRef: {name: "name"}) that must happen before v1beta1. Closes stacklok#4634 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review feedback on MCPGroupRef refactoring - Add nil guard to MCPServerEntry validateGroupRef using GetName() instead of direct .Name access to prevent potential panic - Add unit tests for MCPGroupRef.GetName() and VirtualMCPServer.ResolveGroupName() covering precedence logic - Normalize nil-check pattern across all field index extractors to use GetName() == "" (handles both nil and empty name) - Mark config.Config.Group as optional and deprecated since spec.groupRef is now the preferred path - Improve Validate() error message to mention both spec.groupRef and config.groupRef paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update missed example and docs for MCPGroupRef wire format - Fix vmcp_with_telemetry_ref.yaml (missed in initial pass) - Update virtualmcpserver-api.md to document spec.groupRef as primary field and update all YAML examples to struct format - Update virtualmcpcompositetooldefinition-guide.md and virtualmcpserver-observability.md examples - Regenerate crd-api.md to show MCPGroupRef typed struct Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eters (stacklok#4712) * Add --remote-auth-scope-param-name flag for non-standard OAuth scope parameters Some OAuth providers use non-standard query parameter names for scopes in the authorization URL. For example, Slack's OAuth v2 requires user-token scopes in "user_scope" instead of the standard "scope" parameter. This causes ToolHive's OAuth flow to fail with invalid_scope errors when connecting to providers like Slack's MCP server. Add a new --remote-auth-scope-param-name flag that allows users to override the query parameter name used for scopes. When set, scopes are sent under the specified parameter name and the standard "scope" parameter is cleared. The oauth2Config.Scopes field is preserved so token refresh requests continue to work correctly. Signed-off-by: Gustavo Gomez <gmogmz@indeed.com> * Address review feedback: fix empty scope= and OIDC fallback path - Replace SetAuthURLParam("scope", "") with temporarily nil-ing oauth2Config.Scopes before AuthCodeURL, then restoring via defer. This omits the scope parameter entirely instead of producing an invalid empty scope= (RFC 6749 §3.3). - Propagate ScopeParamName on the OIDC discovery fallback path in createOAuthConfig, so --remote-auth-scope-param-name works with --remote-auth-issuer as well. - Strengthen test assertion to verify scope parameter is truly absent, not just empty-valued. Signed-off-by: Gustavo Gomez <gmogmz@indeed.com> * Fix missing swagger docs for scope_param_name field Signed-off-by: Gustavo Gomez <gmogmz@indeed.com> --------- Signed-off-by: Gustavo Gomez <gmogmz@indeed.com>
* Update VERSION for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update values.yaml for release Release-Triggered-By: blkt * Update values.yaml for release Release-Triggered-By: blkt * Update values.yaml for release Release-Triggered-By: blkt * Update VERSION for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update README.md for release Release-Triggered-By: blkt * Update Chart.yaml for release Release-Triggered-By: blkt * Update README.md for release Release-Triggered-By: blkt * Update values.yaml for release Release-Triggered-By: blkt
The values-openshift.yaml was a copy of the default values with outdated image tags (v0.15.0) and different resource limits. It provided no OpenShift-specific configuration and was misleading as a reference. Remove it along with the README references that pointed to it. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add script engine public API types Introduce the pkg/script/ package with the public interface for vMCP code mode. The Executor interface provides Execute() for running Starlark scripts and ToolDescription() for tools/list injection. Tool binds MCP tool metadata with a dispatch callback. Config holds execution parameters (step limit, parallel concurrency cap, tool call timeout). Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add Starlark execution engine in internal/core Core execution engine that wraps user scripts in a function body for top-level return support, enforces step limits to prevent runaway computation, and captures print() output as logs. This is the lowest layer of the script package — it only knows about Starlark, not MCP. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use require/table-driven tests per testing guidelines Convert tests in description_test.go and execute_test.go to use testify require and table-driven structure per project testing rules. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add Go/Starlark type conversions and MCP result parsing Introduce internal/conversions with three concerns: - Bidirectional Go ↔ Starlark type conversion (preserving JSON number fidelity by promoting whole float64s to Int) - MCP result parsing that handles structured content with/without the mcp-go SDK wrapper, text content as JSON or plain string, and error results - Tool name sanitization for converting MCP names to valid Starlark identifiers Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add parallel(), call_tool(), and tool callable builtins Introduce internal/builtins with: - Tool callable factory supporting positional args (arg0, arg1, ...), keyword args, and mixed calling conventions - call_tool("name", ...) for dispatching to tools by original name when the name is not a valid Starlark identifier - parallel() builtin for concurrent fan-out with optional concurrency limit via semaphore - BuildToolMap for collision-safe name → callback mapping Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Wire executor to internal packages with integration tests Connect the public Executor interface to internal/core, internal/builtins, and internal/conversions. The executor builds a Starlark environment from bound tools (each as a callable with positional+kwargs support), injects data arguments, executes the script, and wraps the result as an mcp.CallToolResult with optional log content. Integration tests verify multi-tool scripts, loops/conditionals, parallel fan-out, data arguments, call_tool dispatch, step limit enforcement, and log capture. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address self-review findings Fix correctness and safety issues found during code review: - Fix data race: parallel() child threads now get their own log buffers instead of sharing the parent Print callback. Logs are merged after wg.Wait() completes. - Propagate step limit to parallel() child threads so scripts cannot bypass CPU limits via parallel([lambda: infinite_loop()]). - Thread context.Context into parallel() for cancellation support. Semaphore acquire and task launch check ctx.Done(). - Set thread.Load to return an error instead of nil (prevents panic if a script uses a load() statement). - Reject data argument keys that shadow builtins or tool names instead of silently overwriting them. - Return errors for invalid data argument types instead of silently dropping them. - Simplify builtins package to expose a single Build() function that returns globals and reserved names. All other functions are unexported. - Consolidate builtins tests into a single test file using table-driven tests. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review comments - Remove reserved set from builtins.Build() — caller checks globals keys directly to prevent data argument shadowing - Consolidate integration tests into a single table-driven TestExecutor - Add test for automatic JSON string → structured data conversion Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address automated review comments - Make per-tool-call timeout a caller responsibility: remove ToolCallTimeout from Config and document the contract on Tool.Call. Add test proving caller-set context deadlines are respected. - Fix UTF-8 truncation in GenerateToolDescription: slice by rune count instead of byte index to avoid splitting multi-byte characters. - Run go mod tidy to mark go.starlark.net as a direct dependency. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address reviewer feedback on parallel execution and edge cases - Introduce pkg/foreach with generic bounded concurrent iteration using a worker pool. Goroutine count matches the concurrency limit, not the task count. Reusable outside pkg/script. - Rewrite parallel() to use foreach.Concurrent instead of raw goroutines, fixing the goroutine explosion for large task lists. - Divide step budget evenly across parallel children (stepLimit/N per child) to prevent amplification via nested parallel() calls. - Document wrapScript triple-quoted string limitation. - Strengthen collision test to assert dispatch reaches the correct tool. - Clamp negative ParallelMax to 0 (unlimited) in resolveConfig. Part of stacklok#4742 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
) When the vMCP has an embedded auth server, ensureOIDCDiscovered() fails fatally if the issuer URL is unreachable from inside the cluster, preventing the local key provider from ever being consulted. Make the discovery error non-fatal when a keyProvider is configured. The local provider resolves signing keys in-process, so HTTP-based OIDC discovery is not required for token validation. When discovery fails with a keyProvider present, mark it as completed to avoid the per-request retry penalty (3 attempts with backoff). When the issuer IS reachable, discovery succeeds normally and the HTTP JWKS fallback remains available for key ID misses against the local provider. Also improve the error message when the local provider cannot resolve a key and no JWKS URL is available, and add a constructor log distinguishing deferred discovery from non-fatal discovery. Fixes stacklok#4747 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The "Edit with explicit port" test case used port 9090 in the request body. The port-availability check (`networking.IsAvailable`) makes a real TCP bind, so the test failed whenever port 9090 was already in use on the host machine. Move the test case out of the static table and into a standalone sub-test that allocates a free port at runtime via `net.Listen` with `":0"`. The port is released before the HTTP handler runs so the availability check can bind it. Also add the missing `net` and `require` imports that this approach needs.
* Split skillsvc.go into per-concern files skillsvc.go had grown to ~2000 lines mixing ten concerns (service construction, OCI install, git install, extraction, client resolution, registry lookup, content fetching, build/push, uninstall, list/info). Split it into focused files within the same skillsvc package: service, install, install_oci, install_git, install_extraction, clients, oci, registry, content, list, uninstall, build, scope. All symbols stay in the same package, so there are no circular-import risks and no exported API changes. No behavioural changes, no renames, no signature changes. * Split skillsvc_test.go into per-concern test files skillsvc_test.go had grown to ~3900 lines. Split it into per-area test files mirroring the production layout, with shared fixtures extracted into testhelpers_test.go. Each Test* function is moved verbatim — no behavioural changes to any test. * Remove original skillsvc.go and skillsvc_test.go The previous two commits added the split files but left the originals in place. Delete them now that all symbols have been moved to per-concern files.
…4957) Commit c334edf ("vmcp: stop the TEI manager on Start failure and apply image default in Serve") renamed the unexported defaultTEIImage constant to the exported DefaultEmbeddingImage so Serve() can default the TEI image alongside DefaultEmbeddingModel. The production files were updated but embedding_manager_test.go was missed, leaving three references to the now-undefined identifier. Golangci-lint catches it as a typecheck error, which fails Lint Go Code and Test Go Code on every PR opened against main. Update the three remaining references so the package compiles and CI goes green. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump the default go transport builder image from golang:1.25-alpine to golang:1.26-alpine so new go-scheme workloads build with the current stable toolchain. Users can still pin an older release via --runtime-image when a project needs it. Also correct two pre-existing doc references that claimed the Go runtime stage uses alpine:3.22 -- go.tmpl already pins alpine:3.23, so the docs were drifted. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Return 502 Bad Gateway when OCI content pull fails getContentFromOCI previously mapped registry.Pull failures to 400 Bad Request, but auth errors, unreachable registries, missing manifests, and network timeouts are upstream failures the caller cannot fix by changing their input. Align with getContentFromGit, which already returns 502 Bad Gateway for git resolve failures, so clients see consistent semantics across both backends. * Return 502 Bad Gateway when OCI install pull fails installFromOCI previously mapped registry.Pull failures to 400 Bad Request, which incorrectly blamed the caller for upstream registry problems (auth, network, manifest unknown). Return 502 Bad Gateway to match the behaviour just applied to GetContent and the existing git install path.
Bump the default uvx transport builder image from python:3.13-slim to python:3.14-slim so new uvx-scheme workloads run on a supported Python release with the latest security updates. Users can still pin an older release via --runtime-image when a package needs it. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump the default npx transport builder image from node:22-alpine to node:24-alpine. Node 24 is the current active LTS release; Node 22 has moved to maintenance. New npx-scheme workloads built with defaults now run on the current LTS line. Users can still pin an older release via --runtime-image when a package needs it. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tacklok#4932) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
…acklok#4960) Follow-up to stacklok#4956: installFromOCI now returns 502 on upstream pull failures (matching installFromGit), but the @failure list on the installSkill swagger annotation still only advertised 400/409/500. Add @failure 502 {string} string "Bad Gateway" and regenerate the spec so clients see the real contract for POST /api/v1beta/skills.
* Add registry-aware workload creation
Add optional registry and server fields to POST /api/v1beta/workloads
so clients can create workloads by referencing a registry server:
{"registry": "default", "server": "io.github.stacklok/fetch"}
thv resolves the server from the registry, extracts image/transport/
env vars/permissions/etc from metadata, and builds the RunConfig
server-side. User-provided fields always override registry defaults.
This eliminates client-side field extraction — Studio goes from
GET+extract+POST to just POST.
Ref: stacklok#4199
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Regenerate OpenAPI spec for registry-aware workload fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address review findings for registry-aware workload creation
- Fix handler validation: accept registry+server as alternative to
image/url (was rejecting before resolution could run)
- Fix validation ordering: check registry+server pairing before
attempting resolution
- Validate registry name: only "default" is currently supported,
reject unknown values with clear error
- Regenerate OpenAPI spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Preserve registry metadata through BuildFullRunConfig
Return ServerMetadata from resolveRegistryServer and assign it to
the local serverMetadata/imageMetadata variables so downstream code
(registry info on RunConfig, tool validation, remote auth config,
proxy port) has access to it.
Skip the imageRetriever re-lookup when metadata was already resolved
from a registry reference — re-looking up by bare image ref would
fail since the image ref doesn't match any server name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Complete remote server metadata handling and address review feedback
Fix remote server path to get the same treatment as image servers:
- Extract ProxyPort from RemoteServerMetadata in registry assignment
- Build full remoteAuthConfig from registry OAuthConfig (scopes,
issuer, authorize/token URLs, PKCE, headers, env vars)
- Previously only URL and Headers were copied; OAuth, EnvVars, and
ProxyPort were silently dropped
Other fixes:
- Make resolveRegistryServer a method on WorkloadService using
s.configProvider instead of creating a separate config.NewProvider()
- Improve validation: check partial registry+server before the
generic "image or url required" error for better DX
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix remote auth overwrite, add image provenance verification
Critical fixes found by review:
Remote server path:
- Guard `if req.URL != ""` to skip createRequestToRemoteAuthConfig
when metadata was resolved from registry (was unconditionally
overwriting the registry-built remoteAuthConfig, dropping all
OAuth fields, EnvVars, and ProxyPort)
- Add ClientSecret/BearerToken handling from user request on top
of registry-resolved OAuth config
Image server path:
- Add image provenance verification (VerifyImage) for registry-
resolved images. The normal imageRetriever path calls this
internally but registry references bypass it.
- Export VerifyImage from retriever package for reuse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add proper HTTP status codes to registry resolution errors
- Unknown registry → 400 Bad Request (was 500)
- Server not found → 404 Not Found (was 500)
- Registry provider unavailable → 503 Service Unavailable (was 500)
- Partial registry+server guard → 400 Bad Request (was 500)
- Fix misleading error message: "not found in registry" → "failed
to look up in registry" for non-not-found errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add unit tests for registry-aware workload helpers
Cover applyImageDefaults (env var merge, user override precedence),
applyRemoteDefaults, applyRegistryDefaults dispatch, and the
resolveRegistryServer 400 path for unknown registries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Extract remote auth config builder helper
The registry-resolved RemoteServerMetadata branch and the
imageRetriever-returned remote branch in BuildFullRunConfig built
remote.Config from registry metadata in identical ways. Extract
buildRemoteAuthConfigFromMetadata so future changes to the OAuth
config shape land in one place.
Also drop the registry+server pairing guard from BuildFullRunConfig
since the route handler already rejects partial pairs with a 400
before the service is called.
Add unit tests covering nil OAuthConfig, full field population,
resource precedence (user > metadata > URL-derived), and CLI-string
formatting for user-provided ClientSecret and BearerToken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tacklok#4964) The dual-claim extraction, dot-notation traversal, and merge-order fix from 88937fce lacked direct unit tests for helper functions and had no integration tests exercising group-based Cedar policies through the middleware stack. This closes both gaps. Tier 1 — unit tests for parseCedarEntityID, sanitizeURIForCedar, extractClientIDFromClaims, preprocessClaims, preprocessArguments, mergeContexts, and IsAuthorized entity merge priority. Adds edge cases to existing TestResolveNestedClaim (ambiguity) and TestAuthorizeWithJWTClaims_DualClaim (same claim, missing JWT key, non-array graceful handling, both dot-notation). Tier 2 — integration tests for group-based list filtering (Entra dual, Entra groups, Okta URI, Keycloak nested, prompts, resources), non-list operations (tool call, prompt get, resource read), transitive THVGroup→THVRole hierarchy through middleware, and upstream provider group extraction. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add E2E tests for thv vmcp CLI quick mode and config-file mode Add test/e2e/vmcp_cli_test.go covering the two serve modes: quick mode (--group) and config-file mode (init → validate → serve --config), each with an MCP client connectivity assertion that backend tools are exposed. Includes a binding test confirming the server listens only on 127.0.0.1, and negative tests for missing flags and a nonexistent config path. Closes stacklok#4888 * fixes from review --------- Co-authored-by: taskbot <taskbot@users.noreply.github.com>
The registry-resolved path in BuildFullRunConfig hardcoded retriever.VerifyImageWarn while the imageRetriever path threaded verification through its argument list. Keeping two inline literals means the paths can silently drift if one is later made configurable. Store the mode on WorkloadService (still defaulting to warn) and read it in both call sites so changing behavior is a single-line edit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The vmcp subcommand is not ready for release. Mark the parent command Hidden so it does not appear in help output or shell completion, while remaining functional for internal testing. Regenerated CLI docs drop the now-hidden pages. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update VERSION for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update values.yaml for release Release-Triggered-By: JAORMX * Update values.yaml for release Release-Triggered-By: JAORMX * Update values.yaml for release Release-Triggered-By: JAORMX * Update VERSION for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update README.md for release Release-Triggered-By: JAORMX * Update Chart.yaml for release Release-Triggered-By: JAORMX * Update README.md for release Release-Triggered-By: JAORMX * Update values.yaml for release Release-Triggered-By: JAORMX
…lok#4965) When serverName is configured on the Cedar authorizer, resource entities (Tool, Prompt, Resource) need an MCP parent so that server-scoped Cedar policies like `resource in MCP::"github"` evaluate correctly. Without this parent, those policies silently match nothing and deny all requests. Add serverName parameter to CreateEntitiesForRequest and build an MCP::"<serverName>" parent UID when non-empty. Thread a.serverName through all four authorize* methods in core.go. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…lok#4905) * Default redirectUri on upstream providers to {resourceUrl}/oauth/callback The MCPExternalAuthConfig CRD documents that redirectUri defaults to {resourceUrl}/oauth/callback when not specified, but the operator was passing the empty string through to the proxy runner, which then rejected it with "redirect_uri is required". Pass resourceURL through BuildAuthServerRunConfig to buildUpstreamRunConfig so empty RedirectURI fields on both OIDC and OAuth2 upstream providers are resolved to the documented default before generating the runconfig. This applies to both MCPServer and VirtualMCPServer code paths. Fixes stacklok#4874 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add unit test for deriveResourceURL in converter_test.go Address code review feedback: add TestDeriveResourceURL table-driven test matching the convention established by TestDeriveAllowedAudiences and TestDeriveScopesSupported. Covers nil IncomingAuth, nil OIDC, empty Resource, and populated Resource cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Graduate CRDs from v1alpha1 to v1beta1 Signal API stability by promoting all ToolHive CRDs to v1beta1 while preserving backwards compatibility — the CRDs serve both versions simultaneously so existing v1alpha1 resources survive the upgrade untouched and users migrate manifests at their own pace. v1alpha1 and v1beta1 are schema-identical. The new cmd/thv-operator/api/v1alpha1/ package defines only the root resource types (MCPServer, MCPGroup, ...) as thin wrappers whose Spec and Status fields reference the canonical types from v1beta1. controller-gen walks field types when building the OpenAPI schema, so both versions resolve to the same schema without any duplication of the actual model — I verified empirically that all 12 CRDs produce structurally identical schemas (modulo the legitimately-different root descriptions). The upgrade is orchestrated through the `served` and `storage` flags on each version entry: the new CRDs list both versions as served, mark v1beta1 as storage, and mark v1alpha1 as deprecated with a warning. No conversion webhook is configured, which defaults to strategy: None — safe because the schemas are identical. Kubernetes swaps the apiVersion string when serving an object at a different version than it was stored at. Users can upgrade the CRD chart without deleting anything, keep reading/writing at v1alpha1 indefinitely, and migrate to v1beta1 by re-applying manifests. Once status.storedVersions no longer contains v1alpha1, a future release can drop the v1alpha1 entry and remove the wrapper package. What changed: - Mechanical rename from v1alpha1 to v1beta1 across all Go imports, YAML apiVersion strings, Helm deploy manifests, chainsaw fixtures, examples, docs, and generated artifacts. - New cmd/thv-operator/api/v1alpha1/ package: 12 root types + 12 list types as thin wrappers over v1beta1 Spec/Status, plus scheme registration. Each root type carries a +kubebuilder:deprecatedversion warning so kubectl prints a migration hint on every access. - Register both schemes in cmd/thv-operator/main.go. - Regenerated CRD manifests list both versions (v1alpha1 deprecated and non-storage, v1beta1 storage) with conversion.strategy: None. - Removed the obsolete pre-upgrade Helm hook that blocked upgrades when v1alpha1 resources existed — no longer needed since those resources now survive the upgrade. - Drive-by: regenerate pkg/audit/zz_generated.deepcopy.go which had drifted after DetectApplicationErrors was added to Config without re-running controller-gen. Closes stacklok#2556 * Update test files to use v1beta1 after merge Two test files that came in via the merge from main still reference the v1alpha1 Go package, which no longer exists on this branch. The operator binaries compile fine, but CI lint/test fail on the missing mcpv1alpha1 symbol in the test package. - cmd/thv-operator/controllers/mcpremoteproxy_deployment_test.go - cmd/thv-operator/controllers/mcpserver_resource_overrides_test.go Applied the same mcpv1alpha1 → mcpv1beta1 rename that the rest of the branch uses. No behavioural change.
* Update VERSION for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update values.yaml for release Release-Triggered-By: ChrisJBurns * Update values.yaml for release Release-Triggered-By: ChrisJBurns * Update values.yaml for release Release-Triggered-By: ChrisJBurns * Update VERSION for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update README.md for release Release-Triggered-By: ChrisJBurns * Update Chart.yaml for release Release-Triggered-By: ChrisJBurns * Update README.md for release Release-Triggered-By: ChrisJBurns * Update values.yaml for release Release-Triggered-By: ChrisJBurns
refreshCache() was returning stale cached data for any API error, including auth failures (401/403 from registry and OAuth flow timeouts). Users saw previously-cached registry content with no error after their credentials expired or the OAuth browser flow timed out. Fix in two parts: - auth.Transport.RoundTrip now wraps all Token() errors with ErrRegistryAuthRequired, classifying token-acquisition failures as auth errors regardless of the underlying cause. - refreshCache() checks for ErrRegistryAuthRequired/ErrRegistryUnauthorized before the stale-cache fallback; transient errors (network, 5xx) still degrade gracefully. Fixes stacklok#4950 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…in (stacklok#4982) * Bundle CRD manifests as a release asset Adds a `thv-crds.tar.gz` tarball to each release containing the CRD YAML manifests from `deploy/charts/operator-crds/files/crds/`. Motivation: downstream consumers of the CRDs (notably stacklok/docs-website, which generates per-CRD reference pages) currently have to clone the entire toolhive repo at each release tag just to read these 13 manifests. Shipping them as a ~94KB tarball asset lets those consumers skip the clone and just `gh release download` like they already do for `thv-cli-docs.tar.gz` and `swagger.yaml`. Purely additive — no changes to existing release assets or workflows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Re-export toolhive-core schemas as release assets Reads the toolhive-core version from go.mod at release time, downloads the four JSON schema files from that version of stacklok/toolhive-core, and ships them alongside toolhive's own release assets. Motivation: downstream consumers (notably stacklok/docs-website) currently have to replicate this logic: read go.mod, derive the core version, then fetch from a different repo's release. Re-exporting the schemas here makes toolhive's release self-contained — one `gh release download` call gets everything. Paired with stacklok#4982 (CRD manifests as release asset); together these eliminate the need for docs-website to clone toolhive or hit a second repo during its release-doc regeneration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Retire the docs-website repository_dispatch chain docs-website no longer consumes `repository_dispatch: published-release` events — its reference-doc regeneration now runs via a Renovate-driven pipeline that reads the new `thv-crds.tar.gz` and re-exported core schemas introduced earlier in this PR. See stacklok/docs-website#748 for context. Changes: - Delete `.github/workflows/update-docs-website.yml` (the dispatch sender, called via workflow_call from `releaser.yml`). - Remove the `update-docs-website` job from `releaser.yml` along with its `extract-release-actor` dependency, which only existed to feed the dispatch's `assign_to` input. - Update `notify-release-failure`'s `needs:` list to drop the removed jobs. Follow-up for maintainers: the `DOCS_REPO_DISPATCH_TOKEN` repo secret can be retired — nothing references it after this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add advisory CRD schema compatibility CI check The v1beta1 graduation in stacklok#4849 made a stability commitment that is enforced only by convention today. Adds a new api-compat workflow that runs crd-schema-checker on every PR touching operator API surface and reports compatible/incompatible/unknown status in the job step summary. Phase 1 is advisory: the job runs with continue-on-error so findings are surfaced without blocking merges. Phase 2 will flip enforcement and add the api-break-allowed escape hatch label. Part of stacklok#4972. Baseline is the last published CRD chart from OCI, falling back to origin/main when the chart for the latest tag is not yet published (the release-to-publish lag window). NoBools and NoMaps style validators are disabled — they are OpenShift API-style rules, not compat-breaking checks, and fire on fields we legitimately use. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review feedback on API compat workflow Apply findings from the multi-agent consensus review. Summary: - SHA-pin crd-schema-checker to 3fee146022bf — the tool has no release tags, and @latest left CI non-deterministic and exposed the runner to supply-chain risk (continue-on-error does not sandbox the step). - Add timeout-minutes: 10 — expected runtime is ~3 min; the bound protects against a hung helm pull or go install without the 6h default burning CI budget on a genuine hang. - Drop 2>/dev/null from helm pull — its stderr is one useful line when the fallback path activates; redirecting hid transient ghcr.io outages behind the expected "tag not yet published" case. - Narrow path filter to files/crds/** plus the workflow self-watch. templates/** is caught by operator-ci's generate-crds drift check; values.yaml and crd-helm-wrapper only change Helm conditionals and annotations the checker ignores, and the workflow's explicit --set flags force every CRD to render regardless. - Route step outputs through env: blocks — defense-in-depth against future edits that might route PR-controlled data through them. Count-floor and origin/main fallback rework deferred: the floor would generate false positives on legitimate CRD add/remove, and the origin/main rework needs design discussion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fail-closed when baseline chart is unavailable Remove the origin/main fallback in baseline resolution. Falling back silently compares subsequent PRs against an already-broken baseline once a breaking change lands on main, narrowing the Phase 2 enforcement guarantee. Better to fail the job and wait for the release chart to publish than to emit a false green. Phase 1's continue-on-error means a failure here does not block PRs during the release-to-OCI publish window. Phase 2 will cover that window via the api-break-allowed label if needed. Addresses review finding F5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop design doc reference from workflow comment The design doc is a planning artifact and is not committed to the repo, so the reference is a dead link for anyone reading the workflow file. The workflow's own inline comments already explain the rollout phases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update VERSION for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update values.yaml for release Release-Triggered-By: rdimitrov * Update values.yaml for release Release-Triggered-By: rdimitrov * Update values.yaml for release Release-Triggered-By: rdimitrov * Update VERSION for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update README.md for release Release-Triggered-By: rdimitrov * Update Chart.yaml for release Release-Triggered-By: rdimitrov * Update README.md for release Release-Triggered-By: rdimitrov * Update values.yaml for release Release-Triggered-By: rdimitrov
v0.23.1 is the upgrade target: v0.23.0 plus an auth fix. It moves the
API to v1beta1 (storage v1beta1, conversion None, schemas identical to
v1alpha1), so one merge covers v0.20 through v0.23.
Transition shims, so the shared CRDs keep admitting production objects
that are still managed by the v0.19 operator:
- MCPServer.spec.groupRef accepts both a plain string and {name}. The
field is schemaless and MCPGroupRef.UnmarshalJSON decodes either
form; any other shape decodes to an empty name so one bad object
cannot break the operator cache. CEL cannot type-check a schemaless
field, so invalid values surface through GroupRefValidated instead.
- VirtualMCPServer.spec.groupRef stays optional in the schema; the
controller still rejects objects without it.
Conflict resolution:
- vMCP serve logic moved to pkg/vmcp/cli upstream; the fork's backend
timeouts, session TTL, write timeout and session owner URL are
ported there.
- The fork session manager is kept. DataStorage keeps Upsert, which the
fork's HA session paths still use. Upstream's restored-metadata
write-back (stacklok#4842) is taken; its lazy tool injection hooks are
dropped because the fork already repairs cross-replica sessions on
request initialization.
- Backend session hints from restore are accepted but not forwarded,
since backends that reject unknown Mcp-Session-Id values would fail
the restore; lost sessions are recovered through reconnect.
- The token handler keeps defaulting the audience on every grant type
(client credentials rely on it) and treats an empty resource as
absent.
- Fork-only CRD fields are kept: sessionTTL, sessionStorage.usernameRef,
allowedClientIds, optional oidcConfigRef.audience, redis address and
secrets list keys. The duplicate MCPOIDCConfigReference.resourceUrl
both sides added is collapsed into upstream's.
- Regenerated deepcopy, CRDs, CRD API docs and chart READMEs; the
release-qualified operator ClusterRole name is preserved.
PAIINFRA-200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
davidrubio-dp
approved these changes
Sep 29, 2026
BraulioV
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v0.23.1 is the target of the toolhive upgrade: v0.23.0 plus an auth fix. After the PAIINFRA-198 merge,
dp-stablesits at v0.19.0. v0.20–v0.23 bring thev1beta1API. Storage moves tov1beta1withconversion: None, and the schemas are identical tov1alpha1. So one merge covers the whole range. The operator CRDs are shared by the prod (v0.19) and stg operators in every cluster, so this merge also adds shims that keep the new CRDs admitting the objects prod still has.Nothing is deployed by this PR. The rollout is PAIINFRA-201.
v0.23.1intodp-stable(117 upstream commits, 38 conflicting files). Merge with a merge commit, not squash.MCPServer.spec.groupRefshim. Upstream v0.20 made it object-only, but every prod MCPServer uses a plain string. The field is now schemaless in the CRD, andMCPGroupRef.UnmarshalJSONdecodes both"name"and{name: ...}. Any other shape decodes to an empty name, so a single bad object can't break list/watch decoding in the operator;GroupRefValidatedthen reports it.VirtualMCPServer.spec.groupRefshim. It stays optional in the CRD, because prod objects don't set it until PAIINFRA-201. The controller still rejects objects without it.sessionTTL,sessionStorage.usernameRef,allowedClientIds, optionaloidcConfigRef.audience, redisaddress, and thesecretslist keys (name+key).Type of change
Test plan
Unit tests (
task test)Linting (
task lint-fix; unrelated rewrites it made to upstream files were reverted)Manual testing (describe below)
task build,go vet ./...,task operator-generate,task operator-manifests,task crdref-genandtask helm-docsall ran. The generated files are committed.task test: two packages fail, and both fail the same way ondp-stablebefore this merge:pkg/vmcp/session/internal/security(TestRestoreHijackPrevention_AuthenticatedRoundTrip) andpkg/webhook/validating(TestValidatingMiddleware_HTTP422AlwaysDenies).pkg/vmcp/aggregatorhas a data race in a fork test that is flaky on both branches.task lint: 86 findings, against 90 ondp-stable. Nothing new beyond the cappedgoconstlist reporting different test files.envtest integration suites (k8s 1.31): EmbeddingServer, MCPExternalAuthConfig, MCPGroup, MCPOIDCConfig, MCPTelemetryConfig and MCPToolConfig pass. MCPRegistry (17/21), MCPRemoteProxy (4) and MCPServer (1) fail with the same specs on
dp-stable. The VirtualMCPServer composite-tool watch spec is flaky under-pon both branches, and passes when run alone.New envtest cases in
mcpserver_cel_validation_integration_test.gocheck thegroupRefshim against a real API server:v1beta1andv1alpha1;List.Prod objects against the new CRDs. I rendered
toolhive-config/toolhive-config-stgfromplatform-apps@developfor every environment. Each toolhive CR was then dry-run created on an envtest API server and decoded into the newv1beta1Go types:dp-stable: 263/263 pass;i0hub VirtualMCPServers, whoseoutgoingAuth.backends.*.type: external_auth_config_refwas removed upstream in v0.21;groupRef.Does this introduce a user-facing change?
No. Nothing is deployed until PAIINFRA-201.
Special notes for reviewers
Rollout gate for PAIINFRA-201: prod must run
toolhive-config≥ 0.18.0 before these CRDs roll out, or the 5i0hub VirtualMCPServers stop validating.For review, compare against upstream:
git diff v0.23.1 origin/chore/merge-upstream-v0.23.1is the full fork delta (177 files, mostly fork code carried over from before this merge).git diff origin/dp-stable...is dominated by the upstream changes.Non-obvious resolutions:
groupRefshim. The ticket planned a CEL rule, but Kubernetes can't build CEL type information for a schemaless field. That's true both from the parent and on the field itself; the API server rejects the CRD. Validation therefore happens in the operator, not at admission.pkg/vmcp/cli(Extract vMCP serve/validate logic into pkg/vmcp/cli/ stacklok/toolhive#4891). The fork's backend init/request timeouts,sessionTTL, write timeout and session-owner URL are ported topkg/vmcp/cli/serve_timeouts.go, along with their tests.DataStorage.Upsertis restored: upstream Remove Upsert from DataStorage interface — closes #4726 stacklok/toolhive#4797 removed it, but the fork's HA session paths still call it.checkSessionalso compares only backend IDs.BeforeListTools/BeforeCallToollazy tool injection is dropped. The fork already repairs cross-replica sessions inOnRequestInitialization, which runs first.Mcp-Session-Idon Initialize would fail restores against backends that reject unknown sessions (e.g. mcp-for-argocd). The fork already recovers lost sessions by reconnecting.audwhenresourceis absent stacklok/toolhive#4805 limits that toauthorization_code. The one upstream change taken: an emptyresourcecounts as absent.MCPOIDCConfigReference.resourceUrlwas added by both sides, and git's rename detection produced a duplicate field. It's collapsed into upstream's.🤖 Generated with Claude Code